summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/AjaxDispatcher.php60
-rw-r--r--includes/AjaxFunctions.php85
-rw-r--r--includes/AjaxResponse.php19
-rw-r--r--includes/Article.php1138
-rw-r--r--includes/AuthPlugin.php60
-rw-r--r--includes/AutoLoader.php283
-rw-r--r--includes/Autopromote.php69
-rw-r--r--includes/BacklinkCache.php24
-rw-r--r--includes/BagOStuff.php239
-rw-r--r--includes/Block.php66
-rw-r--r--includes/CacheDependency.php17
-rw-r--r--includes/Category.php38
-rw-r--r--includes/CategoryPage.php358
-rw-r--r--includes/Categoryfinder.php50
-rw-r--r--includes/Cdb.php9
-rw-r--r--includes/Cdb_PHP.php9
-rw-r--r--includes/ChangeTags.php14
-rw-r--r--includes/ChangesFeed.php19
-rw-r--r--includes/ChangesList.php114
-rw-r--r--includes/Collation.php307
-rw-r--r--includes/ConfEditor.php26
-rw-r--r--includes/Credits.php134
-rw-r--r--includes/DatabaseFunctions.php412
-rw-r--r--includes/DefaultSettings.php5328
-rw-r--r--includes/Defines.php135
-rw-r--r--includes/DjVuImage.php37
-rw-r--r--includes/EditPage.php468
-rw-r--r--includes/Exception.php109
-rw-r--r--includes/Exif.php16
-rw-r--r--includes/Export.php108
-rw-r--r--includes/ExternalEdit.php9
-rw-r--r--includes/ExternalStore.php13
-rw-r--r--includes/ExternalStoreDB.php35
-rw-r--r--includes/ExternalUser.php37
-rw-r--r--includes/Feed.php52
-rw-r--r--includes/FeedUtils.php16
-rw-r--r--includes/FileDeleteForm.php38
-rw-r--r--includes/FileRevertForm.php4
-rw-r--r--includes/ForkController.php2
-rw-r--r--includes/FormOptions.php40
-rw-r--r--includes/GlobalFunctions.php1271
-rw-r--r--includes/HTMLCacheUpdate.php1
-rw-r--r--includes/HTMLFileCache.php41
-rw-r--r--includes/HTMLForm.php745
-rw-r--r--includes/HistoryBlob.php41
-rw-r--r--includes/HistoryPage.php238
-rw-r--r--includes/Hooks.php60
-rw-r--r--includes/Html.php188
-rw-r--r--includes/HttpFunctions.old.php12
-rw-r--r--includes/HttpFunctions.php519
-rw-r--r--includes/IP.php676
-rw-r--r--includes/ImageFunctions.php105
-rw-r--r--includes/ImageGallery.php115
-rw-r--r--includes/ImagePage.php435
-rw-r--r--includes/Import.php1431
-rw-r--r--includes/Interwiki.php48
-rw-r--r--includes/JSMin.php290
-rw-r--r--includes/Licenses.php14
-rw-r--r--includes/LinkBatch.php87
-rw-r--r--includes/LinkCache.php46
-rw-r--r--includes/Linker.php797
-rw-r--r--includes/LinksUpdate.php164
-rw-r--r--includes/LocalisationCache.php33
-rw-r--r--includes/LogEventsList.php343
-rw-r--r--includes/LogPage.php251
-rw-r--r--includes/MacBinary.php25
-rw-r--r--includes/MagicWord.php37
-rw-r--r--includes/Math.php27
-rw-r--r--includes/MemcachedSessions.php43
-rw-r--r--includes/Message.php360
-rw-r--r--includes/MessageBlobStore.php370
-rw-r--r--includes/MessageCache.php219
-rw-r--r--includes/Metadata.php30
-rw-r--r--includes/MimeMagic.php238
-rw-r--r--includes/Namespace.php56
-rw-r--r--includes/NamespaceCompat.php9
-rw-r--r--includes/ObjectCache.php4
-rw-r--r--includes/OutputHandler.php5
-rw-r--r--includes/OutputPage.php883
-rw-r--r--includes/PageQueryPage.php7
-rw-r--r--includes/Pager.php150
-rw-r--r--includes/PoolCounter.php203
-rw-r--r--includes/Preferences.php1430
-rw-r--r--includes/PrefixSearch.php27
-rw-r--r--includes/Profiler.php43
-rw-r--r--includes/ProfilerSimple.php6
-rw-r--r--includes/ProfilerSimpleText.php24
-rw-r--r--includes/ProfilerSimpleTrace.php30
-rw-r--r--includes/ProfilerStub.php10
-rw-r--r--includes/ProtectionForm.php65
-rw-r--r--includes/ProxyTools.php16
-rw-r--r--includes/QueryPage.php43
-rw-r--r--includes/RawPage.php96
-rw-r--r--includes/RecentChange.php25
-rw-r--r--includes/Revision.php12
-rw-r--r--includes/Sanitizer.php96
-rw-r--r--includes/SeleniumWebSettings.php72
-rw-r--r--includes/Setup.php83
-rw-r--r--includes/SiteConfiguration.php10
-rw-r--r--includes/SiteStats.php116
-rw-r--r--includes/Skin.php878
-rw-r--r--includes/SkinTemplate.php223
-rw-r--r--includes/SpecialPage.php237
-rw-r--r--includes/SquidPurgeClient.php6
-rw-r--r--includes/SquidUpdate.php7
-rw-r--r--includes/Status.php154
-rw-r--r--includes/StreamFile.php14
-rw-r--r--includes/StringUtils.php29
-rw-r--r--includes/StubObject.php65
-rw-r--r--includes/Title.php1904
-rw-r--r--includes/User.php532
-rw-r--r--includes/UserMailer.php249
-rw-r--r--includes/UserRightsProxy.php56
-rw-r--r--includes/WatchedItem.php7
-rw-r--r--includes/WatchlistEditor.php63
-rw-r--r--includes/WebRequest.php525
-rw-r--r--includes/WebResponse.php6
-rw-r--r--includes/WebStart.php64
-rw-r--r--includes/Wiki.php157
-rw-r--r--includes/WikiError.php16
-rw-r--r--includes/WikiMap.php11
-rw-r--r--includes/Xml.php181
-rw-r--r--includes/XmlFunctions.php86
-rw-r--r--includes/ZhClient.php20
-rw-r--r--includes/ZhConversion.php1311
-rw-r--r--includes/api/ApiBase.php275
-rw-r--r--includes/api/ApiBlock.php38
-rw-r--r--includes/api/ApiDelete.php81
-rw-r--r--includes/api/ApiDisabled.php14
-rw-r--r--includes/api/ApiEditPage.php293
-rw-r--r--includes/api/ApiEmailUser.php111
-rw-r--r--includes/api/ApiExpandTemplates.php39
-rw-r--r--includes/api/ApiFeedWatchlist.php104
-rw-r--r--includes/api/ApiFormatBase.php85
-rw-r--r--includes/api/ApiFormatDbg.php22
-rw-r--r--includes/api/ApiFormatDump.php64
-rw-r--r--includes/api/ApiFormatJson.php44
-rw-r--r--includes/api/ApiFormatPhp.php22
-rw-r--r--includes/api/ApiFormatRaw.php39
-rw-r--r--includes/api/ApiFormatTxt.php22
-rw-r--r--includes/api/ApiFormatWddx.php47
-rw-r--r--includes/api/ApiFormatXml.php115
-rw-r--r--includes/api/ApiFormatYaml.php24
-rw-r--r--includes/api/ApiHelp.php115
-rw-r--r--includes/api/ApiImport.php115
-rw-r--r--includes/api/ApiLogin.php24
-rw-r--r--includes/api/ApiLogout.php29
-rw-r--r--includes/api/ApiMain.php422
-rw-r--r--includes/api/ApiMove.php158
-rw-r--r--includes/api/ApiOpenSearch.php74
-rw-r--r--includes/api/ApiPageSet.php239
-rw-r--r--includes/api/ApiParamInfo.php148
-rw-r--r--includes/api/ApiParse.php445
-rw-r--r--includes/api/ApiPatrol.php58
-rw-r--r--includes/api/ApiProtect.php141
-rw-r--r--includes/api/ApiPurge.php57
-rw-r--r--includes/api/ApiQuery.php329
-rw-r--r--includes/api/ApiQueryAllCategories.php91
-rw-r--r--includes/api/ApiQueryAllLinks.php148
-rw-r--r--includes/api/ApiQueryAllUsers.php141
-rw-r--r--includes/api/ApiQueryAllimages.php107
-rw-r--r--includes/api/ApiQueryAllmessages.php81
-rw-r--r--includes/api/ApiQueryAllpages.php158
-rw-r--r--includes/api/ApiQueryBacklinks.php268
-rw-r--r--includes/api/ApiQueryBase.php185
-rw-r--r--includes/api/ApiQueryBlocks.php221
-rw-r--r--includes/api/ApiQueryCategories.php139
-rw-r--r--includes/api/ApiQueryCategoryInfo.php42
-rw-r--r--includes/api/ApiQueryCategoryMembers.php312
-rw-r--r--includes/api/ApiQueryDeletedrevs.php210
-rw-r--r--includes/api/ApiQueryDisabled.php27
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php83
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php117
-rw-r--r--includes/api/ApiQueryExternalLinks.php65
-rw-r--r--includes/api/ApiQueryFilearchive.php264
-rw-r--r--includes/api/ApiQueryIWBacklinks.php217
-rw-r--r--includes/api/ApiQueryIWLinks.php158
-rw-r--r--includes/api/ApiQueryImageInfo.php379
-rw-r--r--includes/api/ApiQueryImages.php92
-rw-r--r--includes/api/ApiQueryInfo.php355
-rw-r--r--includes/api/ApiQueryLangLinks.php89
-rw-r--r--includes/api/ApiQueryLinks.php159
-rw-r--r--includes/api/ApiQueryLogEvents.php221
-rw-r--r--includes/api/ApiQueryPageProps.php150
-rw-r--r--includes/api/ApiQueryProtectedTitles.php123
-rw-r--r--includes/api/ApiQueryRandom.php77
-rw-r--r--includes/api/ApiQueryRecentChanges.php382
-rw-r--r--includes/api/ApiQueryRevisions.php376
-rw-r--r--includes/api/ApiQuerySearch.php166
-rw-r--r--includes/api/ApiQuerySiteinfo.php191
-rw-r--r--includes/api/ApiQueryStashImageInfo.php152
-rw-r--r--includes/api/ApiQueryTags.php141
-rw-r--r--includes/api/ApiQueryUserContributions.php260
-rw-r--r--includes/api/ApiQueryUserInfo.php113
-rw-r--r--includes/api/ApiQueryUsers.php190
-rw-r--r--includes/api/ApiQueryWatchlist.php269
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php135
-rw-r--r--includes/api/ApiResult.php171
-rw-r--r--includes/api/ApiRollback.php146
-rw-r--r--includes/api/ApiRsd.php180
-rw-r--r--includes/api/ApiUnblock.php67
-rw-r--r--includes/api/ApiUndelete.php86
-rw-r--r--includes/api/ApiUpload.php455
-rw-r--r--includes/api/ApiUserrights.php92
-rw-r--r--includes/api/ApiWatch.php55
-rw-r--r--includes/db/Database.php1654
-rw-r--r--includes/db/DatabaseIbm_db2.php1341
-rw-r--r--includes/db/DatabaseMssql.php1684
-rw-r--r--includes/db/DatabaseMysql.php240
-rw-r--r--includes/db/DatabaseOracle.php664
-rw-r--r--includes/db/DatabasePostgres.php922
-rw-r--r--includes/db/DatabaseSqlite.php247
-rw-r--r--includes/db/LBFactory.php76
-rw-r--r--includes/db/LBFactory_Multi.php6
-rw-r--r--includes/db/LBFactory_Single.php57
-rw-r--r--includes/db/LoadBalancer.php123
-rw-r--r--includes/db/LoadMonitor.php22
-rw-r--r--includes/diff/DifferenceEngine.php2002
-rw-r--r--includes/diff/DifferenceInterface.php1024
-rw-r--r--includes/diff/WikiDiff.php1241
-rw-r--r--includes/diff/WikiDiff3.php (renamed from includes/diff/Diff.php)322
-rw-r--r--includes/extauth/Hardcoded.php39
-rw-r--r--includes/extauth/MediaWiki.php56
-rw-r--r--includes/extauth/vB.php47
-rw-r--r--includes/filerepo/ArchivedFile.php18
-rw-r--r--includes/filerepo/FSRepo.php30
-rw-r--r--includes/filerepo/File.php128
-rw-r--r--includes/filerepo/FileRepo.php173
-rw-r--r--includes/filerepo/FileRepoStatus.php6
-rw-r--r--includes/filerepo/ForeignAPIFile.php83
-rw-r--r--includes/filerepo/ForeignAPIRepo.php258
-rw-r--r--includes/filerepo/ForeignDBFile.php8
-rw-r--r--includes/filerepo/ForeignDBRepo.php23
-rw-r--r--includes/filerepo/ForeignDBViaLBRepo.php7
-rw-r--r--includes/filerepo/Image.php16
-rw-r--r--includes/filerepo/LocalFile.php409
-rw-r--r--includes/filerepo/LocalRepo.php20
-rw-r--r--includes/filerepo/NullRepo.php6
-rw-r--r--includes/filerepo/OldLocalFile.php29
-rw-r--r--includes/filerepo/RepoGroup.php31
-rw-r--r--includes/filerepo/UnregisteredLocalFile.php8
-rw-r--r--includes/installer/CliInstaller.php171
-rw-r--r--includes/installer/DatabaseInstaller.php580
-rw-r--r--includes/installer/DatabaseUpdater.php574
-rw-r--r--includes/installer/Installer.i18n.php12267
-rw-r--r--includes/installer/Installer.php1511
-rw-r--r--includes/installer/LocalSettingsGenerator.php349
-rw-r--r--includes/installer/MysqlInstaller.php589
-rw-r--r--includes/installer/MysqlUpdater.php832
-rw-r--r--includes/installer/OracleInstaller.php279
-rw-r--r--includes/installer/OracleUpdater.php114
-rw-r--r--includes/installer/PhpBugTests.php72
-rw-r--r--includes/installer/PostgresInstaller.php601
-rw-r--r--includes/installer/PostgresUpdater.php628
-rw-r--r--includes/installer/SqliteInstaller.php190
-rw-r--r--includes/installer/SqliteUpdater.php84
-rw-r--r--includes/installer/WebInstaller.php1034
-rw-r--r--includes/installer/WebInstallerOutput.php269
-rw-r--r--includes/installer/WebInstallerPage.php1238
-rw-r--r--includes/job/DoubleRedirectJob.php (renamed from includes/DoubleRedirectJob.php)6
-rw-r--r--includes/job/EmaillingJob.php (renamed from includes/EmaillingJob.php)9
-rw-r--r--includes/job/EnotifNotifyJob.php (renamed from includes/EnotifNotifyJob.php)6
-rw-r--r--includes/job/JobQueue.php (renamed from includes/JobQueue.php)52
-rw-r--r--includes/job/RefreshLinksJob.php (renamed from includes/RefreshLinksJob.php)8
-rw-r--r--includes/job/UploadFromUrlJob.php153
-rw-r--r--includes/json/FormatJson.php54
-rw-r--r--includes/json/Services_JSON.php48
-rw-r--r--includes/libs/CSSJanus.php323
-rw-r--r--includes/libs/CSSMin.php214
-rw-r--r--includes/libs/IEContentAnalyzer.php (renamed from includes/IEContentAnalyzer.php)16
-rw-r--r--includes/libs/IEUrlExtension.php247
-rw-r--r--includes/libs/JavaScriptMinifier.php579
-rw-r--r--includes/libs/README4
-rw-r--r--includes/libs/spyc.php (renamed from includes/api/ApiFormatYaml_spyc.php)64
-rw-r--r--includes/media/BMP.php4
-rw-r--r--includes/media/Bitmap.php533
-rw-r--r--includes/media/Bitmap_ClientOnly.php14
-rw-r--r--includes/media/DjVu.php7
-rw-r--r--includes/media/GIF.php43
-rw-r--r--includes/media/GIFMetadataExtractor.php21
-rw-r--r--includes/media/Generic.php51
-rw-r--r--includes/media/MediaTransformOutput.php (renamed from includes/MediaTransformOutput.php)36
-rw-r--r--includes/media/PNG.php82
-rw-r--r--includes/media/PNGMetadataExtractor.php104
-rw-r--r--includes/media/SVG.php121
-rw-r--r--includes/media/SVGMetadataExtractor.php313
-rw-r--r--includes/media/Tiff.php6
-rw-r--r--includes/memcached-client.php277
-rw-r--r--includes/mime.info30
-rw-r--r--includes/mime.types46
-rw-r--r--includes/normal/CleanUpTest.php53
-rw-r--r--includes/normal/RandomTest.php38
-rw-r--r--includes/normal/Utf8Case.php11
-rw-r--r--includes/normal/Utf8CaseGenerate.php53
-rw-r--r--includes/normal/Utf8Test.php53
-rw-r--r--includes/normal/UtfNormal.php162
-rw-r--r--includes/normal/UtfNormalBench.php42
-rw-r--r--includes/normal/UtfNormalData.inc13
-rw-r--r--includes/normal/UtfNormalDataK.inc7
-rw-r--r--includes/normal/UtfNormalDefines.php6
-rw-r--r--includes/normal/UtfNormalGenerate.php60
-rw-r--r--includes/normal/UtfNormalTest.php54
-rw-r--r--includes/normal/UtfNormalTest2.php239
-rw-r--r--includes/normal/UtfNormalUtil.php39
-rw-r--r--includes/parser/CoreLinkFunctions.php5
-rw-r--r--includes/parser/CoreParserFunctions.php64
-rw-r--r--includes/parser/CoreTagHooks.php11
-rw-r--r--includes/parser/DateFormatter.php10
-rw-r--r--includes/parser/LinkHolderArray.php108
-rw-r--r--includes/parser/Parser.php2105
-rw-r--r--includes/parser/ParserCache.php172
-rw-r--r--includes/parser/ParserOptions.php185
-rw-r--r--includes/parser/ParserOutput.php178
-rw-r--r--includes/parser/Parser_DiffTest.php16
-rw-r--r--includes/parser/Parser_LinkHooks.php26
-rw-r--r--includes/parser/Preprocessor.php8
-rw-r--r--includes/parser/Preprocessor_DOM.php90
-rw-r--r--includes/parser/Preprocessor_Hash.php72
-rw-r--r--includes/parser/Tidy.php28
-rw-r--r--includes/proxy_check.php2
-rw-r--r--includes/resourceloader/ResourceLoader.php740
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php176
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php509
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php239
-rw-r--r--includes/resourceloader/ResourceLoaderSiteModule.php63
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php225
-rw-r--r--includes/resourceloader/ResourceLoaderUserModule.php50
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php121
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php171
-rw-r--r--includes/revisiondelete/RevisionDelete.php690
-rw-r--r--includes/revisiondelete/RevisionDeleteAbstracts.php454
-rw-r--r--includes/revisiondelete/RevisionDeleter.php270
-rw-r--r--includes/search/SearchEngine.php761
-rw-r--r--includes/search/SearchIBM_DB2.php53
-rw-r--r--includes/search/SearchMssql.php254
-rw-r--r--includes/search/SearchMySQL.php52
-rw-r--r--includes/search/SearchMySQL4.php34
-rw-r--r--includes/search/SearchOracle.php62
-rw-r--r--includes/search/SearchPostgres.php64
-rw-r--r--includes/search/SearchSqlite.php82
-rw-r--r--includes/search/SearchUpdate.php31
-rw-r--r--includes/specials/SpecialActiveusers.php90
-rw-r--r--includes/specials/SpecialAllmessages.php84
-rw-r--r--includes/specials/SpecialAllpages.php66
-rw-r--r--includes/specials/SpecialAncientpages.php38
-rw-r--r--includes/specials/SpecialBlankpage.php22
-rw-r--r--includes/specials/SpecialBlockip.php195
-rw-r--r--includes/specials/SpecialBlockme.php66
-rw-r--r--includes/specials/SpecialBooksources.php31
-rw-r--r--includes/specials/SpecialBrokenRedirects.php22
-rw-r--r--includes/specials/SpecialCategories.php68
-rw-r--r--includes/specials/SpecialComparePages.php170
-rw-r--r--includes/specials/SpecialConfirmemail.php46
-rw-r--r--includes/specials/SpecialContributions.php231
-rw-r--r--includes/specials/SpecialDeadendpages.php19
-rw-r--r--includes/specials/SpecialDeletedContributions.php32
-rw-r--r--includes/specials/SpecialDisambiguations.php33
-rw-r--r--includes/specials/SpecialDoubleRedirects.php21
-rw-r--r--includes/specials/SpecialEmailuser.php514
-rw-r--r--includes/specials/SpecialExport.php267
-rw-r--r--includes/specials/SpecialFewestrevisions.php17
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php28
-rw-r--r--includes/specials/SpecialFilepath.php94
-rw-r--r--includes/specials/SpecialImport.php89
-rw-r--r--includes/specials/SpecialIpblocklist.php275
-rw-r--r--includes/specials/SpecialLinkSearch.php41
-rw-r--r--includes/specials/SpecialListfiles.php123
-rw-r--r--includes/specials/SpecialListgrouprights.php62
-rw-r--r--includes/specials/SpecialListredirects.php26
-rw-r--r--includes/specials/SpecialListusers.php69
-rw-r--r--includes/specials/SpecialLockdb.php140
-rw-r--r--includes/specials/SpecialLog.php185
-rw-r--r--includes/specials/SpecialLonelypages.php18
-rw-r--r--includes/specials/SpecialLongpages.php17
-rw-r--r--includes/specials/SpecialMIMEsearch.php24
-rw-r--r--includes/specials/SpecialMergeHistory.php90
-rw-r--r--includes/specials/SpecialMostcategories.php25
-rw-r--r--includes/specials/SpecialMostimages.php27
-rw-r--r--includes/specials/SpecialMostlinked.php31
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php27
-rw-r--r--includes/specials/SpecialMostlinkedtemplates.php25
-rw-r--r--includes/specials/SpecialMostrevisions.php25
-rw-r--r--includes/specials/SpecialMovepage.php181
-rw-r--r--includes/specials/SpecialNewimages.php42
-rw-r--r--includes/specials/SpecialNewpages.php100
-rw-r--r--includes/specials/SpecialPopularpages.php20
-rw-r--r--includes/specials/SpecialPreferences.php37
-rw-r--r--includes/specials/SpecialPrefixindex.php54
-rw-r--r--includes/specials/SpecialProtectedpages.php94
-rw-r--r--includes/specials/SpecialProtectedtitles.php71
-rw-r--r--includes/specials/SpecialRandompage.php30
-rw-r--r--includes/specials/SpecialRandomredirect.php24
-rw-r--r--includes/specials/SpecialRecentchanges.php113
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php43
-rw-r--r--includes/specials/SpecialRemoveRestrictions.php60
-rw-r--r--includes/specials/SpecialResetpass.php39
-rw-r--r--includes/specials/SpecialRevisiondelete.php1283
-rw-r--r--includes/specials/SpecialSearch.php185
-rw-r--r--includes/specials/SpecialShortpages.php28
-rw-r--r--includes/specials/SpecialSpecialpages.php166
-rw-r--r--includes/specials/SpecialStatistics.php92
-rw-r--r--includes/specials/SpecialTags.php38
-rw-r--r--includes/specials/SpecialUncategorizedcategories.php22
-rw-r--r--includes/specials/SpecialUncategorizedimages.php19
-rw-r--r--includes/specials/SpecialUncategorizedpages.php18
-rw-r--r--includes/specials/SpecialUncategorizedtemplates.php19
-rw-r--r--includes/specials/SpecialUndelete.php253
-rw-r--r--includes/specials/SpecialUnlockdb.php127
-rw-r--r--includes/specials/SpecialUnusedcategories.php17
-rw-r--r--includes/specials/SpecialUnusedimages.php37
-rw-r--r--includes/specials/SpecialUnusedtemplates.php26
-rw-r--r--includes/specials/SpecialUnwatchedpages.php24
-rw-r--r--includes/specials/SpecialUpload.php422
-rw-r--r--includes/specials/SpecialUploadStash.php394
-rw-r--r--includes/specials/SpecialUserlogin.php256
-rw-r--r--includes/specials/SpecialUserlogout.php72
-rw-r--r--includes/specials/SpecialUserrights.php107
-rw-r--r--includes/specials/SpecialVersion.php353
-rw-r--r--includes/specials/SpecialWantedcategories.php23
-rw-r--r--includes/specials/SpecialWantedfiles.php44
-rw-r--r--includes/specials/SpecialWantedpages.php22
-rw-r--r--includes/specials/SpecialWantedtemplates.php30
-rw-r--r--includes/specials/SpecialWatchlist.php108
-rw-r--r--includes/specials/SpecialWhatlinkshere.php48
-rw-r--r--includes/specials/SpecialWithoutinterwiki.php23
-rw-r--r--includes/templates/NoLocalSettings.php48
-rw-r--r--includes/templates/PHP4.php6
-rw-r--r--includes/templates/Userlogin.php81
-rw-r--r--includes/upload/UploadBase.php372
-rw-r--r--includes/upload/UploadFromFile.php61
-rw-r--r--includes/upload/UploadFromStash.php17
-rw-r--r--includes/upload/UploadFromUrl.php221
-rw-r--r--includes/upload/UploadStash.php397
-rw-r--r--includes/zhtable/Makefile.py596
-rw-r--r--includes/zhtable/simp2trad.manual7
-rw-r--r--includes/zhtable/simpphrases.manual11
-rw-r--r--includes/zhtable/simpphrases_exclude.manual3
-rw-r--r--includes/zhtable/toCN.manual3
-rw-r--r--includes/zhtable/toHK.manual1
-rw-r--r--includes/zhtable/toSimp.manual20
-rw-r--r--includes/zhtable/toTW.manual4
-rw-r--r--includes/zhtable/toTrad.manual42
-rw-r--r--includes/zhtable/trad2simp.manual3
-rw-r--r--includes/zhtable/tradphrases.manual376
-rw-r--r--includes/zhtable/tradphrases_exclude.manual9
445 files changed, 70793 insertions, 28724 deletions
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php
index 5bd7cfa4..e36787fd 100644
--- a/includes/AjaxDispatcher.php
+++ b/includes/AjaxDispatcher.php
@@ -42,31 +42,27 @@ class AjaxDispatcher {
}
switch( $this->mode ) {
-
- case 'get':
- $this->func_name = isset( $_GET["rs"] ) ? $_GET["rs"] : '';
- if ( ! empty( $_GET["rsargs"] ) ) {
- $this->args = $_GET["rsargs"];
- } else {
- $this->args = array();
- }
- break;
-
- case 'post':
- $this->func_name = isset( $_POST["rs"] ) ? $_POST["rs"] : '';
- if ( ! empty( $_POST["rsargs"] ) ) {
- $this->args = $_POST["rsargs"];
- } else {
- $this->args = array();
- }
- break;
-
- default:
- wfProfileOut( __METHOD__ );
- return;
- # Or we could throw an exception:
- # throw new MWException( __METHOD__ . ' called without any data (mode empty).' );
-
+ case 'get':
+ $this->func_name = isset( $_GET["rs"] ) ? $_GET["rs"] : '';
+ if ( ! empty( $_GET["rsargs"] ) ) {
+ $this->args = $_GET["rsargs"];
+ } else {
+ $this->args = array();
+ }
+ break;
+ case 'post':
+ $this->func_name = isset( $_POST["rs"] ) ? $_POST["rs"] : '';
+ if ( ! empty( $_POST["rsargs"] ) ) {
+ $this->args = $_POST["rsargs"];
+ } else {
+ $this->args = array();
+ }
+ break;
+ default:
+ wfProfileOut( __METHOD__ );
+ return;
+ # Or we could throw an exception:
+ # throw new MWException( __METHOD__ . ' called without any data (mode empty).' );
}
wfProfileOut( __METHOD__ );
@@ -89,8 +85,11 @@ class AjaxDispatcher {
if ( ! in_array( $this->func_name, $wgAjaxExportList ) ) {
wfDebug( __METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n" );
- wfHttpError( 400, 'Bad Request',
- "unknown function " . (string) $this->func_name );
+ wfHttpError(
+ 400,
+ 'Bad Request',
+ "unknown function " . (string) $this->func_name
+ );
} else {
wfDebug( __METHOD__ . ' dispatching ' . $this->func_name . "\n" );
@@ -99,6 +98,7 @@ class AjaxDispatcher {
} else {
$func = $this->func_name;
}
+
try {
$result = call_user_func_array( $func, $this->args );
@@ -109,8 +109,7 @@ class AjaxDispatcher {
wfHttpError( 500, 'Internal Error',
"{$this->func_name} returned no data" );
- }
- else {
+ } else {
if ( is_string( $result ) ) {
$result = new AjaxResponse( $result );
}
@@ -120,7 +119,6 @@ class AjaxDispatcher {
wfDebug( __METHOD__ . ' dispatch complete for ' . $this->func_name . "\n" );
}
-
} catch ( Exception $e ) {
wfDebug( __METHOD__ . ' ERROR while dispatching '
. $this->func_name . "(" . var_export( $this->args, true ) . "): "
@@ -135,7 +133,7 @@ class AjaxDispatcher {
}
}
- wfProfileOut( __METHOD__ );
$wgOut = null;
+ wfProfileOut( __METHOD__ );
}
}
diff --git a/includes/AjaxFunctions.php b/includes/AjaxFunctions.php
index e3180e0a..8e5de31b 100644
--- a/includes/AjaxFunctions.php
+++ b/includes/AjaxFunctions.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Handler functions for Ajax requests
+ *
* @file
* @ingroup Ajax
*/
@@ -28,6 +30,7 @@ function js_unescape( $source, $iconv_to = 'UTF-8' ) {
if ( $charAt == '%' ) {
$pos++;
$charAt = substr ( $source, $pos, 1 );
+
if ( $charAt == 'u' ) {
// we got a unicode character
$pos++;
@@ -48,7 +51,7 @@ function js_unescape( $source, $iconv_to = 'UTF-8' ) {
}
if ( $iconv_to != "UTF-8" ) {
- $decodedStr = iconv( "UTF-8", $iconv_to, $decodedStr );
+ $decodedStr = iconv( "utf-8", $iconv_to, $decodedStr );
}
return $decodedStr;
@@ -62,84 +65,23 @@ function js_unescape( $source, $iconv_to = 'UTF-8' ) {
* @return utf8char
*/
function code2utf( $num ) {
- if ( $num < 128 )
+ if ( $num < 128 ) {
return chr( $num );
- if ( $num < 2048 )
- return chr( ( $num >> 6 ) + 192 ) . chr( ( $num&63 ) + 128 );
- if ( $num < 65536 )
- return chr( ( $num >> 12 ) + 224 ) . chr( ( ( $num >> 6 )&63 ) + 128 ) . chr( ( $num&63 ) + 128 );
- if ( $num < 2097152 )
- return chr( ( $num >> 18 ) + 240 ) . chr( ( ( $num >> 12 )&63 ) + 128 ) . chr( ( ( $num >> 6 )&63 ) + 128 ) . chr( ( $num&63 ) + 128 );
- return '';
-}
-
-/**
- * Called for AJAX watch/unwatch requests.
- * @param $pagename Prefixed title string for page to watch/unwatch
- * @param $watch String 'w' to watch, 'u' to unwatch
- * @return String '<w#>' or '<u#>' on successful watch or unwatch,
- * respectively, followed by an HTML message to display in the alert box; or
- * '<err#>' on error
- */
-function wfAjaxWatch( $pagename = "", $watch = "" ) {
- if ( wfReadOnly() ) {
- // redirect to action=(un)watch, which will display the database lock
- // message
- return '<err#>';
}
- if ( 'w' !== $watch && 'u' !== $watch ) {
- return '<err#>';
+ if ( $num < 2048 ) {
+ return chr( ( $num >> 6 ) + 192 ) . chr( ( $num&63 ) + 128 );
}
- $watch = 'w' === $watch;
- $title = Title::newFromDBkey( $pagename );
- if ( !$title ) {
- // Invalid title
- return '<err#>';
+ if ( $num < 65536 ) {
+ return chr( ( $num >> 12 ) + 224 ) . chr( ( ( $num >> 6 )&63 ) + 128 ) . chr( ( $num&63 ) + 128 );
}
- $article = new Article( $title );
- $watching = $title->userIsWatching();
- if ( $watch ) {
- if ( !$watching ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
- $ok = $article->doWatch();
- $dbw->commit();
- }
- } else {
- if ( $watching ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
- $ok = $article->doUnwatch();
- $dbw->commit();
- }
- }
- // Something stopped the change
- if ( isset( $ok ) && !$ok ) {
- return '<err#>';
- }
- if ( $watch ) {
- return '<w#>' . wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() );
- } else {
- return '<u#>' . wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() );
+ if ( $num < 2097152 ) {
+ return chr( ( $num >> 18 ) + 240 ) . chr( ( ( $num >> 12 )&63 ) + 128 ) . chr( ( ( $num >> 6 )&63 ) + 128 ) . chr( ( $num&63 ) + 128 );
}
-}
-
-/**
- * Called in some places (currently just extensions)
- * to get the thumbnail URL for a given file at a given resolution.
- */
-function wfAjaxGetThumbnailUrl( $file, $width, $height ) {
- $file = wfFindFile( $file );
-
- if ( !$file || !$file->exists() )
- return null;
-
- $url = $file->getThumbnail( $width, $height )->url;
- return $url;
+ return '';
}
/**
@@ -149,8 +91,9 @@ function wfAjaxGetThumbnailUrl( $file, $width, $height ) {
function wfAjaxGetFileUrl( $file ) {
$file = wfFindFile( $file );
- if ( !$file || !$file->exists() )
+ if ( !$file || !$file->exists() ) {
return null;
+ }
$url = $file->getUrl();
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
index f7495666..014798f8 100644
--- a/includes/AjaxResponse.php
+++ b/includes/AjaxResponse.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Response handler for Ajax requests
+ *
* @file
* @ingroup Ajax
*/
@@ -15,7 +17,6 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* @ingroup Ajax
*/
class AjaxResponse {
-
/** Number of seconds to get the response cached by a proxy */
private $mCacheDuration;
@@ -99,19 +100,16 @@ class AjaxResponse {
if ( $this->mLastModified ) {
header ( "Last-Modified: " . $this->mLastModified );
- }
- else {
+ } else {
header ( "Last-Modified: " . gmdate( "D, d M Y H:i:s" ) . " GMT" );
}
if ( $this->mCacheDuration ) {
-
# If squid caches are configured, tell them to cache the response,
# and tell the client to always check with the squid. Otherwise,
# tell the client to use a cached copy, without a way to purge it.
if ( $wgUseSquid ) {
-
# Expect explicite purge of the proxy cache, but require end user agents
# to revalidate against the proxy on each visit.
# Surrogate-Control controls our Squid, Cache-Control downstream caches
@@ -127,7 +125,7 @@ class AjaxResponse {
# Let the client do the caching. Cache is not purged.
header ( "Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->mCacheDuration ) . " GMT" );
- header ( "Cache-Control: s-max-age={$this->mCacheDuration},public,max-age={$this->mCacheDuration}" );
+ header ( "Cache-Control: s-maxage={$this->mCacheDuration},public,max-age={$this->mCacheDuration}" );
}
} else {
@@ -156,10 +154,12 @@ class AjaxResponse {
wfDebug( "$fname: CACHE DISABLED, NO TIMESTAMP\n" );
return;
}
+
if ( !$wgCachePages ) {
wfDebug( "$fname: CACHE DISABLED\n", false );
return;
}
+
if ( $wgUser->getOption( 'nocache' ) ) {
wfDebug( "$fname: USER DISABLED CACHE\n", false );
return;
@@ -177,6 +177,7 @@ class AjaxResponse {
$ismodsince = wfTimestamp( TS_MW, $modsinceTime ? $modsinceTime : 1 );
wfDebug( "$fname: -- client send If-Modified-Since: " . $modsince . "\n", false );
wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", false );
+
if ( ( $ismodsince >= $timestamp ) && $wgUser->validateCache( $ismodsince ) && $ismodsince >= $wgCacheEpoch ) {
ini_set( 'zlib.output_compression', 0 );
$this->setResponseCode( "304 Not Modified" );
@@ -198,7 +199,10 @@ class AjaxResponse {
function loadFromMemcached( $mckey, $touched ) {
global $wgMemc;
- if ( !$touched ) return false;
+
+ if ( !$touched ) {
+ return false;
+ }
$mcvalue = $wgMemc->get( $mckey );
if ( $mcvalue ) {
@@ -206,6 +210,7 @@ class AjaxResponse {
if ( $touched <= $mcvalue['timestamp'] ) {
wfDebug( "Got $mckey from cache\n" );
$this->mText = $mcvalue['value'];
+
return true;
} else {
wfDebug( "$mckey has expired\n" );
diff --git a/includes/Article.php b/includes/Article.php
index 5edfc10d..3e8cfd5e 100644
--- a/includes/Article.php
+++ b/includes/Article.php
@@ -11,6 +11,7 @@
* 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 {
/**@{{
@@ -33,15 +34,15 @@ class Article {
var $mRedirectTarget = null; // !< Title object if set
var $mRedirectUrl = false; // !<
var $mRevIdFetched = 0; // !<
- var $mRevision; // !<
+ var $mRevision; // !< Revision object if set
var $mTimestamp = ''; // !<
- var $mTitle; // !<
+ var $mTitle; // !< Title object
var $mTotalAdjustment = 0; // !<
var $mTouched = '19700101000000'; // !<
var $mUser = -1; // !< Not loaded
- var $mUserText = ''; // !<
- var $mParserOptions; // !<
- var $mParserOutput; // !<
+ var $mUserText = ''; // !< username from Revision if set
+ var $mParserOptions; // !< ParserOptions object
+ var $mParserOutput; // !< ParserCache object if set
/**@}}*/
/**
@@ -50,12 +51,13 @@ class Article {
* @param $oldId Integer revision ID, null to fetch from request, zero for current
*/
public function __construct( Title $title, $oldId = null ) {
+ // FIXME: does the reference play any role here?
$this->mTitle =& $title;
$this->mOldId = $oldId;
}
/**
- * Constructor from an article article
+ * Constructor from an page id
* @param $id The article ID to load
*/
public static function newFromID( $id ) {
@@ -70,7 +72,7 @@ class Article {
* from another page on the wiki.
* @param $from Title object.
*/
- public function setRedirectedFrom( $from ) {
+ public function setRedirectedFrom( Title $from ) {
$this->mRedirectedFrom = $from;
}
@@ -82,20 +84,29 @@ class Article {
* @return mixed Title object, or null if this page is not a redirect
*/
public function getRedirectTarget() {
- if ( !$this->mTitle || !$this->mTitle->isRedirect() )
+ if ( !$this->mTitle->isRedirect() ) {
return null;
- if ( !is_null( $this->mRedirectTarget ) )
+ }
+
+ if ( $this->mRedirectTarget !== null ) {
return $this->mRedirectTarget;
+ }
+
# Query the redirect table
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'redirect',
- array( 'rd_namespace', 'rd_title' ),
+ array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
array( 'rd_from' => $this->getID() ),
__METHOD__
);
- if ( $row ) {
- return $this->mRedirectTarget = Title::makeTitle( $row->rd_namespace, $row->rd_title );
+
+ // rd_fragment and rd_interwiki were added later, populate them if empty
+ if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
+ return $this->mRedirectTarget = Title::makeTitle(
+ $row->rd_namespace, $row->rd_title,
+ $row->rd_fragment, $row->rd_interwiki );
}
+
# This page doesn't have an entry in the redirect table
return $this->mRedirectTarget = $this->insertRedirect();
}
@@ -104,43 +115,66 @@ class Article {
* Insert an entry for this page into the redirect table.
*
* Don't call this function directly unless you know what you're doing.
- * @return Title object
+ * @return Title object or null if not a redirect
*/
public function insertRedirect() {
- $retval = Title::newFromRedirect( $this->getContent() );
+ // recurse through to only get the final target
+ $retval = Title::newFromRedirectRecurse( $this->getContent() );
if ( !$retval ) {
return null;
}
+ $this->insertRedirectEntry( $retval );
+ return $retval;
+ }
+
+ /**
+ * Insert or update the redirect table entry for this page to indicate
+ * it redirects to $rt .
+ * @param $rt Title redirect target
+ */
+ public function insertRedirectEntry( $rt ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->replace( 'redirect', array( 'rd_from' ),
array(
'rd_from' => $this->getID(),
- 'rd_namespace' => $retval->getNamespace(),
- 'rd_title' => $retval->getDBkey()
+ 'rd_namespace' => $rt->getNamespace(),
+ 'rd_title' => $rt->getDBkey(),
+ 'rd_fragment' => $rt->getFragment(),
+ 'rd_interwiki' => $rt->getInterwiki(),
),
__METHOD__
);
- return $retval;
}
/**
- * Get the Title object this page redirects to
+ * Get the Title object or URL this page redirects to
*
* @return mixed false, Title of in-wiki target, or string with URL
*/
public function followRedirect() {
- $text = $this->getContent();
- return $this->followRedirectText( $text );
+ return $this->getRedirectURL( $this->getRedirectTarget() );
}
/**
* Get the Title object this text redirects to
*
+ * @param $text string article content containing redirect info
* @return mixed false, Title of in-wiki target, or string with URL
+ * @deprecated
*/
public function followRedirectText( $text ) {
- $rt = Title::newFromRedirectRecurse( $text ); // recurse through to only get the final target
- # process if title object is valid and not special:userlogout
+ // recurse through to only get the final target
+ return $this->getRedirectURL( Title::newFromRedirectRecurse( $text ) );
+ }
+
+ /**
+ * Get the Title object or URL to use for a redirect. We use Title
+ * objects for same-wiki, non-special redirects and URLs for everything
+ * else.
+ * @param $rt Title Redirect target
+ * @return mixed false, Title object of local target, or string with URL
+ */
+ public function getRedirectURL( $rt ) {
if ( $rt ) {
if ( $rt->getInterwiki() != '' ) {
if ( $rt->isLocal() ) {
@@ -164,15 +198,18 @@ class Article {
return $rt->getFullURL();
}
}
+
return $rt;
}
}
+
// No or invalid redirect
return false;
}
/**
- * get the title object of the article
+ * Get the title object of the article
+ * @return Title object of this page
*/
public function getTitle() {
return $this->mTitle;
@@ -180,6 +217,7 @@ class Article {
/**
* Clear the object
+ * FIXME: shouldn't this be public?
* @private
*/
public function clear() {
@@ -204,31 +242,38 @@ class Article {
/**
* Note that getContent/loadContent do not follow redirects anymore.
* If you need to fetch redirectable content easily, try
- * the shortcut in Article::followContent()
+ * the shortcut in Article::followRedirect()
+ *
+ * This function has side effects! Do not use this function if you
+ * only want the real revision text if any.
*
* @return Return the text of this revision
*/
public function getContent() {
- global $wgUser, $wgContLang, $wgOut, $wgMessageCache;
+ global $wgUser, $wgContLang, $wgMessageCache;
+
wfProfileIn( __METHOD__ );
+
if ( $this->getID() === 0 ) {
# If this is a MediaWiki:x message, then load the messages
# and return the message value for x.
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
# If this is a system message, get the default text.
list( $message, $lang ) = $wgMessageCache->figureMessage( $wgContLang->lcfirst( $this->mTitle->getText() ) );
- $wgMessageCache->loadAllMessages( $lang );
$text = wfMsgGetKey( $message, false, $lang, false );
+
if ( wfEmptyMsg( $message, $text ) )
$text = '';
} else {
$text = wfMsgExt( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', 'parsemag' );
}
wfProfileOut( __METHOD__ );
+
return $text;
} else {
$this->loadContent();
wfProfileOut( __METHOD__ );
+
return $this->mContent;
}
}
@@ -243,8 +288,10 @@ class Article {
if ( $this->mContentLoaded && $this->mOldId == 0 ) {
return $this->mContent;
}
+
$rev = Revision::newFromTitle( $this->mTitle );
$text = $rev ? $rev->getRawText() : false;
+
return $text;
}
@@ -274,16 +321,25 @@ class Article {
* @return mixed string on success, false on failure
*/
public function getUndoText( Revision $undo, Revision $undoafter = null ) {
+ $currentRev = Revision::newFromTitle( $this->mTitle );
+ if ( !$currentRev ) {
+ return false; // no page
+ }
$undo_text = $undo->getText();
$undoafter_text = $undoafter->getText();
- $cur_text = $this->getContent();
+ $cur_text = $currentRev->getText();
+
if ( $cur_text == $undo_text ) {
# No use doing a merge if it's just a straight revert.
return $undoafter_text;
}
+
$undone_text = '';
- if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) )
+
+ if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
return false;
+ }
+
return $undone_text;
}
@@ -295,6 +351,7 @@ class Article {
if ( is_null( $this->mOldId ) ) {
$this->mOldId = $this->getOldIDFromRequest();
}
+
return $this->mOldId;
}
@@ -305,13 +362,16 @@ class Article {
*/
public function getOldIDFromRequest() {
global $wgRequest;
+
$this->mRedirectUrl = false;
+
$oldid = $wgRequest->getVal( 'oldid' );
+
if ( isset( $oldid ) ) {
$oldid = intval( $oldid );
if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
$nextid = $this->mTitle->getNextRevisionID( $oldid );
- if ( $nextid ) {
+ if ( $nextid ) {
$oldid = $nextid;
} else {
$this->mRedirectUrl = $this->mTitle->getFullURL( 'redirect=no' );
@@ -323,9 +383,11 @@ class Article {
}
}
}
+
if ( !$oldid ) {
$oldid = 0;
}
+
return $oldid;
}
@@ -333,22 +395,24 @@ class Article {
* Load the revision (including text) into this object
*/
function loadContent() {
- if ( $this->mContentLoaded ) return;
+ if ( $this->mContentLoaded ) {
+ return;
+ }
+
wfProfileIn( __METHOD__ );
- # Query variables :P
+
$oldid = $this->getOldID();
- # Pre-fill content with error message so that if something
- # fails we'll have something telling us what we intended.
$this->mOldId = $oldid;
$this->fetchContent( $oldid );
+
wfProfileOut( __METHOD__ );
}
-
/**
* Fetch a page record with the given conditions
* @param $dbr Database object
* @param $conditions Array
+ * @return mixed Database result resource, or false on failure
*/
protected function pageData( $dbr, $conditions ) {
$fields = array(
@@ -364,20 +428,23 @@ class Article {
'page_latest',
'page_len',
);
+
wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
- $row = $dbr->selectRow(
- 'page',
- $fields,
- $conditions,
- __METHOD__
- );
+
+ $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__ );
+
wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
- return $row ;
+
+ return $row;
}
/**
+ * Fetch a page record matching the Title object's namespace and title
+ * using a sanitized title string
+ *
* @param $dbr Database object
* @param $title Title object
+ * @return mixed Database result resource, or false on failure
*/
public function pageDataFromTitle( $dbr, $title ) {
return $this->pageData( $dbr, array(
@@ -386,6 +453,8 @@ class Article {
}
/**
+ * Fetch a page record matching the requested ID
+ *
* @param $dbr Database
* @param $id Integer
*/
@@ -406,8 +475,9 @@ class Article {
}
$lc = LinkCache::singleton();
+
if ( $data ) {
- $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect );
+ $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect, $data->page_latest );
$this->mTitle->mArticleID = intval( $data->page_id );
@@ -419,20 +489,19 @@ class Article {
$this->mIsRedirect = intval( $data->page_is_redirect );
$this->mLatest = intval( $data->page_latest );
} else {
- if ( is_object( $this->mTitle ) ) {
- $lc->addBadLinkObj( $this->mTitle );
- }
+ $lc->addBadLinkObj( $this->mTitle );
$this->mTitle->mArticleID = 0;
}
- $this->mDataLoaded = true;
+ $this->mDataLoaded = true;
}
/**
* Get text of an article from database
* Does *NOT* follow redirects.
+ *
* @param $oldid Int: 0 for whatever the latest revision is
- * @return string
+ * @return mixed string containing article contents, or false if null
*/
function fetchContent( $oldid = 0 ) {
if ( $this->mContentLoaded ) {
@@ -449,28 +518,33 @@ class Article {
if ( $oldid ) {
$revision = Revision::newFromId( $oldid );
- if ( is_null( $revision ) ) {
+ if ( $revision === null ) {
wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
return false;
}
+
$data = $this->pageDataFromId( $dbr, $revision->getPage() );
+
if ( !$data ) {
wfDebug( __METHOD__ . " failed to get page data linked to revision id $oldid\n" );
return false;
}
+
$this->mTitle = Title::makeTitle( $data->page_namespace, $data->page_title );
$this->loadPageData( $data );
} else {
if ( !$this->mDataLoaded ) {
$data = $this->pageDataFromTitle( $dbr, $this->mTitle );
+
if ( !$data ) {
wfDebug( __METHOD__ . " failed to find page data for title " . $this->mTitle->getPrefixedText() . "\n" );
return false;
}
+
$this->loadPageData( $data );
}
$revision = Revision::newFromId( $this->mLatest );
- if ( is_null( $revision ) ) {
+ if ( $revision === null ) {
wfDebug( __METHOD__ . " failed to retrieve current page, rev_id {$this->mLatest}\n" );
return false;
}
@@ -489,7 +563,7 @@ class Article {
$this->mContentLoaded = true;
$this->mRevision =& $revision;
- wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ) ;
+ wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
return $this->mContent;
}
@@ -498,23 +572,13 @@ class Article {
* Read/write accessor to select FOR UPDATE
*
* @param $x Mixed: FIXME
+ * @return mixed value of $x, or value stored in Article::mForUpdate
*/
public function forUpdate( $x = null ) {
return wfSetVar( $this->mForUpdate, $x );
}
/**
- * Get the database which should be used for reads
- *
- * @return Database
- * @deprecated - just call wfGetDB( DB_MASTER ) instead
- */
- function getDB() {
- wfDeprecated( __METHOD__ );
- return wfGetDB( DB_MASTER );
- }
-
- /**
* Get options for all SELECT statements
*
* @param $options Array: an optional options array which'll be appended to
@@ -529,6 +593,7 @@ class Article {
$options = 'FOR UPDATE';
}
}
+
return $options;
}
@@ -536,11 +601,7 @@ class Article {
* @return int Page ID
*/
public function getID() {
- if ( $this->mTitle ) {
- return $this->mTitle->getArticleID();
- } else {
- return 0;
- }
+ return $this->mTitle->getArticleID();
}
/**
@@ -568,6 +629,7 @@ class Article {
public function getCount() {
if ( -1 == $this->mCounter ) {
$id = $this->getID();
+
if ( $id == 0 ) {
$this->mCounter = 0;
} else {
@@ -580,11 +642,12 @@ class Article {
);
}
}
+
return $this->mCounter;
}
/**
- * Determine whether a page would be suitable for being counted as an
+ * Determine whether a page would be suitable for being counted as an
* article in the site_stats table based on the title & its content
*
* @param $text String: text to analyze
@@ -594,13 +657,14 @@ class Article {
global $wgUseCommaCount;
$token = $wgUseCommaCount ? ',' : '[[';
+
return $this->mTitle->isContentPage() && !$this->isRedirect( $text ) && in_string( $token, $text );
}
/**
* Tests if the article text represents a redirect
*
- * @param $text String: FIXME
+ * @param $text mixed string containing article contents, or boolean
* @return bool
*/
public function isRedirect( $text = false ) {
@@ -608,12 +672,14 @@ class Article {
if ( $this->mDataLoaded ) {
return $this->mIsRedirect;
}
+
// Apparently loadPageData was never called
$this->loadContent();
$titleObj = Title::newFromRedirectRecurse( $this->fetchContent() );
} else {
$titleObj = Title::newFromRedirect( $text );
}
+
return $titleObj !== null;
}
@@ -627,6 +693,7 @@ class Article {
if ( $this->getOldID() == 0 ) {
return true;
}
+
return $this->exists() && isset( $this->mRevision ) && $this->mRevision->isCurrent();
}
@@ -635,12 +702,15 @@ class Article {
* This isn't necessary for all uses, so it's only done if needed.
*/
protected function loadLastEdit() {
- if ( -1 != $this->mUser )
+ if ( -1 != $this->mUser ) {
return;
+ }
# New or non-existent articles have no user information
$id = $this->getID();
- if ( 0 == $id ) return;
+ if ( 0 == $id ) {
+ return;
+ }
$this->mLastRevision = Revision::loadFromPageId( wfGetDB( DB_MASTER ), $id );
if ( !is_null( $this->mLastRevision ) ) {
@@ -653,46 +723,71 @@ class Article {
}
}
+ /**
+ * @return string GMT timestamp of last article revision
+ **/
+
public function getTimestamp() {
// Check if the field has been filled by ParserCache::get()
if ( !$this->mTimestamp ) {
$this->loadLastEdit();
}
+
return wfTimestamp( TS_MW, $this->mTimestamp );
}
+ /**
+ * @return int user ID for the user that made the last article revision
+ */
public function getUser() {
$this->loadLastEdit();
return $this->mUser;
}
+ /**
+ * @return string username of the user that made the last article revision
+ */
public function getUserText() {
$this->loadLastEdit();
return $this->mUserText;
}
+ /**
+ * @return string Comment stored for the last article revision
+ */
public function getComment() {
$this->loadLastEdit();
return $this->mComment;
}
+ /**
+ * Returns true if last revision was marked as "minor edit"
+ *
+ * @return boolean Minor edit indicator for the last article revision.
+ */
public function getMinorEdit() {
$this->loadLastEdit();
return $this->mMinorEdit;
}
- /* Use this to fetch the rev ID used on page views */
+ /**
+ * Use this to fetch the rev ID used on page views
+ *
+ * @return int revision ID of last article revision
+ */
public function getRevIdFetched() {
$this->loadLastEdit();
return $this->mRevIdFetched;
}
/**
+ * FIXME: this does what?
* @param $limit Integer: default 0.
* @param $offset Integer: default 0.
+ * @return UserArrayFromResult object with User objects of article contributors for requested range
*/
public function getContributors( $limit = 0, $offset = 0 ) {
- # XXX: this is expensive; cache this info somewhere.
+ # FIXME: this is expensive; cache this info somewhere.
$dbr = wfGetDB( DB_SLAVE );
$revTable = $dbr->tableName( 'revision' );
@@ -701,6 +796,7 @@ class Article {
$pageId = $this->getId();
$user = $this->getUser();
+
if ( $user ) {
$excludeCond = "AND rev_user != $user";
} else {
@@ -718,8 +814,9 @@ class Article {
GROUP BY rev_user, rev_user_text
ORDER BY timestamp DESC";
- if ( $limit > 0 )
+ if ( $limit > 0 ) {
$sql = $dbr->limitResult( $sql, $limit, $offset );
+ }
$sql .= ' ' . $this->getSelectOptions();
$res = $dbr->query( $sql, __METHOD__ );
@@ -732,9 +829,8 @@ class Article {
* page of the given title.
*/
public function view() {
- global $wgUser, $wgOut, $wgRequest, $wgContLang;
- global $wgEnableParserCache, $wgStylePath, $wgParser;
- global $wgUseTrackbacks, $wgUseFileCache;
+ global $wgUser, $wgOut, $wgRequest, $wgParser;
+ global $wgUseFileCache, $wgUseETag;
wfProfileIn( __METHOD__ );
@@ -742,22 +838,26 @@ class Article {
$oldid = $this->getOldID();
$parserCache = ParserCache::singleton();
- $parserOptions = clone $this->getParserOptions();
+ $parserOptions = $this->getParserOptions();
# Render printable version, use printable version cache
if ( $wgOut->isPrintable() ) {
$parserOptions->setIsPrintable( true );
+ $parserOptions->setEditSection( false );
+ } else if ( $wgUseETag && !$this->mTitle->quickUserCan( 'edit' ) ) {
+ $parserOptions->setEditSection( false );
}
# Try client and file cache
if ( $oldid === 0 && $this->checkTouched() ) {
- global $wgUseETag;
if ( $wgUseETag ) {
$wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) );
}
- # Is is client cached?
+
+ # Is it client cached?
if ( $wgOut->checkLastModified( $this->getTouched() ) ) {
wfDebug( __METHOD__ . ": done 304\n" );
wfProfileOut( __METHOD__ );
+
return;
# Try file cache
} else if ( $wgUseFileCache && $this->tryFileCache() ) {
@@ -766,17 +866,17 @@ class Article {
$wgOut->disable();
$this->viewUpdates();
wfProfileOut( __METHOD__ );
+
return;
}
}
- $sk = $wgUser->getSkin();
-
# getOldID may want us to redirect somewhere else
if ( $this->mRedirectUrl ) {
$wgOut->redirect( $this->mRedirectUrl );
wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
wfProfileOut( __METHOD__ );
+
return;
}
@@ -785,20 +885,25 @@ class Article {
$wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
# If we got diff in the query, we want to see a diff page instead of the article.
- if ( !is_null( $wgRequest->getVal( 'diff' ) ) ) {
+ if ( $wgRequest->getCheck( 'diff' ) ) {
wfDebug( __METHOD__ . ": showing diff page\n" );
$this->showDiffPage();
wfProfileOut( __METHOD__ );
+
return;
}
# Allow frames by default
$wgOut->allowClickjacking();
+ if ( !$wgUseETag && !$this->mTitle->quickUserCan( 'edit' ) ) {
+ $parserOptions->setEditSection( false );
+ }
+
# Should the parser cache be used?
$useParserCache = $this->useParserCache( $oldid );
wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
- if ( $wgUser->getOption( 'stubthreshold' ) ) {
+ if ( $wgUser->getStubThreshold() ) {
wfIncrStats( 'pcache_miss_stub' );
}
@@ -810,16 +915,17 @@ class Article {
$pass = 0;
$outputDone = false;
$this->mParserOutput = false;
+
while ( !$outputDone && ++$pass ) {
switch( $pass ) {
case 1:
wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
break;
-
case 2:
# Try the parser cache
if ( $useParserCache ) {
$this->mParserOutput = $parserCache->get( $this, $parserOptions );
+
if ( $this->mParserOutput !== false ) {
wfDebug( __METHOD__ . ": showing parser cache contents\n" );
$wgOut->addParserOutput( $this->mParserOutput );
@@ -830,7 +936,6 @@ class Article {
}
}
break;
-
case 3:
$text = $this->getContent();
if ( $text === false || $this->getID() == 0 ) {
@@ -853,22 +958,22 @@ class Article {
# Are we looking at an old revision
if ( $oldid && !is_null( $this->mRevision ) ) {
$this->setOldSubtitle( $oldid );
+
if ( !$this->showDeletedRevisionHeader() ) {
wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
wfProfileOut( __METHOD__ );
return;
}
+
# If this "old" version is the current, then try the parser cache...
if ( $oldid === $this->getLatest() && $this->useParserCache( false ) ) {
$this->mParserOutput = $parserCache->get( $this, $parserOptions );
if ( $this->mParserOutput ) {
- wfDebug( __METHOD__ . ": showing parser cache for current rev permalink\n" );
+ wfDebug( __METHOD__ . ": showing parser cache for current rev permalink\n" );
$wgOut->addParserOutput( $this->mParserOutput );
$wgOut->setRevisionId( $this->mLatest );
- $this->showViewFooter();
- $this->viewUpdates();
- wfProfileOut( __METHOD__ );
- return;
+ $outputDone = true;
+ break;
}
}
}
@@ -882,36 +987,35 @@ class Article {
wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
$this->showCssOrJsPage();
$outputDone = true;
- } else if ( $rt = Title::newFromRedirectArray( $text ) ) {
- wfDebug( __METHOD__ . ": showing redirect=no page\n" );
- # Viewing a redirect page (e.g. with parameter redirect=no)
- # Don't append the subtitle if this was an old revision
- $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
- # Parse just to get categories, displaytitle, etc.
- $this->mParserOutput = $wgParser->parse( $text, $this->mTitle, $parserOptions );
- $wgOut->addParserOutputNoText( $this->mParserOutput );
- $outputDone = true;
+ } else {
+ $rt = Title::newFromRedirectArray( $text );
+ if ( $rt ) {
+ wfDebug( __METHOD__ . ": showing redirect=no page\n" );
+ # Viewing a redirect page (e.g. with parameter redirect=no)
+ # Don't append the subtitle if this was an old revision
+ $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
+ # Parse just to get categories, displaytitle, etc.
+ $this->mParserOutput = $wgParser->parse( $text, $this->mTitle, $parserOptions );
+ $wgOut->addParserOutputNoText( $this->mParserOutput );
+ $outputDone = true;
+ }
}
break;
-
case 4:
# Run the parse, protected by a pool counter
wfDebug( __METHOD__ . ": doing uncached parse\n" );
- $key = $parserCache->getKey( $this, $parserOptions );
- $poolCounter = PoolCounter::factory( 'Article::view', $key );
- $dirtyCallback = $useParserCache ? array( $this, 'tryDirtyCache' ) : false;
- $status = $poolCounter->executeProtected( array( $this, 'doViewParse' ), $dirtyCallback );
- if ( !$status->isOK() ) {
+ $key = $parserCache->getKey( $this, $parserOptions );
+ $poolArticleView = new PoolWorkArticleView( $this, $key, $useParserCache, $parserOptions );
+
+ if ( !$poolArticleView->execute() ) {
# Connection or timeout error
- $this->showPoolError( $status );
wfProfileOut( __METHOD__ );
return;
} else {
$outputDone = true;
}
break;
-
# Should be unreachable, but just in case...
default:
break 2;
@@ -921,6 +1025,7 @@ class Article {
# Adjust the title if it was set by displaytitle, -{T|}- or language conversion
if ( $this->mParserOutput ) {
$titleText = $this->mParserOutput->getTitleText();
+
if ( strval( $titleText ) !== '' ) {
$wgOut->setPageTitle( $titleText );
}
@@ -929,6 +1034,7 @@ class Article {
# For the main page, overwrite the <title> element with the con-
# tents of 'pagetitle-view-mainpage' instead of the default (if
# that's not empty).
+ # This message always exists because it is in the i18n files
if ( $this->mTitle->equals( Title::newMainPage() )
&& ( $m = wfMsgForContent( 'pagetitle-view-mainpage' ) ) !== '' )
{
@@ -951,7 +1057,7 @@ class Article {
* Article::view() only, other callers should use the DifferenceEngine class.
*/
public function showDiffPage() {
- global $wgOut, $wgRequest, $wgUser;
+ global $wgRequest, $wgUser;
$diff = $wgRequest->getVal( 'diff' );
$rcid = $wgRequest->getVal( 'rcid' );
@@ -980,9 +1086,11 @@ class Article {
* This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these
* page views.
*/
- public function showCssOrJsPage() {
+ protected function showCssOrJsPage() {
global $wgOut;
- $wgOut->addHTML( wfMsgExt( 'clearyourcache', 'parse' ) );
+
+ $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache'>\n$1\n</div>", 'clearyourcache' );
+
// Give hooks a chance to customise the output
if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) {
// Wrap the whole lot in a <pre> and don't parse
@@ -995,52 +1103,48 @@ class Article {
}
/**
- * Get the robot policy to be used for the current action=view request.
- * @return String the policy that should be set
- * @deprecated use getRobotPolicy() instead, which returns an associative
- * array
- */
- public function getRobotPolicyForView() {
- wfDeprecated( __FUNC__ );
- $policy = $this->getRobotPolicy( 'view' );
- return $policy['index'] . ',' . $policy['follow'];
- }
-
- /**
* Get the robot policy to be used for the current view
* @param $action String the action= GET parameter
* @return Array the policy that should be set
* TODO: actions other than 'view'
*/
public function getRobotPolicy( $action ) {
-
global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies;
global $wgDefaultRobotPolicy, $wgRequest;
$ns = $this->mTitle->getNamespace();
+
if ( $ns == NS_USER || $ns == NS_USER_TALK ) {
# Don't index user and user talk pages for blocked users (bug 11443)
if ( !$this->mTitle->isSubpage() ) {
$block = new Block();
if ( $block->load( $this->mTitle->getText() ) ) {
- return array( 'index' => 'noindex',
- 'follow' => 'nofollow' );
+ return array(
+ 'index' => 'noindex',
+ 'follow' => 'nofollow'
+ );
}
}
}
if ( $this->getID() === 0 || $this->getOldID() ) {
# Non-articles (special pages etc), and old revisions
- return array( 'index' => 'noindex',
- 'follow' => 'nofollow' );
+ return array(
+ 'index' => 'noindex',
+ 'follow' => 'nofollow'
+ );
} elseif ( $wgOut->isPrintable() ) {
# Discourage indexing of printable versions, but encourage following
- return array( 'index' => 'noindex',
- 'follow' => 'follow' );
+ return array(
+ 'index' => 'noindex',
+ 'follow' => 'follow'
+ );
} elseif ( $wgRequest->getInt( 'curid' ) ) {
# For ?curid=x urls, disallow indexing
- return array( 'index' => 'noindex',
- 'follow' => 'follow' );
+ return array(
+ 'index' => 'noindex',
+ 'follow' => 'follow'
+ );
}
# Otherwise, construct the policy based on the various config variables.
@@ -1048,24 +1152,29 @@ class Article {
if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
# Honour customised robot policies for this namespace
- $policy = array_merge( $policy,
- self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] ) );
+ $policy = array_merge(
+ $policy,
+ self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
+ );
}
if ( $this->mTitle->canUseNoindex() && is_object( $this->mParserOutput ) && $this->mParserOutput->getIndexPolicy() ) {
# __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
# a final sanity check that we have really got the parser output.
- $policy = array_merge( $policy,
- array( 'index' => $this->mParserOutput->getIndexPolicy() ) );
+ $policy = array_merge(
+ $policy,
+ array( 'index' => $this->mParserOutput->getIndexPolicy() )
+ );
}
if ( isset( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) ) {
# (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
- $policy = array_merge( $policy,
- self::formatRobotPolicy( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) );
+ $policy = array_merge(
+ $policy,
+ self::formatRobotPolicy( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] )
+ );
}
return $policy;
-
}
/**
@@ -1093,6 +1202,7 @@ class Article {
$arr['follow'] = $var;
}
}
+
return $arr;
}
@@ -1100,12 +1210,15 @@ class Article {
* If this request is a redirect view, send "redirected from" subtitle to
* $wgOut. Returns true if the header was needed, false if this is not a
* redirect view. Handles both local and remote redirects.
+ *
+ * @return boolean
*/
public function showRedirectedFromHeader() {
global $wgOut, $wgUser, $wgRequest, $wgRedirectSources;
$rdfrom = $wgRequest->getVal( 'rdfrom' );
$sk = $wgUser->getSkin();
+
if ( isset( $this->mRedirectedFrom ) ) {
// This is an internally redirected page view.
// We'll need a backlink to the source page for navigation.
@@ -1117,6 +1230,7 @@ class Article {
array( 'redirect' => 'no' ),
array( 'known', 'noclasses' )
);
+
$s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
$wgOut->setSubtitle( $s );
@@ -1130,6 +1244,7 @@ class Article {
$wgOut->addLink( array( 'rel' => 'canonical',
'href' => $this->mTitle->getLocalURL() )
);
+
return true;
}
} elseif ( $rdfrom ) {
@@ -1139,9 +1254,11 @@ class Article {
$redir = $sk->makeExternalLink( $rdfrom, $rdfrom );
$s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
$wgOut->setSubtitle( $s );
+
return true;
}
}
+
return false;
}
@@ -1151,10 +1268,11 @@ class Article {
*/
public function showNamespaceHeader() {
global $wgOut;
+
if ( $this->mTitle->isTalkPage() ) {
$msg = wfMsgNoTrans( 'talkpageheader' );
if ( $msg !== '-' && !wfEmptyMsg( 'talkpageheader', $msg ) ) {
- $wgOut->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1</div>", array( 'talkpageheader' ) );
+ $wgOut->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1\n</div>", array( 'talkpageheader' ) );
}
}
}
@@ -1163,7 +1281,8 @@ class Article {
* Show the footer section of an ordinary page view
*/
public function showViewFooter() {
- global $wgOut, $wgUseTrackbacks, $wgRequest;
+ global $wgOut, $wgUseTrackbacks;
+
# check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
if ( $this->mTitle->getNamespace() == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) {
$wgOut->addWikiMsg( 'anontalkpagetext' );
@@ -1186,13 +1305,16 @@ class Article {
*/
public function showPatrolFooter() {
global $wgOut, $wgRequest, $wgUser;
+
$rcid = $wgRequest->getVal( 'rcid' );
- if ( !$rcid || !$this->mTitle->exists() || !$this->mTitle->quickUserCan( 'patrol' ) ) {
+ if ( !$rcid || !$this->mTitle->quickUserCan( 'patrol' ) ) {
return;
}
$sk = $wgUser->getSkin();
+ $token = $wgUser->editToken( $rcid );
+ $wgOut->preventClickjacking();
$wgOut->addHTML(
"<div class='patrollink'>" .
@@ -1204,7 +1326,8 @@ class Article {
array(),
array(
'action' => 'markpatrolled',
- 'rcid' => $rcid
+ 'rcid' => $rcid,
+ 'token' => $token,
),
array( 'known', 'noclasses' )
)
@@ -1226,8 +1349,9 @@ class Article {
$rootPart = $parts[0];
$user = User::newFromName( $rootPart, false /* allow IP users*/ );
$ip = User::isIP( $rootPart );
+
if ( !$user->isLoggedIn() && !$ip ) { # User does not exist
- $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1</div>",
+ $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
array( 'userpage-userdoesnotexist-view', $rootPart ) );
} else if ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
LogEventsList::showLogExtract(
@@ -1246,7 +1370,9 @@ class Article {
);
}
}
+
wfRunHooks( 'ShowMissingArticle', array( $this ) );
+
# Show delete and move logs
LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle->getPrefixedText(), '',
array( 'lim' => 10,
@@ -1269,35 +1395,42 @@ class Article {
$editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
$errors = array_merge( $createErrors, $editErrors );
- if ( !count( $errors ) )
+ if ( !count( $errors ) ) {
$text = wfMsgNoTrans( 'noarticletext' );
- else
+ } else {
$text = wfMsgNoTrans( 'noarticletext-nopermission' );
+ }
}
$text = "<div class='noarticletext'>\n$text\n</div>";
+
if ( !$this->hasViewableContent() ) {
// If there's no backing content, send a 404 Not Found
// for better machine handling of broken links.
- $wgRequest->response()->header( "HTTP/1.x 404 Not Found" );
+ $wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
}
+
$wgOut->addWikiText( $text );
}
/**
* If the revision requested for view is deleted, check permissions.
* Send either an error message or a warning header to $wgOut.
- * Returns true if the view is allowed, false if not.
+ *
+ * @return boolean true if the view is allowed, false if not.
*/
public function showDeletedRevisionHeader() {
global $wgOut, $wgRequest;
+
if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
// Not deleted
return true;
}
+
// If the user is not allowed to see it...
if ( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
'rev-deleted-text-permission' );
+
return false;
// If the user needs to confirm that they want to see it...
} else if ( $wgRequest->getInt( 'unhide' ) != 1 ) {
@@ -1306,26 +1439,30 @@ class Article {
$link = $this->mTitle->getFullUrl( "oldid={$oldid}&unhide=1" );
$msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
array( $msg, $link ) );
+
return false;
// We are allowed to see...
} else {
$msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
'rev-suppressed-text-view' : 'rev-deleted-text-view';
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", $msg );
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
+
return true;
}
}
- /*
- * Should the parser cache be used?
- */
+ /**
+ * Should the parser cache be used?
+ *
+ * @return boolean
+ */
public function useParserCache( $oldid ) {
global $wgUser, $wgEnableParserCache;
return $wgEnableParserCache
- && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0
+ && $wgUser->getStubThreshold() == 0
&& $this->exists()
&& empty( $oldid )
&& !$this->mTitle->isCssOrJsPage()
@@ -1337,15 +1474,22 @@ class Article {
*/
public function doViewParse() {
global $wgOut;
+
$oldid = $this->getOldID();
- $useParserCache = $this->useParserCache( $oldid );
- $parserOptions = clone $this->getParserOptions();
+ $parserOptions = $this->getParserOptions();
+
# Render printable version, use printable version cache
$parserOptions->setIsPrintable( $wgOut->isPrintable() );
+
# Don't show section-edit links on old revisions... this way lies madness.
- $parserOptions->setEditSection( $this->isCurrent() );
+ if ( !$this->isCurrent() || $wgOut->isPrintable() || !$this->mTitle->quickUserCan( 'edit' ) ) {
+ $parserOptions->setEditSection( false );
+ }
+
$useParserCache = $this->useParserCache( $oldid );
$this->outputWikiText( $this->getContent(), $useParserCache, $parserOptions );
+
+ return true;
}
/**
@@ -1353,13 +1497,21 @@ class Article {
* output it and return true. If it is not present, output nothing and
* return false. This is used as a callback function for
* PoolCounter::executeProtected().
+ *
+ * @return boolean
*/
public function tryDirtyCache() {
global $wgOut;
$parserCache = ParserCache::singleton();
$options = $this->getParserOptions();
- $options->setIsPrintable( $wgOut->isPrintable() );
+
+ if ( $wgOut->isPrintable() ) {
+ $options->setIsPrintable( true );
+ $options->setEditSection( false );
+ }
+
$output = $parserCache->getDirty( $this, $options );
+
if ( $output ) {
wfDebug( __METHOD__ . ": sending dirty output\n" );
wfDebugLog( 'dirty', "dirty output " . $parserCache->getKey( $this, $options ) . "\n" );
@@ -1367,126 +1519,123 @@ class Article {
$this->mParserOutput = $output;
$wgOut->addParserOutput( $output );
$wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
+
return true;
} else {
wfDebugLog( 'dirty', "dirty missing\n" );
wfDebug( __METHOD__ . ": no dirty cache\n" );
+
return false;
}
}
/**
- * Show an error page for an error from the pool counter.
- * @param $status Status
- */
- public function showPoolError( $status ) {
- global $wgOut;
- $wgOut->clearHTML(); // for release() errors
- $wgOut->enableClientCache( false );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->addWikiText(
- '<div class="errorbox">' .
- $status->getWikiText( false, 'view-pool-error' ) .
- '</div>'
- );
- }
-
- /**
* View redirect
+ *
* @param $target Title object or Array of destination(s) to redirect
* @param $appendSubtitle Boolean [optional]
* @param $forceKnown Boolean: should the image be shown as a bluelink regardless of existence?
+ * @return string containing HMTL with redirect link
*/
public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
global $wgOut, $wgContLang, $wgStylePath, $wgUser;
- # Display redirect
+
if ( !is_array( $target ) ) {
$target = array( $target );
}
+
$imageDir = $wgContLang->getDir();
- $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
- $imageUrl2 = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
- $alt2 = $wgContLang->isRTL() ? '&larr;' : '&rarr;'; // should -> and <- be used instead of entities?
if ( $appendSubtitle ) {
$wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) );
}
+
$sk = $wgUser->getSkin();
// the loop prepends the arrow image before the link, so the first case needs to be outside
$title = array_shift( $target );
+
if ( $forceKnown ) {
- $link = $sk->link(
- $title,
- htmlspecialchars( $title->getFullText() ),
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
+ $link = $sk->linkKnown( $title, htmlspecialchars( $title->getFullText() ) );
} else {
$link = $sk->link( $title, htmlspecialchars( $title->getFullText() ) );
}
- // automatically append redirect=no to each link, since most of them are redirect pages themselves
+
+ $nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
+ $alt = $wgContLang->isRTL() ? '←' : '→';
+ // Automatically append redirect=no to each link, since most of them are redirect pages themselves.
+ // FIXME: where this happens?
foreach ( $target as $rt ) {
+ $link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) );
if ( $forceKnown ) {
- $link .= '<img src="' . $imageUrl2 . '" alt="' . $alt2 . ' " />'
- . $sk->link(
- $rt,
- htmlspecialchars( $rt->getFullText() ),
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
+ $link .= $sk->linkKnown( $rt, htmlspecialchars( $rt->getFullText() ) );
} else {
- $link .= '<img src="' . $imageUrl2 . '" alt="' . $alt2 . ' " />'
- . $sk->link( $rt, htmlspecialchars( $rt->getFullText() ) );
+ $link .= $sk->link( $rt, htmlspecialchars( $rt->getFullText() ) );
}
}
- return '<img src="' . $imageUrl . '" alt="#REDIRECT " />' .
- '<span class="redirectText">' . $link . '</span>';
+ $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
+ return '<div class="redirectMsg">' .
+ Html::element( 'img', array( 'src' => $imageUrl, 'alt' => '#REDIRECT' ) ) .
+ '<span class="redirectText">' . $link . '</span></div>';
}
+ /**
+ * Builds trackback links for article display if $wgUseTrackbacks is set to true
+ */
public function addTrackbacks() {
global $wgOut, $wgUser;
+
$dbr = wfGetDB( DB_SLAVE );
$tbs = $dbr->select( 'trackbacks',
array( 'tb_id', 'tb_title', 'tb_url', 'tb_ex', 'tb_name' ),
array( 'tb_page' => $this->getID() )
);
- if ( !$dbr->numRows( $tbs ) ) return;
+
+ if ( !$dbr->numRows( $tbs ) ) {
+ return;
+ }
$wgOut->preventClickjacking();
$tbtext = "";
- while ( $o = $dbr->fetchObject( $tbs ) ) {
+ foreach ( $tbs as $o ) {
$rmvtxt = "";
+
if ( $wgUser->isAllowed( 'trackback' ) ) {
$delurl = $this->mTitle->getFullURL( "action=deletetrackback&tbid=" .
$o->tb_id . "&token=" . urlencode( $wgUser->editToken() ) );
$rmvtxt = wfMsg( 'trackbackremove', htmlspecialchars( $delurl ) );
}
+
$tbtext .= "\n";
- $tbtext .= wfMsg( strlen( $o->tb_ex ) ? 'trackbackexcerpt' : 'trackback',
+ $tbtext .= wfMsgNoTrans( strlen( $o->tb_ex ) ? 'trackbackexcerpt' : 'trackback',
$o->tb_title,
$o->tb_url,
$o->tb_ex,
$o->tb_name,
$rmvtxt );
}
- $wgOut->wrapWikiMsg( "<div id='mw_trackbacks'>$1</div>\n", array( 'trackbackbox', $tbtext ) );
- $this->mTitle->invalidateCache();
+
+ $wgOut->wrapWikiMsg( "<div id='mw_trackbacks'>\n$1\n</div>\n", array( 'trackbackbox', $tbtext ) );
}
+ /**
+ * Removes trackback record for current article from trackbacks table
+ */
public function deletetrackback() {
global $wgUser, $wgRequest, $wgOut;
+
if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) {
$wgOut->addWikiMsg( 'sessionfailure' );
+
return;
}
$permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
+
if ( count( $permission_errors ) ) {
$wgOut->showPermissionsErrorPage( $permission_errors );
+
return;
}
@@ -1497,8 +1646,13 @@ class Article {
$this->mTitle->invalidateCache();
}
+ /**
+ * Handle action=render
+ */
+
public function render() {
global $wgOut;
+
$wgOut->setArticleBodyOnly( true );
$this->view();
}
@@ -1508,22 +1662,30 @@ class Article {
*/
public function purge() {
global $wgUser, $wgRequest, $wgOut;
+
if ( $wgUser->isAllowed( 'purge' ) || $wgRequest->wasPosted() ) {
+ //FIXME: shouldn't this be in doPurge()?
if ( wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
$this->doPurge();
$this->view();
}
} else {
- $action = htmlspecialchars( $wgRequest->getRequestURL() );
- $button = wfMsgExt( 'confirm_purge_button', array( 'escapenoentities' ) );
- $form = "<form method=\"post\" action=\"$action\">\n" .
- "<input type=\"submit\" name=\"submit\" value=\"$button\" />\n" .
- "</form>\n";
- $top = wfMsgExt( 'confirm-purge-top', array( 'parse' ) );
- $bottom = wfMsgExt( 'confirm-purge-bottom', array( 'parse' ) );
+ $formParams = array(
+ 'method' => 'post',
+ 'action' => $wgRequest->getRequestURL(),
+ );
+
+ $wgOut->addWikiMsg( 'confirm-purge-top' );
+
+ $form = Html::openElement( 'form', $formParams );
+ $form .= Xml::submitButton( wfMsg( 'confirm_purge_button' ) );
+ $form .= Html::closeElement( 'form' );
+
+ $wgOut->addHTML( $form );
+ $wgOut->addWikiMsg( 'confirm-purge-bottom' );
+
$wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->addHTML( $top . $form . $bottom );
}
}
@@ -1532,6 +1694,7 @@ class Article {
*/
public function doPurge() {
global $wgUseSquid;
+
// Invalidate the cache
$this->mTitle->invalidateCache();
@@ -1544,13 +1707,16 @@ class Article {
$update = SquidUpdate::newSimplePurge( $this->mTitle );
$update->doUpdate();
}
+
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
global $wgMessageCache;
+
if ( $this->getID() == 0 ) {
$text = false;
} else {
$text = $this->getRawText();
}
+
$wgMessageCache->replace( $this->mTitle->getDBkey(), $text );
}
}
@@ -1558,7 +1724,7 @@ class Article {
/**
* Insert a new empty page record for this article.
* This *must* be followed up by creating a revision
- * and running $this->updateToLatest( $rev_id );
+ * and running $this->updateRevisionOn( ... );
* or else the record will be left in a funky state.
* Best if all done inside a transaction.
*
@@ -1585,18 +1751,20 @@ class Article {
), __METHOD__, 'IGNORE' );
$affected = $dbw->affectedRows();
+
if ( $affected ) {
$newid = $dbw->insertId();
$this->mTitle->resetArticleId( $newid );
}
wfProfileOut( __METHOD__ );
+
return $affected ? $newid : false;
}
/**
* Update the page record to point to a newly saved revision.
*
- * @param $dbw Database object
+ * @param $dbw DatabaseBase: object
* @param $revision Revision: For ID number, and text used to set
length and redirect status fields
* @param $lastRevision Integer: if given, will not overwrite the page field
@@ -1612,9 +1780,10 @@ class Article {
wfProfileIn( __METHOD__ );
$text = $revision->getText();
- $rt = Title::newFromRedirect( $text );
+ $rt = Title::newFromRedirectRecurse( $text );
$conditions = array( 'page_id' => $this->getId() );
+
if ( !is_null( $lastRevision ) ) {
# An extra check against threads stepping on each other
$conditions['page_latest'] = $lastRevision;
@@ -1656,27 +1825,25 @@ class Article {
// Update/Insert if we don't know if the last revision was a redirect or not
// Delete if changing from redirect to non-redirect
$isRedirect = !is_null( $redirectTitle );
+
if ( $isRedirect || is_null( $lastRevIsRedirect ) || $lastRevIsRedirect !== $isRedirect ) {
wfProfileIn( __METHOD__ );
if ( $isRedirect ) {
- // This title is a redirect, Add/Update row in the redirect table
- $set = array( /* SET */
- 'rd_namespace' => $redirectTitle->getNamespace(),
- 'rd_title' => $redirectTitle->getDBkey(),
- 'rd_from' => $this->getId(),
- );
- $dbw->replace( 'redirect', array( 'rd_from' ), $set, __METHOD__ );
+ $this->insertRedirectEntry( $redirectTitle );
} else {
// This is not a redirect, remove row from redirect table
$where = array( 'rd_from' => $this->getId() );
$dbw->delete( 'redirect', $where, __METHOD__ );
}
+
if ( $this->getTitle()->getNamespace() == NS_FILE ) {
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
}
wfProfileOut( __METHOD__ );
+
return ( $dbw->affectedRows() != 0 );
}
+
return true;
}
@@ -1686,9 +1853,11 @@ class Article {
*
* @param $dbw Database object
* @param $revision Revision object
+ * @return mixed
*/
public function updateIfNewerOn( &$dbw, $revision ) {
wfProfileIn( __METHOD__ );
+
$row = $dbw->selectRow(
array( 'revision', 'page' ),
array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ),
@@ -1696,6 +1865,7 @@ class Article {
'page_id' => $this->getId(),
'page_latest=rev_id' ),
__METHOD__ );
+
if ( $row ) {
if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
wfProfileOut( __METHOD__ );
@@ -1708,17 +1878,23 @@ class Article {
$prev = 0;
$lastRevIsRedirect = null;
}
+
$ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
+
wfProfileOut( __METHOD__ );
return $ret;
}
/**
* @param $section empty/null/false or a section number (0, 1, 2, T1, T2...)
+ * @param $text String: new text of the section
+ * @param $summary String: new section's subject, only if $section is 'new'
+ * @param $edittime String: revision timestamp or null to use the current revision
* @return string Complete article text, or null if error
*/
public function replaceSection( $section, $text, $summary = '', $edittime = null ) {
wfProfileIn( __METHOD__ );
+
if ( strval( $section ) == '' ) {
// Whole-page edit; let the whole text through
} else {
@@ -1728,11 +1904,14 @@ class Article {
$dbw = wfGetDB( DB_MASTER );
$rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
}
+
if ( !$rev ) {
wfDebug( "Article::replaceSection asked for bogus section (page: " .
$this->getId() . "; section: $section; edittime: $edittime)\n" );
+ wfProfileOut( __METHOD__ );
return null;
}
+
$oldtext = $rev->getText();
if ( $section == 'new' ) {
@@ -1744,9 +1923,11 @@ class Article {
} else {
# Replacing an existing section; roll out the big guns
global $wgParser;
+
$text = $wgParser->replaceSection( $oldtext, $section, $text );
}
}
+
wfProfileOut( __METHOD__ );
return $text;
}
@@ -1765,7 +1946,6 @@ class Article {
if ( $comment && $summary != "" ) {
$text = wfMsgForContent( 'newsectionheaderdefaultlevel', $summary ) . "\n\n" . $text;
}
-
$this->doEdit( $text, $summary, $flags );
$dbw = wfGetDB( DB_MASTER );
@@ -1794,6 +1974,7 @@ class Article {
( $forceBot ? EDIT_FORCE_BOT : 0 );
$status = $this->doEdit( $text, $summary, $flags );
+
if ( !$status->isOK() ) {
return false;
}
@@ -1821,6 +2002,23 @@ class Article {
}
/**
+ * Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
+ * @param $flags Int
+ * @return Int updated $flags
+ */
+ function checkFlags( $flags ) {
+ if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
+ if ( $this->mTitle->getArticleID() ) {
+ $flags |= EDIT_UPDATE;
+ } else {
+ $flags |= EDIT_NEW;
+ }
+ }
+
+ return $flags;
+ }
+
+ /**
* Article::doEdit()
*
* Change an existing article or create a new article. Updates RC and all necessary caches,
@@ -1874,7 +2072,7 @@ class Article {
global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
# Low-level sanity check
- if ( $this->mTitle->getText() == '' ) {
+ if ( $this->mTitle->getText() === '' ) {
throw new MWException( 'Something is trying to edit an article with an empty title' );
}
@@ -1886,23 +2084,18 @@ class Article {
# Load $this->mTitle->getArticleID() and $this->mLatest if it's not already
$this->loadPageData();
- if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
- $aid = $this->mTitle->getArticleID();
- if ( $aid ) {
- $flags |= EDIT_UPDATE;
- } else {
- $flags |= EDIT_NEW;
- }
- }
+ $flags = $this->checkFlags( $flags );
if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary,
$flags & EDIT_MINOR, null, null, &$flags, &$status ) ) )
{
wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
- wfProfileOut( __METHOD__ );
+
if ( $status->isOK() ) {
$status->fatal( 'edit-hook-aborted' );
}
+
+ wfProfileOut( __METHOD__ );
return $status;
}
@@ -1929,13 +2122,12 @@ class Article {
if ( $flags & EDIT_UPDATE ) {
# Update article, but only if changed.
$status->value['new'] = false;
+
# Make sure the revision is either completely inserted or not inserted at all
if ( !$wgDBtransactions ) {
$userAbort = ignore_user_abort( true );
}
- $revisionId = 0;
-
$changed = ( strcmp( $text, $oldtext ) != 0 );
if ( $changed ) {
@@ -1947,6 +2139,7 @@ class Article {
# Article gone missing
wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
$status->fatal( 'edit-gone-missing' );
+
wfProfileOut( __METHOD__ );
return $status;
}
@@ -1959,7 +2152,7 @@ class Article {
'parent_id' => $this->mLatest,
'user' => $user->getId(),
'user_text' => $user->getName(),
- ) );
+ ) );
$dbw->begin();
$revisionId = $revision->insertOn( $dbw );
@@ -1976,10 +2169,12 @@ class Article {
if ( !$ok ) {
/* Belated edit conflict! Run away!! */
$status->fatal( 'edit-conflict' );
+
# Delete the invalid revision if the DB is not transactional
if ( !$wgDBtransactions ) {
$dbw->delete( 'revision', array( 'rev_id' => $revisionId ), __METHOD__ );
}
+
$revisionId = 0;
$dbw->rollback();
} else {
@@ -1994,6 +2189,7 @@ class Article {
$this->mLatest, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
$revisionId, $patrolled
);
+
# Log auto-patrolled edits
if ( $patrolled ) {
PatrolLog::record( $rc, true );
@@ -2015,6 +2211,7 @@ class Article {
if ( !$wgDBtransactions ) {
ignore_user_abort( $userAbort );
}
+
// Now that ignore_user_abort is restored, we can respond to fatal errors
if ( !$status->isOK() ) {
wfProfileOut( __METHOD__ );
@@ -2045,6 +2242,7 @@ class Article {
if ( $newid === false ) {
$dbw->rollback();
$status->fatal( 'edit-already-exists' );
+
wfProfileOut( __METHOD__ );
return $status;
}
@@ -2066,14 +2264,17 @@ class Article {
$this->updateRevisionOn( $dbw, $revision, 0 );
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+
# Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
global $wgUseRCPatrol, $wgUseNPPatrol;
+
# Mark as patrolled if the user can do so
$patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && $this->mTitle->userCan( 'autopatrol' );
# Add RC row to the DB
$rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
'', strlen( $text ), $revisionId, $patrolled );
+
# Log auto-patrolled edits
if ( $patrolled ) {
PatrolLog::record( $rc, true );
@@ -2125,13 +2326,15 @@ class Article {
*/
public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
global $wgOut;
+
if ( $noRedir ) {
$query = 'redirect=no';
if ( $extraQuery )
- $query .= "&$query";
+ $query .= "&$extraQuery";
} else {
$query = $extraQuery;
}
+
$wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor );
}
@@ -2139,12 +2342,20 @@ class Article {
* Mark this particular edit/page as patrolled
*/
public function markpatrolled() {
- global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUseNPPatrol, $wgUser;
+ global $wgOut, $wgUser, $wgRequest;
+
$wgOut->setRobotPolicy( 'noindex,nofollow' );
# If we haven't been given an rc_id value, we can't do anything
$rcid = (int) $wgRequest->getVal( 'rcid' );
+
+ if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ), $rcid ) ) {
+ $wgOut->showErrorPage( 'sessionfailure-title', 'sessionfailure' );
+ return;
+ }
+
$rc = RecentChange::newFromId( $rcid );
+
if ( is_null( $rc ) ) {
$wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
return;
@@ -2154,11 +2365,11 @@ class Article {
$returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges';
$return = SpecialPage::getTitleFor( $returnto );
- $dbw = wfGetDB( DB_MASTER );
$errors = $rc->doMarkPatrolled();
if ( in_array( array( 'rcpatroldisabled' ), $errors ) ) {
$wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
+
return;
}
@@ -2171,11 +2382,13 @@ class Article {
$wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) );
$wgOut->addWikiMsg( 'markedaspatrollederror-noautopatrol' );
$wgOut->returnToMain( false, $return );
+
return;
}
if ( !empty( $errors ) ) {
$wgOut->showPermissionsErrorPage( $errors );
+
return;
}
@@ -2190,35 +2403,45 @@ class Article {
*/
public function watch() {
global $wgUser, $wgOut;
+
if ( $wgUser->isAnon() ) {
$wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
return;
}
+
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
return;
}
+
if ( $this->doWatch() ) {
$wgOut->setPagetitle( wfMsg( 'addedwatch' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addWikiMsg( 'addedwatchtext', $this->mTitle->getPrefixedText() );
}
+
$wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
}
/**
* Add this page to $wgUser's watchlist
+ *
+ * This is safe to be called multiple times
+ *
* @return bool true on successful watch operation
*/
public function doWatch() {
global $wgUser;
+
if ( $wgUser->isAnon() ) {
return false;
}
+
if ( wfRunHooks( 'WatchArticle', array( &$wgUser, &$this ) ) ) {
$wgUser->addWatch( $this->mTitle );
return wfRunHooks( 'WatchArticleComplete', array( &$wgUser, &$this ) );
}
+
return false;
}
@@ -2227,19 +2450,23 @@ class Article {
*/
public function unwatch() {
global $wgUser, $wgOut;
+
if ( $wgUser->isAnon() ) {
$wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
return;
}
+
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
return;
}
+
if ( $this->doUnwatch() ) {
$wgOut->setPagetitle( wfMsg( 'removedwatch' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addWikiMsg( 'removedwatchtext', $this->mTitle->getPrefixedText() );
}
+
$wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
}
@@ -2249,13 +2476,16 @@ class Article {
*/
public function doUnwatch() {
global $wgUser;
+
if ( $wgUser->isAnon() ) {
return false;
}
+
if ( wfRunHooks( 'UnwatchArticle', array( &$wgUser, &$this ) ) ) {
$wgUser->removeWatch( $this->mTitle );
return wfRunHooks( 'UnwatchArticleComplete', array( &$wgUser, &$this ) );
}
+
return false;
}
@@ -2289,8 +2519,9 @@ class Article {
$restrictionTypes = $this->mTitle->getRestrictionTypes();
$id = $this->mTitle->getArticleID();
+
if ( $id <= 0 ) {
- wfDebug( "updateRestrictions failed: $id <= 0\n" );
+ wfDebug( "updateRestrictions failed: article id $id <= 0\n" );
return false;
}
@@ -2316,6 +2547,7 @@ class Article {
$current = array();
$updated = Article::flattenRestrictions( $limit );
$changed = false;
+
foreach ( $restrictionTypes as $action ) {
if ( isset( $expiry[$action] ) ) {
# Get current restrictions on $action
@@ -2323,6 +2555,7 @@ class Article {
$current[$action] = implode( '', $aLimits );
# Are any actual restrictions being dealt with here?
$aRChanged = count( $aLimits ) || !empty( $limit[$action] );
+
# If something changed, we need to log it. Checking $aRChanged
# assures that "unprotecting" a page that is not protected does
# not log just because the expiry was "changed".
@@ -2341,38 +2574,44 @@ class Article {
# If nothing's changed, do nothing
if ( $changed ) {
if ( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) {
-
$dbw = wfGetDB( DB_MASTER );
# Prepare a null revision to be added to the history
$modified = $current != '' && $protect;
+
if ( $protect ) {
$comment_type = $modified ? 'modifiedarticleprotection' : 'protectedarticle';
} else {
$comment_type = 'unprotectedarticle';
}
+
$comment = $wgContLang->ucfirst( wfMsgForContent( $comment_type, $this->mTitle->getPrefixedText() ) );
# Only restrictions with the 'protect' right can cascade...
# Otherwise, people who cannot normally protect can "protect" pages via transclusion
$editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' );
+
# The schema allows multiple restrictions
- if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) )
+ if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) ) {
$cascade = false;
+ }
+
$cascade_description = '';
+
if ( $cascade ) {
$cascade_description = ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
}
- if ( $reason )
+ if ( $reason ) {
$comment .= ": $reason";
+ }
$editComment = $comment;
$encodedExpiry = array();
$protect_description = '';
- foreach ( $limit as $action => $restrictions ) {
+ foreach ( $limit as $action => $restrictions ) {
if ( !isset( $expiry[$action] ) )
- $expiry[$action] = 'infinite';
+ $expiry[$action] = Block::infinity();
$encodedExpiry[$action] = Block::encodeExpiry( $expiry[$action], $dbw );
if ( $restrictions != '' ) {
@@ -2385,15 +2624,20 @@ class Article {
} else {
$protect_description .= wfMsgForContent( 'protect-expiry-indefinite' );
}
+
$protect_description .= ') ';
}
}
$protect_description = trim( $protect_description );
- if ( $protect_description && $protect )
+ if ( $protect_description && $protect ) {
$editComment .= " ($protect_description)";
- if ( $cascade )
+ }
+
+ if ( $cascade ) {
$editComment .= "$cascade_description";
+ }
+
# Update restrictions table
foreach ( $limit as $action => $restrictions ) {
if ( $restrictions != '' ) {
@@ -2402,7 +2646,10 @@ class Article {
'pr_type' => $action,
'pr_level' => $restrictions,
'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
- 'pr_expiry' => $encodedExpiry[$action] ), __METHOD__ );
+ 'pr_expiry' => $encodedExpiry[$action]
+ ),
+ __METHOD__
+ );
} else {
$dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
'pr_type' => $action ), __METHOD__ );
@@ -2436,7 +2683,6 @@ class Article {
} else {
$log->addEntry( 'unprotect', $this->mTitle, $reason );
}
-
} # End hook
} # End "changed" check
@@ -2453,35 +2699,46 @@ class Article {
if ( !is_array( $limit ) ) {
throw new MWException( 'Article::flattenRestrictions given non-array restriction set' );
}
+
$bits = array();
ksort( $limit );
+
foreach ( $limit as $action => $restrictions ) {
if ( $restrictions != '' ) {
$bits[] = "$action=$restrictions";
}
}
+
return implode( ':', $bits );
}
/**
* Auto-generates a deletion reason
+ *
* @param &$hasHistory Boolean: whether the page has a history
+ * @return mixed String containing deletion reason or empty string, or boolean false
+ * if no revision occurred
*/
public function generateReason( &$hasHistory ) {
global $wgContLang;
+
$dbw = wfGetDB( DB_MASTER );
// Get the last revision
$rev = Revision::newFromTitle( $this->mTitle );
- if ( is_null( $rev ) )
+
+ if ( is_null( $rev ) ) {
return false;
+ }
// Get the article's contents
$contents = $rev->getText();
$blank = false;
+
// If the page is blank, use the text from the previous revision,
// which can only be blank if there's a move/import/protect dummy revision involved
if ( $contents == '' ) {
$prev = $rev->getPrevious();
+
if ( $prev ) {
$contents = $prev->getText();
$blank = true;
@@ -2495,21 +2752,27 @@ class Article {
__METHOD__,
array( 'LIMIT' => 20 )
);
- if ( $res === false )
+
+ if ( $res === false ) {
// This page has no revisions, which is very weird
return false;
+ }
$hasHistory = ( $res->numRows() > 1 );
$row = $dbw->fetchObject( $res );
- $onlyAuthor = $row->rev_user_text;
- // Try to find a second contributor
- foreach ( $res as $row ) {
- if ( $row->rev_user_text != $onlyAuthor ) {
- $onlyAuthor = false;
- break;
+
+ if ( $row ) { // $row is false if the only contributor is hidden
+ $onlyAuthor = $row->rev_user_text;
+ // Try to find a second contributor
+ foreach ( $res as $row ) {
+ if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
+ $onlyAuthor = false;
+ break;
+ }
}
+ } else {
+ $onlyAuthor = false;
}
- $dbw->freeResult( $res );
// Generate the summary with a '$1' placeholder
if ( $blank ) {
@@ -2517,10 +2780,11 @@ class Article {
// blank. It's just not our lucky day
$reason = wfMsgForContent( 'exbeforeblank', '$1' );
} else {
- if ( $onlyAuthor )
+ if ( $onlyAuthor ) {
$reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
- else
+ } else {
$reason = wfMsgForContent( 'excontent', '$1' );
+ }
}
if ( $reason == '-' ) {
@@ -2538,6 +2802,7 @@ class Article {
$contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
// Now replace the '$1' placeholder
$reason = str_replace( '$1', $contents, $reason );
+
return $reason;
}
@@ -2562,6 +2827,7 @@ class Article {
} elseif ( $reason == 'other' ) {
$reason = $this->DeleteReason;
}
+
# Flag to hide all contents of the archived revisions
$suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
@@ -2570,6 +2836,7 @@ class Article {
# Read-only check...
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
+
return;
}
@@ -2578,6 +2845,7 @@ class Article {
if ( count( $permission_errors ) > 0 ) {
$wgOut->showPermissionsErrorPage( $permission_errors );
+
return;
}
@@ -2601,6 +2869,7 @@ class Article {
'delete',
$this->mTitle->getPrefixedText()
);
+
return;
}
@@ -2608,38 +2877,47 @@ class Article {
$bigHistory = $this->isBigDeletion();
if ( $bigHistory && !$this->mTitle->userCan( 'bigdelete' ) ) {
global $wgLang, $wgDeleteRevisionsLimit;
- $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n",
+
+ $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
+
return;
}
if ( $confirm ) {
$this->doDelete( $reason, $suppress );
+
if ( $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn() ) {
$this->doWatch();
} elseif ( $this->mTitle->userIsWatching() ) {
$this->doUnwatch();
}
+
return;
}
// Generate deletion reason
$hasHistory = false;
- if ( !$reason ) $reason = $this->generateReason( $hasHistory );
+ if ( !$reason ) {
+ $reason = $this->generateReason( $hasHistory );
+ }
// If the page has a history, insert a warning
if ( $hasHistory && !$confirm ) {
global $wgLang;
+
$skin = $wgUser->getSkin();
$revisions = $this->estimateRevisionCount();
+ //FIXME: lego
$wgOut->addHTML( '<strong class="mw-delete-warning-revisions">' .
wfMsgExt( 'historywarning', array( 'parseinline' ), $wgLang->formatNum( $revisions ) ) .
wfMsgHtml( 'word-separator' ) . $skin->historyLink() .
'</strong>'
);
+
if ( $bigHistory ) {
global $wgDeleteRevisionsLimit;
- $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n",
+ $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
}
}
@@ -2652,10 +2930,13 @@ class Article {
*/
public function isBigDeletion() {
global $wgDeleteRevisionsLimit;
+
if ( $wgDeleteRevisionsLimit ) {
$revCount = $this->estimateRevisionCount();
+
return $revCount > $wgDeleteRevisionsLimit;
}
+
return false;
}
@@ -2664,6 +2945,7 @@ class Article {
*/
public function estimateRevisionCount() {
$dbr = wfGetDB( DB_SLAVE );
+
// For an exact count...
// return $dbr->selectField( 'revision', 'COUNT(*)',
// array( 'rev_page' => $this->getId() ), __METHOD__ );
@@ -2683,6 +2965,7 @@ class Article {
// If that doesn't have the latest revision, try the master
$continue = 2;
$db = wfGetDB( DB_SLAVE );
+
do {
$res = $db->select( array( 'page', 'revision' ),
array( 'rev_id', 'rev_user_text' ),
@@ -2695,11 +2978,14 @@ class Article {
'LIMIT' => $num
) )
);
+
if ( !$res ) {
wfProfileOut( __METHOD__ );
return array();
}
+
$row = $db->fetchObject( $res );
+
if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
$db = wfGetDB( DB_MASTER );
$continue--;
@@ -2709,15 +2995,18 @@ class Article {
} while ( $continue );
$authors = array( $row->rev_user_text );
- while ( $row = $db->fetchObject( $res ) ) {
+
+ foreach ( $res as $row ) {
$authors[] = $row->rev_user_text;
}
+
wfProfileOut( __METHOD__ );
return $authors;
}
/**
* Output deletion confirmation dialog
+ * FIXME: Move to another file?
* @param $reason String: prefilled reason
*/
public function confirmDelete( $reason ) {
@@ -2725,13 +3014,7 @@ class Article {
wfDebug( "Article::confirmDelete\n" );
- $deleteBackLink = $wgUser->getSkin()->link(
- $this->mTitle,
- null,
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
+ $deleteBackLink = $wgUser->getSkin()->linkKnown( $this->mTitle );
$wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $deleteBackLink ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addWikiMsg( 'confirmdeletetext' );
@@ -2780,7 +3063,8 @@ class Article {
) ) .
"</td>
</tr>";
- # Dissalow watching is user is not logged in
+
+ # Disallow watching if user is not logged in
if ( $wgUser->isLoggedIn() ) {
$form .= "
<tr>
@@ -2791,6 +3075,7 @@ class Article {
"</td>
</tr>";
}
+
$form .= "
$suppress
<tr>
@@ -2802,7 +3087,7 @@ class Article {
</tr>" .
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' ) .
- Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
+ Html::hidden( 'wpEditToken', $wgUser->editToken() ) .
Xml::closeElement( 'form' );
if ( $wgUser->isAllowed( 'editinterface' ) ) {
@@ -2819,9 +3104,7 @@ class Article {
$wgOut->addHTML( $form );
$wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
- LogEventsList::showLogExtract(
- $wgOut,
- 'delete',
+ LogEventsList::showLogExtract( $wgOut, 'delete',
$this->mTitle->getPrefixedText()
);
}
@@ -2831,7 +3114,8 @@ class Article {
*/
public function doDelete( $reason, $suppress = false ) {
global $wgOut, $wgUser;
- $id = $this->mTitle->getArticleID( GAID_FOR_UPDATE );
+
+ $id = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
$error = '';
if ( wfRunHooks( 'ArticleDelete', array( &$this, &$wgUser, &$reason, &$error ) ) ) {
@@ -2856,7 +3140,9 @@ class Article {
wfMsgExt( 'cannotdelete', array( 'parse' ), $this->mTitle->getPrefixedText() )
)
);
+
$wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
+
LogEventsList::showLogExtract(
$wgOut,
'delete',
@@ -2871,20 +3157,27 @@ class Article {
/**
* Back-end article deletion
* Deletes the article with database consistency, writes logs, purges caches
- * Returns success
+ *
+ * @param $reason string delete reason for deletion log
+ * @param suppress bitfield
+ * Revision::DELETED_TEXT
+ * Revision::DELETED_COMMENT
+ * Revision::DELETED_USER
+ * Revision::DELETED_RESTRICTED
+ * @param $id int article ID
+ * @param $commit boolean defaults to true, triggers transaction end
+ * @return boolean true if successful
*/
- public function doDeleteArticle( $reason, $suppress = false, $id = 0 ) {
- global $wgUseSquid, $wgDeferredUpdateList;
- global $wgUseTrackbacks;
+ public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true ) {
+ global $wgDeferredUpdateList, $wgUseTrackbacks;
wfDebug( __METHOD__ . "\n" );
$dbw = wfGetDB( DB_MASTER );
- $ns = $this->mTitle->getNamespace();
$t = $this->mTitle->getDBkey();
- $id = $id ? $id : $this->mTitle->getArticleID( GAID_FOR_UPDATE );
+ $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
- if ( $t == '' || $id == 0 ) {
+ if ( $t === '' || $id == 0 ) {
return false;
}
@@ -2942,6 +3235,7 @@ class Article {
# Now that it's safely backed up, delete it
$dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
$ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy
+
if ( !$ok ) {
$dbw->rollback();
return false;
@@ -2950,9 +3244,11 @@ class Article {
# Fix category table counts
$cats = array();
$res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
+
foreach ( $res as $row ) {
$cats [] = $row->cl_to;
}
+
$this->updateCategoryCounts( array(), $cats );
# If using cascading deletes, we can skip some explicit deletes
@@ -2969,6 +3265,7 @@ class Article {
$dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
$dbw->delete( 'externallinks', array( 'el_from' => $id ) );
$dbw->delete( 'langlinks', array( 'll_from' => $id ) );
+ $dbw->delete( 'iwlinks', array( 'iwl_from' => $id ) );
$dbw->delete( 'redirect', array( 'rd_from' => $id ) );
}
@@ -2998,7 +3295,9 @@ class Article {
# Make sure logging got through
$log->addEntry( 'delete', $this->mTitle, $reason, array() );
- $dbw->commit();
+ if ( $commit ) {
+ $dbw->commit();
+ }
return true;
}
@@ -3026,6 +3325,7 @@ class Article {
*/
public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails ) {
global $wgUser;
+
$resultDetails = null;
# Check permissions
@@ -3033,15 +3333,18 @@ class Article {
$rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $wgUser );
$errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
- if ( !$wgUser->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) )
+ if ( !$wgUser->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) ) {
$errors[] = array( 'sessionfailure' );
+ }
if ( $wgUser->pingLimiter( 'rollback' ) || $wgUser->pingLimiter() ) {
$errors[] = array( 'actionthrottledtext' );
}
+
# If there were errors, bail out now
- if ( !empty( $errors ) )
+ if ( !empty( $errors ) ) {
return $errors;
+ }
return $this->commitRollback( $fromP, $summary, $bot, $resultDetails );
}
@@ -3057,6 +3360,7 @@ class Article {
*/
public function commitRollback( $fromP, $summary, $bot, &$resultDetails ) {
global $wgUseRCPatrol, $wgUser, $wgLang;
+
$dbw = wfGetDB( DB_MASTER );
if ( wfReadOnly() ) {
@@ -3092,12 +3396,12 @@ class Article {
"rev_user != {$user} OR rev_user_text != {$user_text}"
), __METHOD__,
array( 'USE INDEX' => 'page_timestamp',
- 'ORDER BY' => 'rev_timestamp DESC' )
+ 'ORDER BY' => 'rev_timestamp DESC' )
);
if ( $s === false ) {
# No one else ever edited this page
return array( array( 'cantrollback' ) );
- } else if ( $s->rev_deleted & REVISION::DELETED_TEXT || $s->rev_deleted & REVISION::DELETED_USER ) {
+ } else if ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) {
# Only admins can see this text
return array( array( 'notvisiblerev' ) );
}
@@ -3107,6 +3411,7 @@ class Article {
# Mark all reverted edits as bot
$set['rc_bot'] = 1;
}
+
if ( $wgUseRCPatrol ) {
# Mark all reverted edits as patrolled
$set['rc_patrolled'] = 1;
@@ -3143,11 +3448,14 @@ class Article {
# Save
$flags = EDIT_UPDATE;
- if ( $wgUser->isAllowed( 'minoredit' ) )
+ if ( $wgUser->isAllowed( 'minoredit' ) ) {
$flags |= EDIT_MINOR;
+ }
- if ( $bot && ( $wgUser->isAllowed( 'markbotedits' ) || $wgUser->isAllowed( 'bot' ) ) )
+ if ( $bot && ( $wgUser->isAllowed( 'markbotedits' ) || $wgUser->isAllowed( 'bot' ) ) ) {
$flags |= EDIT_FORCE_BOT;
+ }
+
# Actually store the edit
$status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
if ( !empty( $status->value['revision'] ) ) {
@@ -3164,6 +3472,7 @@ class Article {
'target' => $target,
'newid' => $revId
);
+
return array();
}
@@ -3171,7 +3480,8 @@ class Article {
* User interface for rollback operations
*/
public function rollback() {
- global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol;
+ global $wgUser, $wgOut, $wgRequest;
+
$details = null;
$result = $this->doRollback(
@@ -3186,26 +3496,31 @@ class Article {
$wgOut->rateLimited();
return;
}
+
if ( isset( $result[0][0] ) && ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' ) ) {
$wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
$errArray = $result[0];
$errMsg = array_shift( $errArray );
$wgOut->addWikiMsgArray( $errMsg, $errArray );
+
if ( isset( $details['current'] ) ) {
$current = $details['current'];
+
if ( $current->getComment() != '' ) {
$wgOut->addWikiMsgArray( 'editcomment', array(
$wgUser->getSkin()->formatComment( $current->getComment() ) ), array( 'replaceafter' ) );
}
}
+
return;
}
+
# Display permissions errors before read-only message -- there's no
# point in misleading the user into thinking the inability to rollback
# is only temporary.
if ( !empty( $result ) && $result !== array( array( 'readonlytext' ) ) ) {
- # array_diff is completely broken for arrays of arrays, sigh. Re-
- # move any 'readonlytext' error manually.
+ # array_diff is completely broken for arrays of arrays, sigh.
+ # Remove any 'readonlytext' error manually.
$out = array();
foreach ( $result as $error ) {
if ( $error != array( 'readonlytext' ) ) {
@@ -3213,10 +3528,13 @@ class Article {
}
}
$wgOut->showPermissionsErrorPage( $out );
+
return;
}
+
if ( $result == array( array( 'readonlytext' ) ) ) {
$wgOut->readOnlyPage();
+
return;
}
@@ -3225,12 +3543,14 @@ class Article {
$newId = $details['newid'];
$wgOut->setPageTitle( wfMsg( 'actioncomplete' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
+
if ( $current->getUserText() === '' ) {
$old = wfMsg( 'rev-deleted-user' );
} else {
$old = $wgUser->getSkin()->userLink( $current->getUser(), $current->getUserText() )
. $wgUser->getSkin()->userToolLinks( $current->getUser(), $current->getUserText() );
}
+
$new = $wgUser->getSkin()->userLink( $target->getUser(), $target->getUserText() )
. $wgUser->getSkin()->userToolLinks( $target->getUser(), $target->getUserText() );
$wgOut->addHTML( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
@@ -3242,7 +3562,6 @@ class Article {
}
}
-
/**
* Do standard deferred updates after page view
*/
@@ -3251,12 +3570,14 @@ class Article {
if ( wfReadOnly() ) {
return;
}
+
# Don't update page view counters on views from bot users (bug 14044)
if ( !$wgDisableCounters && !$wgUser->isAllowed( 'bot' ) && $this->getID() ) {
Article::incViewCount( $this->getID() );
$u = new SiteStatsUpdate( 1, 0, 0 );
array_push( $wgDeferredUpdateList, $u );
}
+
# Update newtalk / watchlist notification status
$wgUser->clearNotification( $this->mTitle );
}
@@ -3270,15 +3591,19 @@ class Article {
// Already prepared
return $this->mPreparedEdit;
}
+
global $wgParser;
+
$edit = (object)array();
$edit->revid = $revid;
$edit->newText = $text;
$edit->pst = $this->preSaveTransform( $text );
- $options = $this->getParserOptions();
- $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $options, true, true, $revid );
+ $edit->popts = $this->getParserOptions();
+ $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
$edit->oldText = $this->getContent();
+
$this->mPreparedEdit = $edit;
+
return $edit;
}
@@ -3289,12 +3614,12 @@ class Article {
* Every 100th edit, prune the recent changes table.
*
* @private
- * @param $text New text of the article
- * @param $summary Edit summary
- * @param $minoredit Minor edit
+ * @param $text String: New text of the article
+ * @param $summary String: Edit summary
+ * @param $minoredit Boolean: Minor edit
* @param $timestamp_of_pagechange Timestamp associated with the page change
- * @param $newid rev_id value of the new revision
- * @param $changed Whether or not the content actually changed
+ * @param $newid Integer: rev_id value of the new revision
+ * @param $changed Boolean: Whether or not the content actually changed
*/
public function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange, $newid, $changed = true ) {
global $wgDeferredUpdateList, $wgMessageCache, $wgUser, $wgEnableParserCache;
@@ -3313,9 +3638,8 @@ class Article {
# Save it to the parser cache
if ( $wgEnableParserCache ) {
- $popts = $this->getParserOptions();
$parserCache = ParserCache::singleton();
- $parserCache->save( $editInfo->output, $this, $popts );
+ $parserCache->save( $editInfo->output, $this, $editInfo->popts );
}
# Update the links tables
@@ -3329,10 +3653,12 @@ class Article {
// 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
global $wgRCMaxAge;
+
$dbw = wfGetDB( DB_MASTER );
$cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
$recentchanges = $dbw->tableName( 'recentchanges' );
$sql = "DELETE FROM $recentchanges WHERE rc_timestamp < '{$cutoff}'";
+
$dbw->query( $sql );
}
}
@@ -3356,7 +3682,8 @@ class Article {
# load of user talk pages and piss people off, nor if it's a minor edit
# by a properly-flagged bot.
if ( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed
- && !( $minoredit && $wgUser->isAllowed( 'nominornewtalk' ) ) ) {
+ && !( $minoredit && $wgUser->isAllowed( 'nominornewtalk' ) )
+ ) {
if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
$other = User::newFromName( $shortTitle, false );
if ( !$other ) {
@@ -3410,13 +3737,14 @@ class Article {
return;
}
- $unhide = $wgRequest->getInt( 'unhide' ) == 1 &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'token' ), $oldid );
+ $unhide = $wgRequest->getInt( 'unhide' ) == 1;
+
# Cascade unhide param in links for easy deletion browsing
$extraParams = array();
if ( $wgRequest->getVal( 'unhide' ) ) {
$extraParams['unhide'] = 1;
}
+
$revision = Revision::newFromId( $oldid );
$current = ( $oldid == $this->mLatest );
@@ -3496,6 +3824,7 @@ class Article {
);
$cdel = '';
+
// User can delete revisions or view deleted revisions...
$canHide = $wgUser->isAllowed( 'deleterevision' );
if ( $canHide || ( $revision->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) ) {
@@ -3534,6 +3863,7 @@ class Article {
"</div>\n" .
"\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ),
$prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
+
$wgOut->setSubtitle( $r );
}
@@ -3541,10 +3871,13 @@ class Article {
* This function is called right before saving the wikitext,
* so we can do things like signatures and links-in-context.
*
- * @param $text String
+ * @param $text String article contents
+ * @return string article contents with altered wikitext markup (signatures
+ * converted, {{subst:}}, templates, etc.)
*/
public function preSaveTransform( $text ) {
global $wgParser, $wgUser;
+
return $wgParser->preSaveTransform( $text, $this->mTitle, $wgUser, ParserOptions::newFromUser( $wgUser ) );
}
@@ -3554,13 +3887,17 @@ class Article {
* checkLastModified returns true if it has taken care of all
* output to the client that is necessary for this request.
* (that is, it has sent a cached version of the page)
+ *
+ * @return boolean true if cached version send, false otherwise
*/
protected function tryFileCache() {
static $called = false;
+
if ( $called ) {
wfDebug( "Article::tryFileCache(): called twice!?\n" );
return false;
}
+
$called = true;
if ( $this->isFileCacheable() ) {
$cache = new HTMLFileCache( $this->mTitle );
@@ -3575,6 +3912,7 @@ class Article {
} else {
wfDebug( "Article::tryFileCache(): not cacheable\n" );
}
+
return false;
}
@@ -3584,45 +3922,51 @@ class Article {
*/
public function isFileCacheable() {
$cacheable = false;
+
if ( HTMLFileCache::useFileCache() ) {
- $cacheable = $this->getID() && !$this->mRedirectedFrom;
+ $cacheable = $this->getID() && !$this->mRedirectedFrom && !$this->mTitle->isRedirect();
// Extension may have reason to disable file caching on some pages.
if ( $cacheable ) {
$cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
}
}
+
return $cacheable;
}
/**
* Loads page_touched and returns a value indicating if it should be used
- *
+ * @return boolean true if not a redirect
*/
public function checkTouched() {
if ( !$this->mDataLoaded ) {
$this->loadPageData();
}
+
return !$this->mIsRedirect;
}
/**
* Get the page_touched field
+ * @return string containing GMT timestamp
*/
public function getTouched() {
- # Ensure that page data has been loaded
if ( !$this->mDataLoaded ) {
$this->loadPageData();
}
+
return $this->mTouched;
}
/**
* Get the page_latest field
+ * @return integer rev_id of current revision
*/
public function getLatest() {
if ( !$this->mDataLoaded ) {
$this->loadPageData();
}
+
return (int)$this->mLatest;
}
@@ -3648,6 +3992,7 @@ class Article {
$revision->insertOn( $dbw );
$this->updateRevisionOn( $dbw, $revision );
+ global $wgUser;
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $wgUser ) );
wfProfileOut( __METHOD__ );
@@ -3660,6 +4005,7 @@ class Article {
*/
public static function incViewCount( $id ) {
$id = intval( $id );
+
global $wgHitcounterUpdateFreq;
$dbw = wfGetDB( DB_MASTER );
@@ -3670,6 +4016,7 @@ class Article {
if ( $wgHitcounterUpdateFreq <= 1 || $dbType == 'sqlite' ) {
$dbw->query( "UPDATE $pageTable SET page_counter = page_counter + 1 WHERE page_id = $id" );
+
return;
}
@@ -3682,12 +4029,14 @@ class Article {
if ( ( rand() % $checkfreq != 0 ) or ( $dbw->lastErrno() != 0 ) ) {
# Most of the time (or on SQL errors), skip row count check
$dbw->ignoreErrors( $oldignore );
+
return;
}
$res = $dbw->query( "SELECT COUNT(*) as n FROM $hitcounterTable" );
$row = $dbw->fetchObject( $res );
$rown = intval( $row->n );
+
if ( $rown >= $wgHitcounterUpdateFreq ) {
wfProfileIn( 'Article::incViewCount-collect' );
$old_user_abort = ignore_user_abort( true );
@@ -3699,11 +4048,11 @@ class Article {
'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 {
+ } else {
$dbw->query( "UPDATE $pageTable SET page_counter=page_counter + hc_n " .
"FROM $acchitsTable WHERE page_id = hc_id", __METHOD__ );
}
@@ -3712,6 +4061,7 @@ class Article {
ignore_user_abort( $old_user_abort );
wfProfileOut( 'Article::incViewCount-collect' );
}
+
$dbw->ignoreErrors( $oldignore );
}
@@ -3733,6 +4083,7 @@ class Article {
} else {
$other = $title->getTalkPage();
}
+
$other->invalidateCache();
$other->purgeSquid();
@@ -3741,14 +4092,19 @@ class Article {
$title->deleteTitleProtection();
}
+ /**
+ * Clears caches when article is deleted
+ */
public static function onArticleDelete( $title ) {
global $wgMessageCache;
+
# Update existence markers on article/talk tabs...
if ( $title->isTalkPage() ) {
$other = $title->getSubjectPage();
} else {
$other = $title->getTalkPage();
}
+
$other->invalidateCache();
$other->purgeSquid();
@@ -3762,24 +4118,30 @@ class Article {
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
$wgMessageCache->replace( $title->getDBkey(), false );
}
+
# Images
if ( $title->getNamespace() == NS_FILE ) {
$update = new HTMLCacheUpdate( $title, 'imagelinks' );
$update->doUpdate();
}
+
# User talk pages
if ( $title->getNamespace() == NS_USER_TALK ) {
$user = User::newFromName( $title->getText(), false );
$user->setNewtalk( false );
}
+
# Image redirects
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
}
/**
* Purge caches on page update etc
+ *
+ * @param $title Title object
+ * @todo: verify that $title is always a Title object (and never false or null), add Title hint to parameter $title
*/
- public static function onArticleEdit( $title, $flags = '' ) {
+ public static function onArticleEdit( $title ) {
global $wgDeferredUpdateList;
// Invalidate caches of articles which include this page
@@ -3836,6 +4198,7 @@ class Article {
: 'noarticletextanon';
$wgOut->addHTML( wfMsgExt( $msg, 'parse' ) );
}
+
$wgOut->addHTML( '</div>' );
} else {
$dbr = wfGetDB( DB_SLAVE );
@@ -3852,15 +4215,21 @@ class Article {
$pageInfo = $this->pageCountInfo( $page );
$talkInfo = $this->pageCountInfo( $page->getTalkPage() );
+
+ //FIXME: unescaped messages
$wgOut->addHTML( "<ul><li>" . wfMsg( "numwatchers", $wgLang->formatNum( $numwatchers ) ) . '</li>' );
$wgOut->addHTML( "<li>" . wfMsg( 'numedits', $wgLang->formatNum( $pageInfo['edits'] ) ) . '</li>' );
+
if ( $talkInfo ) {
$wgOut->addHTML( '<li>' . wfMsg( "numtalkedits", $wgLang->formatNum( $talkInfo['edits'] ) ) . '</li>' );
}
+
$wgOut->addHTML( '<li>' . wfMsg( "numauthors", $wgLang->formatNum( $pageInfo['authors'] ) ) . '</li>' );
+
if ( $talkInfo ) {
$wgOut->addHTML( '<li>' . wfMsg( 'numtalkauthors', $wgLang->formatNum( $talkInfo['authors'] ) ) . '</li>' );
}
+
$wgOut->addHTML( '</ul>' );
}
}
@@ -3870,13 +4239,15 @@ class Article {
* on a given page. If page does not exist, returns false.
*
* @param $title Title object
- * @return array
+ * @return mixed array or boolean false
*/
public function pageCountInfo( $title ) {
$id = $title->getArticleId();
+
if ( $id == 0 ) {
return false;
}
+
$dbr = wfGetDB( DB_SLAVE );
$rev_clause = array( 'rev_page' => $id );
$edits = $dbr->selectField(
@@ -3893,6 +4264,7 @@ class Article {
__METHOD__,
$this->getSelectOptions()
);
+
return array( 'edits' => $edits, 'authors' => $authors );
}
@@ -3905,20 +4277,23 @@ class Article {
public function getUsedTemplates() {
$result = array();
$id = $this->mTitle->getArticleID();
+
if ( $id == 0 ) {
return array();
}
+
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( array( 'templatelinks' ),
array( 'tl_namespace', 'tl_title' ),
array( 'tl_from' => $id ),
__METHOD__ );
+
if ( $res !== false ) {
foreach ( $res as $row ) {
$result[] = Title::makeTitle( $row->tl_namespace, $row->tl_title );
}
}
- $dbr->freeResult( $res );
+
return $result;
}
@@ -3931,21 +4306,24 @@ class Article {
public function getHiddenCategories() {
$result = array();
$id = $this->mTitle->getArticleID();
+
if ( $id == 0 ) {
return array();
}
+
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
array( 'cl_to' ),
array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
'page_namespace' => NS_CATEGORY, 'page_title=cl_to' ),
__METHOD__ );
+
if ( $res !== false ) {
foreach ( $res as $row ) {
$result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
}
}
- $dbr->freeResult( $res );
+
return $result;
}
@@ -3957,11 +4335,14 @@ class Article {
* @return string An appropriate autosummary, or an empty string.
*/
public static function getAutosummary( $oldtext, $newtext, $flags ) {
+ global $wgContLang;
+
# Decide what kind of autosummary is needed.
# Redirect autosummaries
$ot = Title::newFromRedirect( $oldtext );
$rt = Title::newFromRedirect( $newtext );
+
if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
return wfMsgForContent( 'autoredircomment', $rt->getFullText() );
}
@@ -3969,10 +4350,11 @@ class Article {
# New page autosummaries
if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
# If they're making a new article, give its text, truncated, in the summary.
- global $wgContLang;
+
$truncatedtext = $wgContLang->truncate(
str_replace( "\n", ' ', $newtext ),
max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
+
return wfMsgForContent( 'autosumm-new', $truncatedtext );
}
@@ -3981,10 +4363,11 @@ class Article {
return wfMsgForContent( 'autosumm-blank' );
} elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
# Removing more than 90% of the article
- global $wgContLang;
+
$truncatedtext = $wgContLang->truncate(
$newtext,
max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
+
return wfMsgForContent( 'autosumm-replace', $truncatedtext );
}
@@ -4000,6 +4383,7 @@ class Article {
*
* @param $text String
* @param $cache Boolean
+ * @param $parserOptions mixed ParserOptions object, or boolean false
*/
public function outputWikiText( $text, $cache = true, $parserOptions = false ) {
global $wgOut;
@@ -4012,9 +4396,14 @@ class Article {
* This does all the heavy lifting for outputWikitext, except it returns the parser
* output instead of sending it straight to $wgOut. Makes things nice and simple for,
* say, embedding thread pages within a discussion system (LiquidThreads)
+ *
+ * @param $text string
+ * @param $cache boolean
+ * @param $parserOptions parsing options, defaults to false
+ * @return string containing parsed output
*/
public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) {
- global $wgParser, $wgOut, $wgEnableParserCache, $wgUseFileCache;
+ global $wgParser, $wgEnableParserCache, $wgUseFileCache;
if ( !$parserOptions ) {
$parserOptions = $this->getParserOptions();
@@ -4031,33 +4420,46 @@ class Article {
$this->mTitle->getPrefixedDBkey() ) );
}
- if ( $wgEnableParserCache && $cache && $this && $this->mParserOutput->getCacheTime() != -1 ) {
+ if ( $wgEnableParserCache && $cache && $this->mParserOutput->isCacheable() ) {
$parserCache = ParserCache::singleton();
$parserCache->save( $this->mParserOutput, $this, $parserOptions );
}
+
// 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->mParserOutput->getCacheTime() == -1 || $this->mParserOutput->containsOldMagic() ) {
+ if ( !$this->mParserOutput->isCacheable() || $this->mParserOutput->containsOldMagic() ) {
$wgUseFileCache = false;
}
+
$this->doCascadeProtectionUpdates( $this->mParserOutput );
+
return $this->mParserOutput;
}
/**
* Get parser options suitable for rendering the primary article wikitext
+ * @return mixed ParserOptions object or boolean false
*/
public function getParserOptions() {
global $wgUser;
+
if ( !$this->mParserOptions ) {
$this->mParserOptions = new ParserOptions( $wgUser );
$this->mParserOptions->setTidy( true );
$this->mParserOptions->enableLimitReport();
}
- return $this->mParserOptions;
+
+ // Clone to allow modifications of the return value without affecting
+ // the cache
+ return clone $this->mParserOptions;
}
+ /**
+ * Updates cascading protections
+ *
+ * @param $parserOutput mixed ParserOptions object, or boolean false
+ **/
protected function doCascadeProtectionUpdates( $parserOutput ) {
if ( !$this->isCurrent() || wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
return;
@@ -4079,9 +4481,9 @@ class Article {
$res = $dbr->select( array( 'templatelinks' ),
array( 'tl_namespace', 'tl_title' ),
array( 'tl_from' => $id ),
- __METHOD__ );
+ __METHOD__
+ );
- global $wgContLang;
foreach ( $res as $row ) {
$tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
}
@@ -4095,7 +4497,6 @@ class Article {
}
# Get the diff
- # Note that we simulate array_diff_key in PHP <5.0.x
$templates_diff = array_diff_key( $poTemplates, $tlTemplates );
if ( count( $templates_diff ) > 0 ) {
@@ -4111,7 +4512,6 @@ class Article {
*
* @param $added array The names of categories that were added
* @param $deleted array The names of categories that were deleted
- * @return null
*/
public function updateCategoryCounts( $added, $deleted ) {
$ns = $this->mTitle->getNamespace();
@@ -4128,7 +4528,9 @@ class Article {
# Okay, nothing to do
return;
}
+
$insertRows = array();
+
foreach ( $insertCats as $cat ) {
$insertRows[] = array(
'cat_id' => $dbw->nextSequenceValue( 'category_cat_id_seq' ),
@@ -4139,6 +4541,7 @@ class Article {
$addFields = array( 'cat_pages = cat_pages + 1' );
$removeFields = array( 'cat_pages = cat_pages - 1' );
+
if ( $ns == NS_CATEGORY ) {
$addFields[] = 'cat_subcats = cat_subcats + 1';
$removeFields[] = 'cat_subcats = cat_subcats - 1';
@@ -4155,6 +4558,7 @@ class Article {
__METHOD__
);
}
+
if ( $deleted ) {
$dbw->update(
'category',
@@ -4165,21 +4569,27 @@ class Article {
}
}
- /** Lightweight method to get the parser output for a page, checking the parser cache
+ /**
+ * Lightweight method to get the parser output for a page, checking the parser cache
* and so on. Doesn't consider most of the stuff that Article::view is forced to
* consider, so it's not appropriate to use there.
+ *
+ * @since 1.16 (r52326) for LiquidThreads
+ *
+ * @param $oldid mixed integer Revision ID or null
*/
- function getParserOutput( $oldid = null ) {
- global $wgEnableParserCache, $wgUser, $wgOut;
+ public function getParserOutput( $oldid = null ) {
+ global $wgEnableParserCache, $wgUser;
// Should the parser cache be used?
$useParserCache = $wgEnableParserCache &&
- intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 &&
- $this->exists() &&
- $oldid === null;
+ $wgUser->getStubThreshold() == 0 &&
+ $this->exists() &&
+ $oldid === null;
wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
- if ( $wgUser->getOption( 'stubthreshold' ) ) {
+
+ if ( $wgUser->getStubThreshold() ) {
wfIncrStats( 'pcache_miss_stub' );
}
@@ -4197,4 +4607,66 @@ class Article {
return $parserOutput;
}
}
+
+ // Deprecated methods
+ /**
+ * Get the database which should be used for reads
+ *
+ * @return Database
+ * @deprecated - just call wfGetDB( DB_MASTER ) instead
+ */
+ function getDB() {
+ wfDeprecated( __METHOD__ );
+ return wfGetDB( DB_MASTER );
+ }
+
+}
+
+class PoolWorkArticleView extends PoolCounterWork {
+ private $mArticle;
+
+ function __construct( $article, $key, $useParserCache, $parserOptions ) {
+ parent::__construct( 'ArticleView', $key );
+ $this->mArticle = $article;
+ $this->cacheable = $useParserCache;
+ $this->parserOptions = $parserOptions;
+ }
+
+ function doWork() {
+ return $this->mArticle->doViewParse();
+ }
+
+ function getCachedWork() {
+ global $wgOut;
+
+ $parserCache = ParserCache::singleton();
+ $this->mArticle->mParserOutput = $parserCache->get( $this->mArticle, $this->parserOptions );
+
+ if ( $this->mArticle->mParserOutput !== false ) {
+ wfDebug( __METHOD__ . ": showing contents parsed by someone else\n" );
+ $wgOut->addParserOutput( $this->mArticle->mParserOutput );
+ # Ensure that UI elements requiring revision ID have
+ # the correct version information.
+ $wgOut->setRevisionId( $this->mArticle->getLatest() );
+ return true;
+ }
+ return false;
+ }
+
+ function fallback() {
+ return $this->mArticle->tryDirtyCache();
+ }
+
+ function error( $status ) {
+ global $wgOut;
+
+ $wgOut->clearHTML(); // for release() errors
+ $wgOut->enableClientCache( false );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
+
+ $errortext = $status->getWikiText( false, 'view-pool-error' );
+ $wgOut->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
+
+ return false;
+ }
}
diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php
index 87ac8adb..7dc99259 100644
--- a/includes/AuthPlugin.php
+++ b/includes/AuthPlugin.php
@@ -1,23 +1,27 @@
<?php
/**
+ * Authentication plugin interface
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
/**
* Authentication plugin interface. Instantiate a subclass of AuthPlugin
@@ -105,7 +109,6 @@ class AuthPlugin {
return true;
}
-
/**
* Return true if the wiki should create a new local account automatically
* when asked to login a user who doesn't exist locally but does in the
@@ -131,11 +134,11 @@ class AuthPlugin {
* @return Boolean
*/
public function allowPropChange( $prop = '' ) {
- if( $prop == 'realname' && is_callable( array( $this, 'allowRealNameChange' ) ) ) {
+ if ( $prop == 'realname' && is_callable( array( $this, 'allowRealNameChange' ) ) ) {
return $this->allowRealNameChange();
- } elseif( $prop == 'emailaddress' && is_callable( array( $this, 'allowEmailChange' ) ) ) {
+ } elseif ( $prop == 'emailaddress' && is_callable( array( $this, 'allowEmailChange' ) ) ) {
return $this->allowEmailChange();
- } elseif( $prop == 'nickname' && is_callable( array( $this, 'allowNickChange' ) ) ) {
+ } elseif ( $prop == 'nickname' && is_callable( array( $this, 'allowNickChange' ) ) ) {
return $this->allowNickChange();
} else {
return true;
@@ -197,11 +200,10 @@ class AuthPlugin {
* @param $realname String
* @return Boolean
*/
- public function addUser( $user, $password, $email='', $realname='' ) {
+ public function addUser( $user, $password, $email = '', $realname = '' ) {
return true;
}
-
/**
* Return true to prevent logins that don't authenticate here from being
* checked against the local database's password fields.
@@ -236,7 +238,7 @@ class AuthPlugin {
* @param $user User object.
* @param $autocreate Boolean: True if user is being autocreated on login
*/
- public function initUser( &$user, $autocreate=false ) {
+ public function initUser( &$user, $autocreate = false ) {
# Override this to do something.
}
@@ -247,7 +249,7 @@ class AuthPlugin {
public function getCanonicalName( $username ) {
return $username;
}
-
+
/**
* Get an instance of a User object
*
@@ -262,22 +264,22 @@ class AuthPluginUser {
function __construct( $user ) {
# Override this!
}
-
+
public function getId() {
# Override this!
return -1;
}
-
+
public function isLocked() {
# Override this!
return false;
}
-
+
public function isHidden() {
# Override this!
return false;
}
-
+
public function resetAuthToken() {
# Override this!
return true;
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index cecb53f9..f1605a56 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -1,10 +1,17 @@
<?php
-/* This defines autoloading handler for whole MediaWiki framework */
+/**
+ * This defines autoloading handler for whole MediaWiki framework
+ *
+ * @file
+ */
-# Locations of core classes
-# 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
+/**
+ * Locations of core classes
+ * 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',
@@ -20,6 +27,7 @@ $wgAutoloadLocalClasses = array(
'BagOStuff' => 'includes/BagOStuff.php',
'Block' => 'includes/Block.php',
'CacheDependency' => 'includes/CacheDependency.php',
+ 'CacheTime' => 'includes/parser/ParserOutput.php',
'Category' => 'includes/Category.php',
'Categoryfinder' => 'includes/Categoryfinder.php',
'CategoryPage' => 'includes/CategoryPage.php',
@@ -35,6 +43,7 @@ $wgAutoloadLocalClasses = array(
'ChangesFeed' => 'includes/ChangesFeed.php',
'ChangeTags' => 'includes/ChangeTags.php',
'ChannelFeed' => 'includes/Feed.php',
+ 'Collation' => 'includes/Collation.php',
'Cookie' => 'includes/HttpFunctions.php',
'CookieJar' => 'includes/HttpFunctions.php',
'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
@@ -44,12 +53,13 @@ $wgAutoloadLocalClasses = array(
'ConstantDependency' => 'includes/CacheDependency.php',
'CreativeCommonsRdf' => 'includes/Metadata.php',
'Credits' => 'includes/Credits.php',
+ 'CSSJanus' => 'includes/libs/CSSJanus.php',
+ 'CSSMin' => 'includes/libs/CSSMin.php',
'DBABagOStuff' => 'includes/BagOStuff.php',
'DependencyWrapper' => 'includes/CacheDependency.php',
'DiffHistoryBlob' => 'includes/HistoryBlob.php',
'DjVuImage' => 'includes/DjVuImage.php',
'DoubleReplacer' => 'includes/StringUtils.php',
- 'DoubleRedirectJob' => 'includes/DoubleRedirectJob.php',
'DublinCoreRdf' => 'includes/Metadata.php',
'Dump7ZipOutput' => 'includes/Export.php',
'DumpBZip2Output' => 'includes/Export.php',
@@ -64,10 +74,8 @@ $wgAutoloadLocalClasses = array(
'DumpPipeOutput' => 'includes/Export.php',
'eAccelBagOStuff' => 'includes/BagOStuff.php',
'EditPage' => 'includes/EditPage.php',
- 'EmaillingJob' => 'includes/EmaillingJob.php',
'EmailNotification' => 'includes/UserMailer.php',
'EnhancedChangesList' => 'includes/ChangesList.php',
- 'EnotifNotifyJob' => 'includes/EnotifNotifyJob.php',
'ErrorPageError' => 'includes/Exception.php',
'Exif' => 'includes/Exif.php',
'ExplodeIterator' => 'includes/StringUtils.php',
@@ -76,9 +84,6 @@ $wgAutoloadLocalClasses = array(
'ExternalStoreHttp' => 'includes/ExternalStoreHttp.php',
'ExternalStore' => 'includes/ExternalStore.php',
'ExternalUser' => 'includes/ExternalUser.php',
- 'ExternalUser_Hardcoded' => 'includes/extauth/Hardcoded.php',
- 'ExternalUser_MediaWiki' => 'includes/extauth/MediaWiki.php',
- 'ExternalUser_vB' => 'includes/extauth/vB.php',
'FatalError' => 'includes/Exception.php',
'FakeTitle' => 'includes/FakeTitle.php',
'FakeMemCachedClient' => 'includes/ObjectCache.php',
@@ -92,8 +97,6 @@ $wgAutoloadLocalClasses = array(
'ForkController' => 'includes/ForkController.php',
'FormatExif' => 'includes/Exif.php',
'FormOptions' => 'includes/FormOptions.php',
- 'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php',
- 'GIFHandler' => 'includes/media/GIF.php',
'GlobalDependency' => 'includes/CacheDependency.php',
'HashBagOStuff' => 'includes/BagOStuff.php',
'HashtableReplacer' => 'includes/StringUtils.php',
@@ -122,8 +125,8 @@ $wgAutoloadLocalClasses = array(
'HTMLRadioField' => 'includes/HTMLForm.php',
'HTMLInfoField' => 'includes/HTMLForm.php',
'Http' => 'includes/HttpFunctions.php',
- 'HttpRequest' => 'includes/HttpFunctions.php',
- 'IEContentAnalyzer' => 'includes/IEContentAnalyzer.php',
+ 'HttpRequest' => 'includes/HttpFunctions.old.php',
+ 'IcuCollation' => 'includes/Collation.php',
'ImageGallery' => 'includes/ImageGallery.php',
'ImageHistoryList' => 'includes/ImagePage.php',
'ImageHistoryPseudoPager' => 'includes/ImagePage.php',
@@ -133,8 +136,7 @@ $wgAutoloadLocalClasses = array(
'IndexPager' => 'includes/Pager.php',
'Interwiki' => 'includes/Interwiki.php',
'IP' => 'includes/IP.php',
- 'Job' => 'includes/JobQueue.php',
- 'JSMin' => 'includes/JSMin.php',
+ 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php',
'LCStore_DB' => 'includes/LocalisationCache.php',
'LCStore_CDB' => 'includes/LocalisationCache.php',
'LCStore_Null' => 'includes/LocalisationCache.php',
@@ -157,18 +159,18 @@ $wgAutoloadLocalClasses = array(
'MagicWord' => 'includes/MagicWord.php',
'MailAddress' => 'includes/UserMailer.php',
'MathRenderer' => 'includes/Math.php',
- 'MediaTransformError' => 'includes/MediaTransformOutput.php',
- 'MediaTransformOutput' => 'includes/MediaTransformOutput.php',
'MediaWikiBagOStuff' => 'includes/BagOStuff.php',
'MediaWiki_I18N' => 'includes/SkinTemplate.php',
'MediaWiki' => 'includes/Wiki.php',
'MemCachedClientforWiki' => 'includes/memcached-client.php',
+ 'Message' => 'includes/Message.php',
+ 'MessageBlobStore' => 'includes/MessageBlobStore.php',
'MessageCache' => 'includes/MessageCache.php',
'MimeMagic' => 'includes/MimeMagic.php',
'MWException' => 'includes/Exception.php',
+ 'MWHttpRequest' => 'includes/HttpFunctions.php',
'MWMemcached' => 'includes/memcached-client.php',
'MWNamespace' => 'includes/Namespace.php',
- 'Namespace' => 'includes/NamespaceCompat.php', // Compat
'OldChangesList' => 'includes/ChangesList.php',
'OutputPage' => 'includes/OutputPage.php',
'PageQueryPage' => 'includes/PageQueryPage.php',
@@ -177,8 +179,10 @@ $wgAutoloadLocalClasses = array(
'Pager' => 'includes/Pager.php',
'PasswordError' => 'includes/User.php',
'PatrolLog' => 'includes/PatrolLog.php',
+ 'PhpHttpRequest' => 'includes/HttpFunctions.php',
'PoolCounter' => 'includes/PoolCounter.php',
'PoolCounter_Stub' => 'includes/PoolCounter.php',
+ 'PoolCounterWork' => 'includes/PoolCounter.php',
'Preferences' => 'includes/Preferences.php',
'PrefixSearch' => 'includes/PrefixSearch.php',
'Profiler' => 'includes/Profiler.php',
@@ -192,13 +196,21 @@ $wgAutoloadLocalClasses = array(
'RCCacheEntry' => 'includes/ChangesList.php',
'RdfMetaData' => 'includes/Metadata.php',
'RecentChange' => 'includes/RecentChange.php',
- 'RefreshLinksJob' => 'includes/RefreshLinksJob.php',
- 'RefreshLinksJob2' => 'includes/RefreshLinksJob.php',
'RegexlikeReplacer' => 'includes/StringUtils.php',
'ReplacementArray' => 'includes/StringUtils.php',
'Replacer' => 'includes/StringUtils.php',
+ 'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php',
+ 'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php',
+ 'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php',
+ 'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderWikiModule.php',
+ 'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php',
+ 'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
+ 'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderUserModule.php',
+ 'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderUserOptionsModule.php',
+ 'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php',
'ReverseChronologicalPager' => 'includes/Pager.php',
'Revision' => 'includes/Revision.php',
+ 'RevisionDelete' => 'includes/revisiondelete/RevisionDelete.php',
'RSSFeed' => 'includes/Feed.php',
'Sanitizer' => 'includes/Sanitizer.php',
'SiteConfiguration' => 'includes/SiteConfiguration.php',
@@ -218,24 +230,17 @@ $wgAutoloadLocalClasses = array(
'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php',
'Status' => 'includes/Status.php',
'StubContLang' => 'includes/StubObject.php',
- 'StubUser' => 'includes/StubObject.php',
'StubUserLang' => 'includes/StubObject.php',
'StubObject' => 'includes/StubObject.php',
'StringUtils' => 'includes/StringUtils.php',
'TablePager' => 'includes/Pager.php',
- 'ThumbnailImage' => 'includes/MediaTransformOutput.php',
- 'TiffHandler' => 'includes/media/Tiff.php',
'TitleDependency' => 'includes/CacheDependency.php',
'Title' => 'includes/Title.php',
'TitleArray' => 'includes/TitleArray.php',
'TitleArrayFromResult' => 'includes/TitleArray.php',
'TitleListDependency' => 'includes/CacheDependency.php',
- 'TransformParameterError' => 'includes/MediaTransformOutput.php',
'UnlistedSpecialPage' => 'includes/SpecialPage.php',
- 'UploadBase' => 'includes/upload/UploadBase.php',
- 'UploadFromStash' => 'includes/upload/UploadFromStash.php',
- 'UploadFromFile' => 'includes/upload/UploadFromFile.php',
- 'UploadFromUrl' => 'includes/upload/UploadFromUrl.php',
+ 'UppercaseCollation' => 'includes/Collation.php',
'User' => 'includes/User.php',
'UserArray' => 'includes/UserArray.php',
'UserArrayFromResult' => 'includes/UserArray.php',
@@ -245,6 +250,7 @@ $wgAutoloadLocalClasses = array(
'WatchedItem' => 'includes/WatchedItem.php',
'WatchlistEditor' => 'includes/WatchlistEditor.php',
'WebRequest' => 'includes/WebRequest.php',
+ 'WebRequestUpload' => 'includes/WebRequest.php',
'WebResponse' => 'includes/WebResponse.php',
'WikiError' => 'includes/WikiError.php',
'WikiErrorMsg' => 'includes/WikiError.php',
@@ -252,9 +258,11 @@ $wgAutoloadLocalClasses = array(
'WikiMap' => 'includes/WikiMap.php',
'WikiReference' => 'includes/WikiMap.php',
'WikiXmlError' => 'includes/WikiError.php',
+ 'WinCacheBagOStuff' => 'includes/BagOStuff.php',
'XCacheBagOStuff' => 'includes/BagOStuff.php',
'XmlDumpWriter' => 'includes/Export.php',
'Xml' => 'includes/Xml.php',
+ 'XmlJsCode' => 'includes/Xml.php',
'XmlSelect' => 'includes/Xml.php',
'XmlTypeCheck' => 'includes/XmlTypeCheck.php',
'ZhClient' => 'includes/ZhClient.php',
@@ -270,6 +278,7 @@ $wgAutoloadLocalClasses = array(
'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php',
'ApiFormatBase' => 'includes/api/ApiFormatBase.php',
'ApiFormatDbg' => 'includes/api/ApiFormatDbg.php',
+ 'ApiFormatDump' => 'includes/api/ApiFormatDump.php',
'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
@@ -292,6 +301,7 @@ $wgAutoloadLocalClasses = array(
'ApiPatrol' => 'includes/api/ApiPatrol.php',
'ApiProtect' => 'includes/api/ApiProtect.php',
'ApiPurge' => 'includes/api/ApiPurge.php',
+ 'ApiRsd' => 'includes/api/ApiRsd.php',
'ApiQuery' => 'includes/api/ApiQuery.php',
'ApiQueryAllCategories' => 'includes/api/ApiQueryAllCategories.php',
'ApiQueryAllimages' => 'includes/api/ApiQueryAllimages.php',
@@ -310,20 +320,25 @@ $wgAutoloadLocalClasses = array(
'ApiQueryDisabled' => 'includes/api/ApiQueryDisabled.php',
'ApiQueryDuplicateFiles' => 'includes/api/ApiQueryDuplicateFiles.php',
'ApiQueryExtLinksUsage' => 'includes/api/ApiQueryExtLinksUsage.php',
+ 'ApiQueryFilearchive' => 'includes/api/ApiQueryFilearchive.php',
'ApiQueryExternalLinks' => 'includes/api/ApiQueryExternalLinks.php',
'ApiQueryGeneratorBase' => 'includes/api/ApiQueryBase.php',
'ApiQueryImageInfo' => 'includes/api/ApiQueryImageInfo.php',
'ApiQueryImages' => 'includes/api/ApiQueryImages.php',
'ApiQueryInfo' => 'includes/api/ApiQueryInfo.php',
+ 'ApiQueryIWLinks' => 'includes/api/ApiQueryIWLinks.php',
+ 'ApiQueryIWBacklinks' => 'includes/api/ApiQueryIWBacklinks.php',
'ApiQueryLangLinks' => 'includes/api/ApiQueryLangLinks.php',
'ApiQueryLinks' => 'includes/api/ApiQueryLinks.php',
'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php',
+ 'ApiQueryPageProps' => 'includes/api/ApiQueryPageProps.php',
'ApiQueryProtectedTitles' => 'includes/api/ApiQueryProtectedTitles.php',
'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php',
- 'ApiQueryRecentChanges'=> 'includes/api/ApiQueryRecentChanges.php',
+ 'ApiQueryRecentChanges' => 'includes/api/ApiQueryRecentChanges.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',
'ApiQueryUserInfo' => 'includes/api/ApiQueryUserInfo.php',
'ApiQueryUsers' => 'includes/api/ApiQueryUsers.php',
@@ -337,9 +352,13 @@ $wgAutoloadLocalClasses = array(
'ApiUpload' => 'includes/api/ApiUpload.php',
'ApiWatch' => 'includes/api/ApiWatch.php',
- 'Spyc' => 'includes/api/ApiFormatYaml_spyc.php',
'UsageException' => 'includes/api/ApiMain.php',
+ # includes/extauth
+ 'ExternalUser_Hardcoded' => 'includes/extauth/Hardcoded.php',
+ 'ExternalUser_MediaWiki' => 'includes/extauth/MediaWiki.php',
+ 'ExternalUser_vB' => 'includes/extauth/vB.php',
+
# includes/json
'Services_JSON' => 'includes/json/Services_JSON.php',
'Services_JSON_Error' => 'includes/json/Services_JSON.php',
@@ -356,23 +375,26 @@ $wgAutoloadLocalClasses = array(
'DatabasePostgres' => 'includes/db/DatabasePostgres.php',
'DatabaseSqlite' => 'includes/db/DatabaseSqlite.php',
'DatabaseSqliteStandalone' => 'includes/db/DatabaseSqlite.php',
+ 'DatabaseType' => 'includes/db/Database.php',
'DBConnectionError' => 'includes/db/Database.php',
'DBError' => 'includes/db/Database.php',
'DBObject' => 'includes/db/Database.php',
'DBQueryError' => 'includes/db/Database.php',
'DBUnexpectedError' => 'includes/db/Database.php',
+ 'FakeResultWrapper' => 'includes/db/Database.php',
+ 'Field' => 'includes/db/Database.php',
'IBM_DB2Blob' => 'includes/db/DatabaseIbm_db2.php',
'LBFactory' => 'includes/db/LBFactory.php',
'LBFactory_Multi' => 'includes/db/LBFactory_Multi.php',
'LBFactory_Simple' => 'includes/db/LBFactory.php',
+ 'LBFactory_Single' => 'includes/db/LBFactory_Single.php',
'LikeMatch' => 'includes/db/Database.php',
'LoadBalancer' => 'includes/db/LoadBalancer.php',
+ 'LoadBalancer_Single' => 'includes/db/LBFactory_Single.php',
'LoadMonitor' => 'includes/db/LoadMonitor.php',
'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php',
- 'MSSQLField' => 'includes/db/DatabaseMssql.php',
- 'MySQLField' => 'includes/db/Database.php',
+ 'MySQLField' => 'includes/db/DatabaseMysql.php',
'MySQLMasterPos' => 'includes/db/DatabaseMysql.php',
- 'ORABlob' => 'includes/db/DatabaseOracle.php',
'ORAField' => 'includes/db/DatabaseOracle.php',
'ORAResult' => 'includes/db/DatabaseOracle.php',
'PostgresField' => 'includes/db/DatabasePostgres.php',
@@ -382,23 +404,23 @@ $wgAutoloadLocalClasses = array(
'IBM_DB2Field' => 'includes/db/DatabaseIbm_db2.php',
# includes/diff
- 'ArrayDiffFormatter' => 'includes/diff/DifferenceEngine.php',
- '_DiffEngine' => 'includes/diff/DifferenceEngine.php',
- 'DifferenceEngine' => 'includes/diff/DifferenceInterface.php',
- 'DiffFormatter' => 'includes/diff/DifferenceEngine.php',
- 'Diff' => 'includes/diff/DifferenceEngine.php',
- '_DiffOp_Add' => 'includes/diff/DifferenceEngine.php',
- '_DiffOp_Change' => 'includes/diff/DifferenceEngine.php',
- '_DiffOp_Copy' => 'includes/diff/DifferenceEngine.php',
- '_DiffOp_Delete' => 'includes/diff/DifferenceEngine.php',
- '_DiffOp' => 'includes/diff/DifferenceEngine.php',
- '_HWLDF_WordAccumulator' => 'includes/diff/DifferenceEngine.php',
- 'MappedDiff' => 'includes/diff/DifferenceEngine.php',
- 'RangeDifference' => 'includes/diff/Diff.php',
- 'TableDiffFormatter' => 'includes/diff/DifferenceEngine.php',
- 'UnifiedDiffFormatter' => 'includes/diff/DifferenceEngine.php',
- 'WikiDiff3' => 'includes/diff/Diff.php',
- 'WordLevelDiff' => 'includes/diff/DifferenceEngine.php',
+ 'ArrayDiffFormatter' => 'includes/diff/WikiDiff.php',
+ '_DiffEngine' => 'includes/diff/WikiDiff.php',
+ 'DifferenceEngine' => 'includes/diff/DifferenceEngine.php',
+ 'DiffFormatter' => 'includes/diff/WikiDiff.php',
+ 'Diff' => 'includes/diff/WikiDiff.php',
+ '_DiffOp_Add' => 'includes/diff/WikiDiff.php',
+ '_DiffOp_Change' => 'includes/diff/WikiDiff.php',
+ '_DiffOp_Copy' => 'includes/diff/WikiDiff.php',
+ '_DiffOp_Delete' => 'includes/diff/WikiDiff.php',
+ '_DiffOp' => 'includes/diff/WikiDiff.php',
+ '_HWLDF_WordAccumulator' => 'includes/diff/WikiDiff.php',
+ 'MappedDiff' => 'includes/diff/WikiDiff.php',
+ 'RangeDifference' => 'includes/diff/WikiDiff3.php',
+ 'TableDiffFormatter' => 'includes/diff/WikiDiff.php',
+ 'UnifiedDiffFormatter' => 'includes/diff/WikiDiff.php',
+ 'WikiDiff3' => 'includes/diff/WikiDiff3.php',
+ 'WordLevelDiff' => 'includes/diff/WikiDiff.php',
# includes/filerepo
'ArchivedFile' => 'includes/filerepo/ArchivedFile.php',
@@ -421,14 +443,59 @@ $wgAutoloadLocalClasses = array(
'RepoGroup' => 'includes/filerepo/RepoGroup.php',
'UnregisteredLocalFile' => 'includes/filerepo/UnregisteredLocalFile.php',
+ # includes/installer
+ 'CliInstaller' => 'includes/installer/CliInstaller.php',
+ 'Installer' => 'includes/installer/Installer.php',
+ 'DatabaseInstaller' => 'includes/installer/DatabaseInstaller.php',
+ 'DatabaseUpdater' => 'includes/installer/DatabaseUpdater.php',
+ 'LBFactory_InstallerFake' => 'includes/installer/Installer.php',
+ 'LocalSettingsGenerator' => 'includes/installer/LocalSettingsGenerator.php',
+ 'WebInstaller' => 'includes/installer/WebInstaller.php',
+ 'WebInstallerPage' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerOutput' => 'includes/installer/WebInstallerOutput.php',
+ 'MysqlInstaller' => 'includes/installer/MysqlInstaller.php',
+ 'MysqlUpdater' => 'includes/installer/MysqlUpdater.php',
+ 'PhpXmlBugTester' => 'includes/installer/PhpBugTests.php',
+ 'PhpRefCallBugTester' => 'includes/installer/PhpBugTests.php',
+ 'PostgresInstaller' => 'includes/installer/PostgresInstaller.php',
+ 'PostgresUpdater' => 'includes/installer/PostgresUpdater.php',
+ 'SqliteInstaller' => 'includes/installer/SqliteInstaller.php',
+ 'SqliteUpdater' => 'includes/installer/SqliteUpdater.php',
+ 'OracleInstaller' => 'includes/installer/OracleInstaller.php',
+ 'OracleUpdater' => 'includes/installer/OracleUpdater.php',
+
+ # includes/job
+ 'DoubleRedirectJob' => 'includes/job/DoubleRedirectJob.php',
+ 'EmaillingJob' => 'includes/job/EmaillingJob.php',
+ 'EnotifNotifyJob' => 'includes/job/EnotifNotifyJob.php',
+ 'Job' => 'includes/job/JobQueue.php',
+ 'RefreshLinksJob' => 'includes/job/RefreshLinksJob.php',
+ 'RefreshLinksJob2' => 'includes/job/RefreshLinksJob.php',
+ 'UploadFromUrlJob' => 'includes/job/UploadFromUrlJob.php',
+
+ # includes/libs
+ 'IEContentAnalyzer' => 'includes/libs/IEContentAnalyzer.php',
+ 'IEUrlExtension' => 'includes/libs/IEUrlExtension.php',
+ 'Spyc' => 'includes/libs/spyc.php',
+
# includes/media
'BitmapHandler' => 'includes/media/Bitmap.php',
'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php',
'BmpHandler' => 'includes/media/BMP.php',
'DjVuHandler' => 'includes/media/DjVu.php',
+ 'GIFHandler' => 'includes/media/GIF.php',
+ 'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php',
'ImageHandler' => 'includes/media/Generic.php',
'MediaHandler' => 'includes/media/Generic.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',
+ 'ThumbnailImage' => 'includes/media/MediaTransformOutput.php',
+ 'TiffHandler' => 'includes/media/Tiff.php',
+ 'TransformParameterError' => 'includes/media/MediaTransformOutput.php',
# includes/normal
'UtfNormal' => 'includes/normal/UtfNormal.php',
@@ -481,7 +548,7 @@ $wgAutoloadLocalClasses = array(
'SearchEngine' => 'includes/search/SearchEngine.php',
'SearchHighlighter' => 'includes/search/SearchEngine.php',
'SearchIBM_DB2' => 'includes/search/SearchIBM_DB2.php',
- 'SearchMySQL4' => 'includes/search/SearchMySQL4.php',
+ 'SearchMssql' => 'includes/search/SearchMssql.php',
'SearchMySQL' => 'includes/search/SearchMySQL.php',
'SearchOracle' => 'includes/search/SearchOracle.php',
'SearchPostgres' => 'includes/search/SearchPostgres.php',
@@ -510,8 +577,7 @@ $wgAutoloadLocalClasses = array(
'DoubleRedirectsPage' => 'includes/specials/SpecialDoubleRedirects.php',
'EmailConfirmation' => 'includes/specials/SpecialConfirmemail.php',
'EmailInvalidation' => 'includes/specials/SpecialConfirmemail.php',
- 'EmailUserForm' => 'includes/specials/SpecialEmailuser.php',
- 'FakeResultWrapper' => 'includes/specials/SpecialAllmessages.php',
+ 'SpecialEmailUser' => 'includes/specials/SpecialEmailuser.php',
'FewestrevisionsPage' => 'includes/specials/SpecialFewestrevisions.php',
'FileDuplicateSearchPage' => 'includes/specials/SpecialFileDuplicateSearch.php',
'IPBlockForm' => 'includes/specials/SpecialBlockip.php',
@@ -541,35 +607,50 @@ $wgAutoloadLocalClasses = array(
'PreferencesForm' => 'includes/Preferences.php',
'RandomPage' => 'includes/specials/SpecialRandompage.php',
'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevisionDeleter' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_RevisionList' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_RevisionItem' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_ArchiveList' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_ArchiveItem' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_FileList' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_FileItem' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_ArchivedFileList' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_ArchivedFileItem' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_LogList' => 'includes/specials/SpecialRevisiondelete.php',
- 'RevDel_LogItem' => 'includes/specials/SpecialRevisiondelete.php',
+ 'RevisionDeleter' => 'includes/revisiondelete/RevisionDeleter.php',
+ 'RevDel_List' => 'includes/revisiondelete/RevisionDeleteAbstracts.php',
+ 'RevDel_Item' => 'includes/revisiondelete/RevisionDeleteAbstracts.php',
+ 'RevDel_RevisionList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_RevisionItem' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_ArchiveList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_ArchiveItem' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_FileList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_FileItem' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_ArchivedFileList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_ArchivedFileItem' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_LogList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_LogItem' => 'includes/revisiondelete/RevisionDelete.php',
'ShortPagesPage' => 'includes/specials/SpecialShortpages.php',
'SpecialActiveUsers' => 'includes/specials/SpecialActiveusers.php',
'SpecialAllpages' => 'includes/specials/SpecialAllpages.php',
'SpecialBlankpage' => 'includes/specials/SpecialBlankpage.php',
+ 'SpecialBlockme' => 'includes/specials/SpecialBlockme.php',
'SpecialBookSources' => 'includes/specials/SpecialBooksources.php',
+ 'SpecialCategories' => 'includes/specials/SpecialCategories.php',
+ 'SpecialComparePages' => 'includes/specials/SpecialComparePages.php',
'SpecialExport' => 'includes/specials/SpecialExport.php',
+ 'SpecialFilepath' => 'includes/specials/SpecialFilepath.php',
'SpecialImport' => 'includes/specials/SpecialImport.php',
'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php',
+ 'SpecialLockdb' => 'includes/specials/SpecialLockdb.php',
+ 'SpecialLog' => 'includes/specials/SpecialLog.php',
+ 'SpecialMergeHistory' => 'includes/specials/SpecialMergeHistory.php',
'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php',
'SpecialPreferences' => 'includes/specials/SpecialPreferences.php',
'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php',
+ 'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php',
+ 'SpecialProtectedtitles' => 'includes/specials/SpecialProtectedtitles.php',
'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php',
'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php',
'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php',
'SpecialSearch' => 'includes/specials/SpecialSearch.php',
+ 'SpecialUploadStash' => 'includes/specials/SpecialUploadStash.php',
+ 'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php',
'SpecialStatistics' => 'includes/specials/SpecialStatistics.php',
'SpecialTags' => 'includes/specials/SpecialTags.php',
+ 'SpecialUnlockdb' => 'includes/specials/SpecialUnlockdb.php',
'SpecialUpload' => 'includes/specials/SpecialUpload.php',
+ 'SpecialUserlogout' => 'includes/specials/SpecialUserlogout.php',
'SpecialVersion' => 'includes/specials/SpecialVersion.php',
'SpecialWhatlinkshere' => 'includes/specials/SpecialWhatlinkshere.php',
'SpecialWhatLinksHere' => 'includes/specials/SpecialWhatlinkshere.php',
@@ -598,18 +679,66 @@ $wgAutoloadLocalClasses = array(
'UsercreateTemplate' => 'includes/templates/Userlogin.php',
'UserloginTemplate' => 'includes/templates/Userlogin.php',
+ # includes/upload
+ 'UploadBase' => 'includes/upload/UploadBase.php',
+ 'UploadFromStash' => 'includes/upload/UploadFromStash.php',
+ 'UploadFromFile' => 'includes/upload/UploadFromFile.php',
+ 'UploadFromUrl' => 'includes/upload/UploadFromUrl.php',
+ 'UploadStash' => 'includes/upload/UploadStash.php',
+ 'UploadStashFile' => 'includes/upload/UploadStash.php',
+ 'UploadStashNotAvailableException' => 'includes/upload/UploadStash.php',
+ 'UploadStashFileNotFoundException' => 'includes/upload/UploadStash.php',
+ 'UploadStashBadPathException' => 'includes/upload/UploadStash.php',
+ 'UploadStashBadVersionException' => 'includes/upload/UploadStash.php',
+ 'UploadStashFileException' => 'includes/upload/UploadStash.php',
+ 'UploadStashZeroLengthFileException' => 'includes/upload/UploadStash.php',
+
# languages
'Language' => 'languages/Language.php',
'FakeConverter' => 'languages/Language.php',
'LanguageConverter' => 'languages/LanguageConverter.php',
+ # maintenance
+ 'AnsiTermColorer' => 'maintenance/tests/testHelpers.inc',
+ 'ConvertLinks' => 'maintenance/convertLinks.php',
+ 'DbTestPreviewer' => 'maintenance/tests/testHelpers.inc',
+ 'DbTestRecorder' => 'maintenance/tests/testHelpers.inc',
+ 'DeleteArchivedFilesImplementation' => 'maintenance/deleteArchivedFiles.inc',
+ 'DeleteArchivedRevisionsImplementation' => 'maintenance/deleteArchivedRevisions.inc',
+ 'DeleteDefaultMessages' => 'maintenance/deleteDefaultMessages.php',
+ 'DummyTermColorer' => 'maintenance/tests/testHelpers.inc',
+ 'ParserTest' => 'maintenance/tests/parser/parserTest.inc',
+ 'ParserTestParserHook' => 'maintenance/tests/parser/parserTestsParserHook.php',
+ 'ParserTestStaticParserHook' => 'maintenance/tests/parser/parserTestsStaticParserHook.php',
+ 'PopulateCategory' => 'maintenance/populateCategory.php',
+ 'PopulateLogSearch' => 'maintenance/populateLogSearch.php',
+ 'PopulateLogUsertext' => 'maintenance/populateLogUsertext.php',
+ 'PopulateParentId' => 'maintenance/populateParentId.php',
+ 'PopulateRevisionLength' => 'maintenance/populateRevisionLength.php',
+ 'RemoteTestRecorder' => 'maintenance/tests/testHelpers.inc',
+ 'SevenZipStream' => 'maintenance/7zip.inc',
+ 'Sqlite' => 'maintenance/sqlite.inc',
+ 'TestFileIterator' => 'maintenance/tests/testHelpers.inc',
+ 'TestRecorder' => 'maintenance/tests/testHelpers.inc',
+ 'UpdateCollation' => 'maintenance/updateCollation.php',
+ 'UpdateRestrictions' => 'maintenance/updateRestrictions.php',
+ 'UserDupes' => 'maintenance/userDupes.inc',
+
+ # maintenance/tests/selenium
+ 'Selenium' => 'maintenance/tests/selenium/Selenium.php',
+ 'SeleniumLoader' => 'maintenance/tests/selenium/SeleniumLoader.php',
+ 'SeleniumTestCase' => 'maintenance/tests/selenium/SeleniumTestCase.php',
+ 'SeleniumTestConsoleLogger' => 'maintenance/tests/selenium/SeleniumTestConsoleLogger.php',
+ 'SeleniumTestHTMLLogger' => 'maintenance/tests/selenium/SeleniumTestHTMLLogger.php',
+ 'SeleniumTestListener' => 'maintenance/tests/selenium/SeleniumTestListener.php',
+ 'SeleniumTestSuite' => 'maintenance/tests/selenium/SeleniumTestSuite.php',
+ 'SeleniumConfig' => 'maintenance/tests/selenium/SeleniumConfig.php',
+
# maintenance/language
+ 'csvStatsOutput' => 'maintenance/language/StatOutputs.php',
'statsOutput' => 'maintenance/language/StatOutputs.php',
- 'wikiStatsOutput' => 'maintenance/language/StatOutputs.php',
'textStatsOutput' => 'maintenance/language/StatOutputs.php',
- 'csvStatsOutput' => 'maintenance/language/StatOutputs.php',
- 'SevenZipStream' => 'maintenance/7zip.inc',
-
+ 'wikiStatsOutput' => 'maintenance/language/StatOutputs.php',
);
class AutoLoader {
@@ -633,14 +762,18 @@ class AutoLoader {
# The case can sometimes be wrong when unserializing PHP 4 objects
$filename = false;
$lowerClass = strtolower( $className );
+
foreach ( $wgAutoloadLocalClasses as $class2 => $file2 ) {
if ( strtolower( $class2 ) == $lowerClass ) {
$filename = $file2;
}
}
+
if ( !$filename ) {
- if( function_exists( 'wfDebug' ) )
+ if ( function_exists( 'wfDebug' ) ) {
wfDebug( "Class {$className} not found; skipped loading\n" );
+ }
+
# Give up
return false;
}
@@ -651,15 +784,17 @@ class AutoLoader {
global $IP;
$filename = "$IP/$filename";
}
+
require( $filename );
+
return true;
}
static function loadAllExtensions() {
global $wgAutoloadClasses;
- foreach( $wgAutoloadClasses as $class => $file ) {
- if( !( class_exists( $class, false ) || interface_exists( $class, false ) ) ) {
+ foreach ( $wgAutoloadClasses as $class => $file ) {
+ if ( !( class_exists( $class, false ) || interface_exists( $class, false ) ) ) {
require( $file );
}
}
@@ -677,10 +812,6 @@ class AutoLoader {
}
}
-function wfLoadAllExtensions() {
- AutoLoader::loadAllExtensions();
-}
-
if ( function_exists( 'spl_autoload_register' ) ) {
spl_autoload_register( array( 'AutoLoader', 'autoload' ) );
} else {
diff --git a/includes/Autopromote.php b/includes/Autopromote.php
index c0adff43..b4d89b24 100644
--- a/includes/Autopromote.php
+++ b/includes/Autopromote.php
@@ -3,6 +3,7 @@
* This class checks if user can get extra rights
* because of conditions specified in $wgAutopromote
*/
+
class Autopromote {
/**
* Get the groups for the given user based on $wgAutopromote.
@@ -12,10 +13,13 @@ class Autopromote {
*/
public static function getAutopromoteGroups( User $user ) {
global $wgAutopromote;
+
$promote = array();
- foreach( $wgAutopromote as $group => $cond ) {
- if( self::recCheckCondition( $cond, $user ) )
+
+ foreach ( $wgAutopromote as $group => $cond ) {
+ if ( self::recCheckCondition( $cond, $user ) ) {
$promote[] = $group;
+ }
}
wfRunHooks( 'GetAutoPromoteGroups', array( $user, &$promote ) );
@@ -41,38 +45,52 @@ class Autopromote {
*/
private static function recCheckCondition( $cond, User $user ) {
$validOps = array( '&', '|', '^', '!' );
- if( is_array( $cond ) && count( $cond ) >= 2 && in_array( $cond[0], $validOps ) ) {
+
+ if ( is_array( $cond ) && count( $cond ) >= 2 && in_array( $cond[0], $validOps ) ) {
# Recursive condition
- if( $cond[0] == '&' ) {
- foreach( array_slice( $cond, 1 ) as $subcond )
- if( !self::recCheckCondition( $subcond, $user ) )
+ if ( $cond[0] == '&' ) {
+ foreach ( array_slice( $cond, 1 ) as $subcond ) {
+ if ( !self::recCheckCondition( $subcond, $user ) ) {
return false;
+ }
+ }
+
return true;
- } elseif( $cond[0] == '|' ) {
- foreach( array_slice( $cond, 1 ) as $subcond )
- if( self::recCheckCondition( $subcond, $user ) )
+ } elseif ( $cond[0] == '|' ) {
+ foreach ( array_slice( $cond, 1 ) as $subcond ) {
+ if ( self::recCheckCondition( $subcond, $user ) ) {
return true;
+ }
+ }
+
return false;
- } elseif( $cond[0] == '^' ) {
+ } elseif ( $cond[0] == '^' ) {
$res = null;
- foreach( array_slice( $cond, 1 ) as $subcond ) {
- if( is_null( $res ) )
+ foreach ( array_slice( $cond, 1 ) as $subcond ) {
+ if ( is_null( $res ) ) {
$res = self::recCheckCondition( $subcond, $user );
- else
- $res = ($res xor self::recCheckCondition( $subcond, $user ));
+ } else {
+ $res = ( $res xor self::recCheckCondition( $subcond, $user ) );
+ }
}
+
return $res;
- } elseif ( $cond[0] = '!' ) {
- foreach( array_slice( $cond, 1 ) as $subcond )
- if( self::recCheckCondition( $subcond, $user ) )
+ } elseif ( $cond[0] == '!' ) {
+ foreach ( array_slice( $cond, 1 ) as $subcond ) {
+ if ( self::recCheckCondition( $subcond, $user ) ) {
return false;
+ }
+ }
+
return true;
}
}
# If we got here, the array presumably does not contain other condi-
# tions; it's not recursive. Pass it off to self::checkCondition.
- if( !is_array( $cond ) )
+ if ( !is_array( $cond ) ) {
$cond = array( $cond );
+ }
+
return self::checkCondition( $cond, $user );
}
@@ -87,13 +105,15 @@ class Autopromote {
* @return bool Whether the condition is true for the user
*/
private static function checkCondition( $cond, User $user ) {
- if( count( $cond ) < 1 )
+ global $wgEmailAuthentication;
+ if ( count( $cond ) < 1 ) {
return false;
+ }
+
switch( $cond[0] ) {
case APCOND_EMAILCONFIRMED:
- if( User::isValidEmailAddr( $user->getEmail() ) ) {
- global $wgEmailAuthentication;
- if( $wgEmailAuthentication ) {
+ if ( User::isValidEmailAddr( $user->getEmail() ) ) {
+ if ( $wgEmailAuthentication ) {
return (bool)$user->getEmailAuthenticationTimestamp();
} else {
return true;
@@ -120,10 +140,11 @@ class Autopromote {
default:
$result = null;
wfRunHooks( 'AutopromoteCondition', array( $cond[0], array_slice( $cond, 1 ), $user, &$result ) );
- if( $result === null ) {
+ if ( $result === null ) {
throw new MWException( "Unrecognized condition {$cond[0]} for autopromotion!" );
}
- return $result ? true : false;
+
+ return (bool)$result;
}
}
}
diff --git a/includes/BacklinkCache.php b/includes/BacklinkCache.php
index 53f92dd9..02b0f170 100644
--- a/includes/BacklinkCache.php
+++ b/includes/BacklinkCache.php
@@ -22,6 +22,14 @@ class BacklinkCache {
}
/**
+ * Serialization handler, diasallows to serialize the database to prevent
+ * failures after this class is deserialized from cache with dead DB connection.
+ */
+ function __sleep() {
+ return array( 'partitionCache', 'fullResultCache', 'title' );
+ }
+
+ /**
* Clear locally stored data
*/
function clear() {
@@ -41,6 +49,7 @@ class BacklinkCache {
if ( !isset( $this->db ) ) {
$this->db = wfGetDB( DB_SLAVE );
}
+
return $this->db;
}
@@ -60,14 +69,17 @@ class BacklinkCache {
// Partial range, not cached
wfDebug( __METHOD__ . ": from DB (uncacheable range)\n" );
$conds = $this->getConditions( $table );
+
// Use the from field in the condition rather than the joined page_id,
// because databases are stupid and don't necessarily propagate indexes.
if ( $startId ) {
$conds[] = "$fromField >= " . intval( $startId );
}
+
if ( $endId ) {
$conds[] = "$fromField <= " . intval( $endId );
}
+
$res = $this->getDB()->select(
array( $table, 'page' ),
array( 'page_namespace', 'page_title', 'page_id' ),
@@ -78,6 +90,7 @@ class BacklinkCache {
'ORDER BY' => $fromField
) );
$ta = TitleArray::newFromResult( $res );
+
wfProfileOut( __METHOD__ );
return $ta;
}
@@ -95,7 +108,9 @@ class BacklinkCache {
) );
$this->fullResultCache[$table] = $res;
}
+
$ta = TitleArray::newFromResult( $this->fullResultCache[$table] );
+
wfProfileOut( __METHOD__ );
return $ta;
}
@@ -150,6 +165,7 @@ class BacklinkCache {
default:
throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
}
+
return $conds;
}
@@ -167,6 +183,7 @@ class BacklinkCache {
}
$titleArray = $this->getLinks( $table );
+
return $titleArray->count();
}
@@ -193,29 +210,35 @@ class BacklinkCache {
if ( isset( $this->fullResultCache[$table] ) ) {
$cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
wfDebug( __METHOD__ . ": got from full result cache\n" );
+
return $cacheEntry['batches'];
}
// Try memcached
global $wgMemc;
+
$memcKey = wfMemcKey(
'backlinks',
md5( $this->title->getPrefixedDBkey() ),
$table,
$batchSize
);
+
$memcValue = $wgMemc->get( $memcKey );
if ( is_array( $memcValue ) ) {
$cacheEntry = $memcValue;
wfDebug( __METHOD__ . ": got from memcached $memcKey\n" );
+
return $cacheEntry['batches'];
}
+
// Fetch from database
$this->getLinks( $table );
$cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
// Save to memcached
$wgMemc->set( $memcKey, $cacheEntry, self::CACHE_EXPIRY );
+
wfDebug( __METHOD__ . ": got from database\n" );
return $cacheEntry['batches'];
}
@@ -254,6 +277,7 @@ class BacklinkCache {
$batches[] = array( $start, $end );
}
+
return array( 'numRows' => $numRows, 'batches' => $batches );
}
}
diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php
index ac0263d8..63c96de7 100644
--- a/includes/BagOStuff.php
+++ b/includes/BagOStuff.php
@@ -1,31 +1,34 @@
<?php
-#
-# Copyright (C) 2003-2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
- * @defgroup Cache Cache
+ * Classes to cache objects in PHP accelerators, SQL database or DBA files
+ *
+ * Copyright © 2003-2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Cache
*/
/**
+ * @defgroup Cache Cache
+ */
+
+/**
* interface is intended to be more or less compatible with
* the PHP memcached client.
*
@@ -102,8 +105,9 @@ abstract class BagOStuff {
}
public function add( $key, $value, $exptime = 0 ) {
- if ( $this->get( $key ) == false ) {
+ if ( !$this->get( $key ) ) {
$this->set( $key, $value, $exptime );
+
return true;
}
}
@@ -126,18 +130,24 @@ abstract class BagOStuff {
}
}
+ /**
+ * @param $key String: Key to increase
+ * @param $value Integer: Value to add to $key (Default 1)
+ * @return null if lock is not possible else $key value increased by $value
+ */
public function incr( $key, $value = 1 ) {
if ( !$this->lock( $key ) ) {
- return false;
+ return null;
}
+
$value = intval( $value );
- $n = false;
if ( ( $n = $this->get( $key ) ) !== false ) {
$n += $value;
$this->set( $key, $n ); // exptime?
}
$this->unlock( $key );
+
return $n;
}
@@ -146,15 +156,16 @@ abstract class BagOStuff {
}
public function debug( $text ) {
- if ( $this->debugMode )
+ if ( $this->debugMode ) {
wfDebug( "BagOStuff debug: $text\n" );
+ }
}
/**
* Convert an optionally relative time to an absolute time
*/
protected function convertExpiry( $exptime ) {
- if ( ( $exptime != 0 ) && ( $exptime < 3600 * 24 * 30 ) ) {
+ if ( ( $exptime != 0 ) && ( $exptime < 86400 * 3650 /* 10 years */ ) ) {
return time() + $exptime;
} else {
return $exptime;
@@ -178,10 +189,13 @@ class HashBagOStuff extends BagOStuff {
protected function expire( $key ) {
$et = $this->bag[$key][1];
+
if ( ( $et == 0 ) || ( $et > time() ) ) {
return false;
}
+
$this->delete( $key );
+
return true;
}
@@ -189,9 +203,11 @@ class HashBagOStuff extends BagOStuff {
if ( !isset( $this->bag[$key] ) ) {
return false;
}
+
if ( $this->expire( $key ) ) {
return false;
}
+
return $this->bag[$key][0];
}
@@ -203,7 +219,9 @@ class HashBagOStuff extends BagOStuff {
if ( !isset( $this->bag[$key] ) ) {
return false;
}
+
unset( $this->bag[$key] );
+
return true;
}
@@ -223,6 +241,7 @@ class SqlBagOStuff extends BagOStuff {
protected function getDB() {
global $wgDBtype;
+
if ( !isset( $this->db ) ) {
/* We must keep a separate connection to MySQL in order to avoid deadlocks
* However, SQLite has an opposite behaviour.
@@ -236,6 +255,7 @@ class SqlBagOStuff extends BagOStuff {
$this->db->clearFlag( DBO_TRX );
}
}
+
return $this->db;
}
@@ -245,12 +265,14 @@ class SqlBagOStuff extends BagOStuff {
$db = $this->getDB();
$row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ),
array( 'keyname' => $key ), __METHOD__ );
+
if ( !$row ) {
$this->debug( 'get: no matching rows' );
return false;
}
$this->debug( "get: retrieved data; expiry time is " . $row->exptime );
+
if ( $this->isExpired( $row->exptime ) ) {
$this->debug( "get: key has expired, deleting" );
try {
@@ -266,26 +288,35 @@ class SqlBagOStuff extends BagOStuff {
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
}
+
return false;
}
+
return $this->unserialize( $db->decodeBlob( $row->value ) );
}
public function set( $key, $value, $exptime = 0 ) {
$db = $this->getDB();
$exptime = intval( $exptime );
- if ( $exptime < 0 ) $exptime = 0;
+
+ if ( $exptime < 0 ) {
+ $exptime = 0;
+ }
+
if ( $exptime == 0 ) {
$encExpiry = $this->getMaxDateTime();
} else {
- if ( $exptime < 3.16e8 ) # ~10 years
+ if ( $exptime < 3.16e8 ) { # ~10 years
$exptime += time();
+ }
+
$encExpiry = $db->timestamp( $exptime );
}
try {
$db->begin();
- $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ );
- $db->insert( 'objectcache',
+ // (bug 24425) use a replace if the db supports it instead of
+ // delete/insert to avoid clashes with conflicting keynames
+ $db->replace( 'objectcache', array( 'keyname' ),
array(
'keyname' => $key,
'value' => $db->encodeBlob( $this->serialize( $value ) ),
@@ -294,21 +325,26 @@ class SqlBagOStuff extends BagOStuff {
$db->commit();
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
+
return false;
}
+
return true;
}
public function delete( $key, $time = 0 ) {
$db = $this->getDB();
+
try {
$db->begin();
$db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ );
$db->commit();
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
+
return false;
}
+
return true;
}
@@ -323,13 +359,15 @@ class SqlBagOStuff extends BagOStuff {
if ( $row === false ) {
// Missing
$db->commit();
- return false;
+
+ return null;
}
$db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ );
if ( $this->isExpired( $row->exptime ) ) {
// Expired, do not reinsert
$db->commit();
- return false;
+
+ return null;
}
$oldValue = intval( $this->unserialize( $db->decodeBlob( $row->value ) ) );
@@ -339,12 +377,19 @@ class SqlBagOStuff extends BagOStuff {
'keyname' => $key,
'value' => $db->encodeBlob( $this->serialize( $newValue ) ),
'exptime' => $row->exptime
- ), __METHOD__ );
+ ), __METHOD__, 'IGNORE' );
+
+ if ( $db->affectedRows() == 0 ) {
+ // Race condition. See bug 28611
+ $newValue = null;
+ }
$db->commit();
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
- return false;
+
+ return null;
}
+
return $newValue;
}
@@ -352,9 +397,11 @@ class SqlBagOStuff extends BagOStuff {
$db = $this->getDB();
$res = $db->select( 'objectcache', array( 'keyname' ), false, __METHOD__ );
$result = array();
+
foreach ( $res as $row ) {
$result[] = $row->keyname;
}
+
return $result;
}
@@ -385,6 +432,7 @@ class SqlBagOStuff extends BagOStuff {
public function expireAll() {
$db = $this->getDB();
$now = $db->timestamp();
+
try {
$db->begin();
$db->delete( 'objectcache', array( 'exptime < ' . $db->addQuotes( $now ) ), __METHOD__ );
@@ -396,6 +444,7 @@ class SqlBagOStuff extends BagOStuff {
public function deleteAll() {
$db = $this->getDB();
+
try {
$db->begin();
$db->delete( 'objectcache', '*', __METHOD__ );
@@ -415,6 +464,7 @@ class SqlBagOStuff extends BagOStuff {
*/
protected function serialize( &$data ) {
$serial = serialize( $data );
+
if ( function_exists( 'gzdeflate' ) ) {
return gzdeflate( $serial );
} else {
@@ -430,11 +480,14 @@ class SqlBagOStuff extends BagOStuff {
protected function unserialize( $serial ) {
if ( function_exists( 'gzinflate' ) ) {
$decomp = @gzinflate( $serial );
+
if ( false !== $decomp ) {
$serial = $decomp;
}
}
+
$ret = unserialize( $serial );
+
return $ret;
}
@@ -444,13 +497,16 @@ class SqlBagOStuff extends BagOStuff {
*/
protected function handleWriteError( $exception ) {
$db = $this->getDB();
+
if ( !$db->wasReadOnlyError() ) {
throw $exception;
}
+
try {
$db->rollback();
} catch ( DBQueryError $e ) {
}
+
wfDebug( __METHOD__ . ": ignoring query error\n" );
$db->ignoreErrors( false );
}
@@ -469,19 +525,23 @@ class MediaWikiBagOStuff extends SqlBagOStuff { }
class APCBagOStuff extends BagOStuff {
public function get( $key ) {
$val = apc_fetch( $key );
+
if ( is_string( $val ) ) {
$val = unserialize( $val );
}
+
return $val;
}
public function set( $key, $value, $exptime = 0 ) {
apc_store( $key, serialize( $value ), $exptime );
+
return true;
}
public function delete( $key, $time = 0 ) {
apc_delete( $key );
+
return true;
}
@@ -489,9 +549,11 @@ class APCBagOStuff extends BagOStuff {
$info = apc_cache_info( 'user' );
$list = $info['cache_list'];
$keys = array();
+
foreach ( $list as $entry ) {
$keys[] = $entry['info'];
}
+
return $keys;
}
}
@@ -507,29 +569,35 @@ class APCBagOStuff extends BagOStuff {
class eAccelBagOStuff extends BagOStuff {
public function get( $key ) {
$val = eaccelerator_get( $key );
+
if ( is_string( $val ) ) {
$val = unserialize( $val );
}
+
return $val;
}
public function set( $key, $value, $exptime = 0 ) {
eaccelerator_put( $key, serialize( $value ), $exptime );
+
return true;
}
public function delete( $key, $time = 0 ) {
eaccelerator_rm( $key );
+
return true;
}
public function lock( $key, $waitTimeout = 0 ) {
eaccelerator_lock( $key );
+
return true;
}
public function unlock( $key ) {
eaccelerator_unlock( $key );
+
return true;
}
}
@@ -541,7 +609,6 @@ class eAccelBagOStuff extends BagOStuff {
* @ingroup Cache
*/
class XCacheBagOStuff extends BagOStuff {
-
/**
* Get a value from the XCache object cache
*
@@ -550,8 +617,11 @@ class XCacheBagOStuff extends BagOStuff {
*/
public function get( $key ) {
$val = xcache_get( $key );
- if ( is_string( $val ) )
+
+ if ( is_string( $val ) ) {
$val = unserialize( $val );
+ }
+
return $val;
}
@@ -565,6 +635,7 @@ class XCacheBagOStuff extends BagOStuff {
*/
public function set( $key, $value, $expire = 0 ) {
xcache_set( $key, serialize( $value ), $expire );
+
return true;
}
@@ -577,6 +648,7 @@ class XCacheBagOStuff extends BagOStuff {
*/
public function delete( $key, $time = 0 ) {
xcache_unset( $key );
+
return true;
}
}
@@ -594,10 +666,12 @@ class DBABagOStuff extends BagOStuff {
public function __construct( $dir = false ) {
global $wgDBAhandler;
+
if ( $dir === false ) {
global $wgTmpDirectory;
$dir = $wgTmpDirectory;
}
+
$this->mFile = "$dir/mw-cache-" . wfWikiID();
$this->mFile .= '.db';
wfDebug( __CLASS__ . ": using cache file {$this->mFile}\n" );
@@ -610,6 +684,7 @@ class DBABagOStuff extends BagOStuff {
function encode( $value, $expiry ) {
# Convert to absolute time
$expiry = $this->convertExpiry( $expiry );
+
return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value );
}
@@ -633,29 +708,37 @@ class DBABagOStuff extends BagOStuff {
} else {
$handle = $this->getWriter();
}
+
if ( !$handle ) {
wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
}
+
return $handle;
}
function getWriter() {
$handle = dba_open( $this->mFile, 'cl', $this->mHandler );
+
if ( !$handle ) {
wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
}
+
return $handle;
}
function get( $key ) {
wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . "($key)\n" );
+
$handle = $this->getReader();
if ( !$handle ) {
+ wfProfileOut( __METHOD__ );
return null;
}
+
$val = dba_fetch( $key, $handle );
list( $val, $expiry ) = $this->decode( $val );
+
# Must close ASAP because locks are held
dba_close( $handle );
@@ -667,6 +750,7 @@ class DBABagOStuff extends BagOStuff {
wfDebug( __METHOD__ . ": $key expired\n" );
$val = null;
}
+
wfProfileOut( __METHOD__ );
return $val;
}
@@ -674,13 +758,18 @@ class DBABagOStuff extends BagOStuff {
function set( $key, $value, $exptime = 0 ) {
wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . "($key)\n" );
+
$blob = $this->encode( $value, $exptime );
+
$handle = $this->getWriter();
if ( !$handle ) {
+ wfProfileOut( __METHOD__ );
return false;
}
+
$ret = dba_replace( $key, $blob, $handle );
dba_close( $handle );
+
wfProfileOut( __METHOD__ );
return $ret;
}
@@ -688,27 +777,38 @@ class DBABagOStuff extends BagOStuff {
function delete( $key, $time = 0 ) {
wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . "($key)\n" );
+
$handle = $this->getWriter();
if ( !$handle ) {
+ wfProfileOut( __METHOD__ );
return false;
}
+
$ret = dba_delete( $key, $handle );
dba_close( $handle );
+
wfProfileOut( __METHOD__ );
return $ret;
}
function add( $key, $value, $exptime = 0 ) {
wfProfileIn( __METHOD__ );
+
$blob = $this->encode( $value, $exptime );
+
$handle = $this->getWriter();
+
if ( !$handle ) {
+ wfProfileOut( __METHOD__ );
return false;
}
+
$ret = dba_insert( $key, $blob, $handle );
+
# Insert failed, check to see if it failed due to an expired key
if ( !$ret ) {
list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
+
if ( $expiry < time() ) {
# Yes expired, delete and try again
dba_delete( $key, $handle );
@@ -718,6 +818,7 @@ class DBABagOStuff extends BagOStuff {
}
dba_close( $handle );
+
wfProfileOut( __METHOD__ );
return $ret;
}
@@ -725,13 +826,81 @@ class DBABagOStuff extends BagOStuff {
function keys() {
$reader = $this->getReader();
$k1 = dba_firstkey( $reader );
+
if ( !$k1 ) {
return array();
}
+
$result[] = $k1;
+
while ( $key = dba_nextkey( $reader ) ) {
$result[] = $key;
}
+
return $result;
}
}
+
+/**
+ * Wrapper for WinCache object caching functions; identical interface
+ * to the APC wrapper
+ *
+ * @ingroup Cache
+ */
+class WinCacheBagOStuff extends BagOStuff {
+
+ /**
+ * Get a value from the WinCache object cache
+ *
+ * @param $key String: cache key
+ * @return mixed
+ */
+ public function get( $key ) {
+ $val = wincache_ucache_get( $key );
+
+ if ( is_string( $val ) ) {
+ $val = unserialize( $val );
+ }
+
+ return $val;
+ }
+
+ /**
+ * Store a value in the WinCache object cache
+ *
+ * @param $key String: cache key
+ * @param $value Mixed: object to store
+ * @param $expire Int: expiration time
+ * @return bool
+ */
+ public function set( $key, $value, $expire = 0 ) {
+ wincache_ucache_set( $key, serialize( $value ), $expire );
+
+ return true;
+ }
+
+ /**
+ * Remove a value from the WinCache object cache
+ *
+ * @param $key String: cache key
+ * @param $time Int: not used in this implementation
+ * @return bool
+ */
+ public function delete( $key, $time = 0 ) {
+ wincache_ucache_delete( $key );
+
+ return true;
+ }
+
+ public function keys() {
+ $info = wincache_ucache_info();
+ $list = $info['ucache_entries'];
+ $keys = array();
+
+ foreach ( $list as $entry ) {
+ $keys[] = $entry['key_name'];
+ }
+
+ return $keys;
+ }
+}
diff --git a/includes/Block.php b/includes/Block.php
index 187ff2db..7c5f0ddd 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -24,8 +24,8 @@ class Block {
const EB_RANGE_ONLY = 4;
function __construct( $address = '', $user = 0, $by = 0, $reason = '',
- $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0,
- $hideName = 0, $blockEmail = 0, $allowUsertalk = 0 )
+ $timestamp = 0, $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0,
+ $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byName = false )
{
$this->mId = 0;
# Expand valid IPv6 addresses
@@ -45,7 +45,7 @@ class Block {
$this->mAllowUsertalk = $allowUsertalk;
$this->mForUpdate = false;
$this->mFromMaster = false;
- $this->mByName = false;
+ $this->mByName = $byName;
$this->mAngryAutoblock = false;
$this->initialiseRange();
}
@@ -63,6 +63,7 @@ class Block {
public static function newFromDB( $address, $user = 0, $killExpired = true ) {
$block = new Block;
$block->load( $address, $user, $killExpired );
+
if ( $block->isValid() ) {
return $block;
} else {
@@ -81,6 +82,7 @@ class Block {
$res = $dbr->resultObject( $dbr->select( 'ipblocks', '*',
array( 'ipb_id' => $id ), __METHOD__ ) );
$block = new Block;
+
if ( $block->loadFromResult( $res ) ) {
return $block;
} else {
@@ -142,6 +144,7 @@ class Block {
$db = wfGetDB( DB_SLAVE );
$options = array();
}
+
return $db;
}
@@ -158,11 +161,12 @@ class Block {
wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
$options = array();
- $db =& $this->getDBOptions( $options );
+ $db = $this->getDBOptions( $options );
- if ( 0 == $user && $address == '' ) {
+ if ( 0 == $user && $address === '' ) {
# Invalid user specification, not blocked
$this->clear();
+
return false;
}
@@ -170,6 +174,7 @@ class Block {
if ( $user ) {
$res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ),
__METHOD__, $options ) );
+
if ( $this->loadFromResult( $res, $killExpired ) ) {
return true;
}
@@ -178,7 +183,7 @@ class Block {
# Try IP block
# TODO: improve performance by merging this query with the autoblock one
# Slightly tricky while handling killExpired as well
- if ( $address ) {
+ if ( $address !== '' ) {
$conds = array( 'ipb_address' => $address, 'ipb_auto' => 0 );
$res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
@@ -190,6 +195,7 @@ class Block {
if ( !$this->mCreateAccount ) {
$this->clear();
}
+
return false;
} else {
return true;
@@ -204,6 +210,7 @@ class Block {
if ( !$this->mCreateAccount ) {
$this->clear();
}
+
return false;
} else {
return true;
@@ -266,6 +273,7 @@ class Block {
}
}
$res->free();
+
return $ret;
}
@@ -275,7 +283,7 @@ class Block {
*
* @param $address String: IP address range
* @param $killExpired Boolean: whether to delete expired rows while loading
- * @param $userid Integer: if not 0, then sets ipb_anon_only
+ * @param $user Integer: if not 0, then sets ipb_anon_only
* @return Boolean
*/
public function loadRange( $address, $killExpired = true, $user = 0 ) {
@@ -291,7 +299,7 @@ class Block {
$range = substr( $iaddr, 0, 4 );
$options = array();
- $db =& $this->getDBOptions( $options );
+ $db = $this->getDBOptions( $options );
$conds = array(
'ipb_range_start' . $db->buildLike( $range, $db->anyString() ),
"ipb_range_start <= '$iaddr'",
@@ -304,6 +312,7 @@ class Block {
$res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
$success = $this->loadFromResult( $res, $killExpired );
+
return $success;
}
@@ -368,6 +377,7 @@ class Block {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ );
+
return $dbw->affectedRows() > 0;
}
@@ -377,9 +387,11 @@ class Block {
*
* @return Boolean: whether or not the insertion was successful.
*/
- public function insert() {
+ public function insert( $dbw = null ) {
wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
- $dbw = wfGetDB( DB_MASTER );
+
+ if ( $dbw === null )
+ $dbw = wfGetDB( DB_MASTER );
$this->validateBlockParams();
$this->initialiseRange();
@@ -475,6 +487,7 @@ class Block {
if ( !$this->mUser && $this->mAnonOnly ) {
$this->mBlockEmail = 0;
}
+
if ( !$this->mByName ) {
if ( $this->mBy ) {
$this->mByName = User::whoIs( $this->mBy );
@@ -518,9 +531,10 @@ class Block {
# No results, don't autoblock anything
wfDebug( "No IP found to retroactively autoblock\n" );
} else {
- while ( $row = $dbr->fetchObject( $res ) ) {
- if ( $row->rc_ip )
+ foreach ( $res as $row ) {
+ if ( $row->rc_ip ) {
$this->doAutoblock( $row->rc_ip );
+ }
}
}
}
@@ -601,13 +615,16 @@ class Block {
# exceed the user block. If it would exceed, then do nothing, else
# prolong block time
if ( $this->mExpiry &&
- ( $this->mExpiry < Block::getAutoblockExpiry( $ipblock->mTimestamp ) ) ) {
+ ( $this->mExpiry < Block::getAutoblockExpiry( $ipblock->mTimestamp ) )
+ ) {
return;
}
+
# Just update the timestamp
if ( !$justInserted ) {
$ipblock->updateTimestamp();
}
+
return;
} else {
$ipblock = new Block;
@@ -626,6 +643,7 @@ class Block {
# Continue suppressing the name if needed
$ipblock->mHideName = $this->mHideName;
$ipblock->mAllowUsertalk = $this->mAllowUsertalk;
+
# If the user is already blocked with an expiry date, we don't
# want to pile on top of that!
if ( $this->mExpiry ) {
@@ -633,6 +651,7 @@ class Block {
} else {
$ipblock->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
}
+
# Insert it
return $ipblock->insert();
}
@@ -643,6 +662,7 @@ class Block {
*/
public function deleteIfExpired() {
wfProfileIn( __METHOD__ );
+
if ( $this->isExpired() ) {
wfDebug( "Block::deleteIfExpired() -- deleting\n" );
$this->delete();
@@ -651,6 +671,7 @@ class Block {
wfDebug( "Block::deleteIfExpired() -- not expired\n" );
$retVal = false;
}
+
wfProfileOut( __METHOD__ );
return $retVal;
}
@@ -661,6 +682,7 @@ class Block {
*/
public function isExpired() {
wfDebug( "Block::isExpired() checking current " . wfTimestampNow() . " vs $this->mExpiry\n" );
+
if ( !$this->mExpiry ) {
return false;
} else {
@@ -777,6 +799,7 @@ class Block {
*/
public static function getAutoblockExpiry( $timestamp ) {
global $wgAutoblockExpiry;
+
return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
}
@@ -792,7 +815,7 @@ class Block {
// IPv6
if ( IP::isIPv6( $range ) && $parts[1] >= 64 && $parts[1] <= 128 ) {
$bits = $parts[1];
- $ipint = IP::toUnsigned6( $parts[0] );
+ $ipint = IP::toUnsigned( $parts[0] );
# Native 32 bit functions WON'T work here!!!
# Convert to a padded binary number
$network = wfBaseConvert( $ipint, 10, 2, 128 );
@@ -812,6 +835,7 @@ class Block {
$range = "$newip/{$parts[1]}";
}
}
+
return $range;
}
@@ -833,6 +857,16 @@ class Block {
public static function infinity() {
# This is a special keyword for timestamps in PostgreSQL, and
# works with CHAR(14) as well because "i" sorts after all numbers.
+
+ # BEGIN DatabaseMssql hack
+ # Since MSSQL doesn't recognize the infinity keyword, set date manually.
+ # TO-DO: Refactor for better DB portability and remove magic date
+ $dbr = wfGetDB( DB_SLAVE );
+ if ( $dbr->getType() == 'mssql' ) {
+ return '3000-01-31 00:00:00.000';
+ }
+ # End DatabaseMssql hack
+
return 'infinity';
}
@@ -848,6 +882,7 @@ class Block {
if ( is_null( $msg ) ) {
$msg = array();
$keys = array( 'infiniteblock', 'expiringblock' );
+
foreach ( $keys as $key ) {
$msg[$key] = wfMsgHtml( $key );
}
@@ -862,6 +897,7 @@ class Block {
$expiretimestr = htmlspecialchars( $wgLang->time( $expiry, true ) );
$expirystr = wfMsgReplaceArgs( $msg['expiringblock'], array( $expiredatestr, $expiretimestr ) );
}
+
return $expirystr;
}
@@ -880,7 +916,7 @@ class Block {
return false;
}
}
+
return $expiry;
}
-
}
diff --git a/includes/CacheDependency.php b/includes/CacheDependency.php
index 11e70738..74ca2864 100644
--- a/includes/CacheDependency.php
+++ b/includes/CacheDependency.php
@@ -1,10 +1,11 @@
<?php
/**
* This class stores an arbitrary value along with its dependencies.
- * Users should typically only use DependencyWrapper::getFromCache(), rather
- * than instantiating one of these objects directly.
+ * Users should typically only use DependencyWrapper::getValueFromCache(),
+ * rather than instantiating one of these objects directly.
* @ingroup Cache
*/
+
class DependencyWrapper {
var $value;
var $deps;
@@ -17,9 +18,11 @@ class DependencyWrapper {
*/
function __construct( $value = false, $deps = array() ) {
$this->value = $value;
+
if ( !is_array( $deps ) ) {
$deps = array( $deps );
}
+
$this->deps = $deps;
}
@@ -32,6 +35,7 @@ class DependencyWrapper {
return true;
}
}
+
return false;
}
@@ -81,6 +85,7 @@ class DependencyWrapper {
$callbackParams = array(), $deps = array() )
{
$obj = $cache->get( $key );
+
if ( is_object( $obj ) && $obj instanceof DependencyWrapper && !$obj->isExpired() ) {
$value = $obj->value;
} elseif ( $callback ) {
@@ -91,6 +96,7 @@ class DependencyWrapper {
} else {
$value = null;
}
+
return $value;
}
}
@@ -207,6 +213,7 @@ class TitleDependency extends CacheDependency {
if ( !isset( $this->titleObj ) ) {
$this->titleObj = Title::makeTitle( $this->ns, $this->dbk );
}
+
return $this->titleObj;
}
@@ -255,6 +262,7 @@ class TitleListDependency extends CacheDependency {
foreach ( $this->getLinkBatch()->data as $ns => $dbks ) {
if ( count( $dbks ) > 0 ) {
$timestamps[$ns] = array();
+
foreach ( $dbks as $dbk => $value ) {
$timestamps[$ns][$dbk] = false;
}
@@ -272,10 +280,11 @@ class TitleListDependency extends CacheDependency {
__METHOD__
);
- while ( $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$timestamps[$row->page_namespace][$row->page_title] = $row->page_touched;
}
}
+
return $timestamps;
}
@@ -297,6 +306,7 @@ class TitleListDependency extends CacheDependency {
function isExpired() {
$newTimestamps = $this->calculateTimestamps();
+
foreach ( $this->timestamps as $ns => $dbks ) {
foreach ( $dbks as $dbk => $oldTimestamp ) {
$newTimestamp = $newTimestamps[$ns][$dbk];
@@ -319,6 +329,7 @@ class TitleListDependency extends CacheDependency {
}
}
}
+
return false;
}
}
diff --git a/includes/Category.php b/includes/Category.php
index e9ffaecf..614933ff 100644
--- a/includes/Category.php
+++ b/includes/Category.php
@@ -25,9 +25,6 @@ class Category {
* @return bool True on success, false on failure.
*/
protected function initialize() {
- if ( $this->mName === null && $this->mTitle )
- $this->mName = $title->getDBkey();
-
if ( $this->mName === null && $this->mID === null ) {
throw new MWException( __METHOD__ . ' has both names and IDs null' );
} elseif ( $this->mID === null ) {
@@ -248,28 +245,33 @@ class Category {
if ( wfReadOnly() ) {
return false;
}
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
+
# Note, we must use names for this, since categorylinks does.
if ( $this->mName === null ) {
if ( !$this->initialize() ) {
return false;
}
- } else {
- # Let's be sure that the row exists in the table. We don't need to
- # do this if we got the row from the table in initialization!
- $seqVal = $dbw->nextSequenceValue( 'category_cat_id_seq' );
- $dbw->insert(
- 'category',
- array(
- 'cat_id' => $seqVal,
- 'cat_title' => $this->mName
- ),
- __METHOD__,
- 'IGNORE'
- );
}
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ # Insert the row if it doesn't exist yet (e.g., this is being run via
+ # update.php from a pre-1.16 schema). TODO: This will cause lots and
+ # lots of gaps on some non-MySQL DBMSes if you run populateCategory.php
+ # repeatedly. Plus it's an extra query that's unneeded almost all the
+ # time. This should be rewritten somehow, probably.
+ $seqVal = $dbw->nextSequenceValue( 'category_cat_id_seq' );
+ $dbw->insert(
+ 'category',
+ array(
+ 'cat_id' => $seqVal,
+ 'cat_title' => $this->mName
+ ),
+ __METHOD__,
+ 'IGNORE'
+ );
+
$cond1 = $dbw->conditional( 'page_namespace=' . NS_CATEGORY, 1, 'NULL' );
$cond2 = $dbw->conditional( 'page_namespace=' . NS_FILE, 1, 'NULL' );
$result = $dbw->selectRow(
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
index 56f85faa..f990b79b 100644
--- a/includes/CategoryPage.php
+++ b/includes/CategoryPage.php
@@ -1,33 +1,41 @@
<?php
/**
- * Special handling for category description pages
- * Modelled after ImagePage.php
+ * Special handling for category description pages.
+ * Modelled after ImagePage.php.
*
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) )
die( 1 );
/**
+ * Special handling for category description pages, showing pages,
+ * subcategories and file that belong to the category
*/
class CategoryPage extends Article {
+ # Subclasses can change this to override the viewer class.
+ protected $mCategoryViewerClass = 'CategoryViewer';
+
function view() {
global $wgRequest, $wgUser;
$diff = $wgRequest->getVal( 'diff' );
$diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
- if ( isset( $diff ) && $diffOnly )
- return Article::view();
+ if ( isset( $diff ) && $diffOnly ) {
+ return parent::view();
+ }
- if ( !wfRunHooks( 'CategoryPageView', array( &$this ) ) )
+ if ( !wfRunHooks( 'CategoryPageView', array( &$this ) ) ) {
return;
+ }
if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
$this->openShowCategory();
}
- Article::view();
+ parent::view();
if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
$this->closeShowCategory();
@@ -36,14 +44,23 @@ class CategoryPage extends Article {
/**
* Don't return a 404 for categories in use.
+ * In use defined as: either the actual page exists
+ * or the category currently has members.
*/
function hasViewableContent() {
if ( parent::hasViewableContent() ) {
return true;
} else {
$cat = Category::newFromTitle( $this->mTitle );
- return $cat->getId() != 0;
+ // If any of these are not 0, then has members
+ if ( $cat->getPageCount()
+ || $cat->getSubcatCount()
+ || $cat->getFileCount()
+ ) {
+ return true;
+ }
}
+ return false;
}
function openShowCategory() {
@@ -52,10 +69,14 @@ class CategoryPage extends Article {
function closeShowCategory() {
global $wgOut, $wgRequest;
- $from = $wgRequest->getVal( 'from' );
- $until = $wgRequest->getVal( 'until' );
- $viewer = new CategoryViewer( $this->mTitle, $from, $until );
+ $from = $until = array();
+ foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
+ $from[$type] = $wgRequest->getVal( "{$type}from" );
+ $until[$type] = $wgRequest->getVal( "{$type}until" );
+ }
+
+ $viewer = new $this->mCategoryViewerClass( $this->mTitle, $from, $until, $wgRequest->getValues() );
$wgOut->addHTML( $viewer->getHTML() );
}
}
@@ -65,27 +86,32 @@ class CategoryViewer {
$articles, $articles_start_char,
$children, $children_start_char,
$showGallery, $gallery,
- $skin;
- /** Category object for this page */
+ $imgsNoGalley, $imgsNoGallery_start_char,
+ $skin, $collation;
+ # Category object for this page
private $cat;
+ # The original query array, to be used in generating paging links.
+ private $query;
- function __construct( $title, $from = '', $until = '' ) {
+ function __construct( $title, $from = '', $until = '', $query = array() ) {
global $wgCategoryPagingLimit;
$this->title = $title;
$this->from = $from;
$this->until = $until;
$this->limit = $wgCategoryPagingLimit;
$this->cat = Category::newFromTitle( $title );
+ $this->query = $query;
+ $this->collation = Collation::singleton();
+ unset( $this->query['title'] );
}
/**
* Format the category data list.
*
* @return string HTML output
- * @private
*/
- function getHTML() {
- global $wgOut, $wgCategoryMagicGallery, $wgCategoryPagingLimit, $wgContLang;
+ public function getHTML() {
+ global $wgOut, $wgCategoryMagicGallery, $wgContLang;
wfProfileIn( __METHOD__ );
$this->showGallery = $wgCategoryMagicGallery && !$wgOut->mNoGallery;
@@ -128,6 +154,9 @@ class CategoryViewer {
if ( $this->showGallery ) {
$this->gallery = new ImageGallery();
$this->gallery->setHideBadImages();
+ } else {
+ $this->imgsNoGallery = array();
+ $this->imgsNoGallery_start_char = array();
}
}
@@ -142,26 +171,29 @@ class CategoryViewer {
/**
* Add a subcategory to the internal lists, using a Category object
*/
- function addSubcategoryObject( $cat, $sortkey, $pageLength ) {
+ function addSubcategoryObject( Category $cat, $sortkey, $pageLength ) {
+ // Subcategory; strip the 'Category' namespace from the link text.
$title = $cat->getTitle();
- $this->addSubcategory( $title, $sortkey, $pageLength );
+
+ $link = $this->getSkin()->link( $title, $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_start_char[] =
+ $this->getSubcategorySortChar( $cat->getTitle(), $sortkey );
}
/**
* Add a subcategory to the internal lists, using a title object
* @deprecated kept for compatibility, please use addSubcategoryObject instead
*/
- function addSubcategory( $title, $sortkey, $pageLength ) {
- // Subcategory; strip the 'Category' namespace from the link text.
- $this->children[] = $this->getSkin()->link(
- $title,
- null,
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
-
- $this->children_start_char[] = $this->getSubcategorySortChar( $title, $sortkey );
+ function addSubcategory( Title $title, $sortkey, $pageLength ) {
+ $this->addSubcategoryObject( Category::newFromTitle( $title ), $sortkey, $pageLength );
}
/**
@@ -170,16 +202,21 @@ class CategoryViewer {
* entry in the categorylinks table is Category:A, not A, which it SHOULD be.
* Workaround: If sortkey == "Category:".$title, than use $title for sorting,
* else use sortkey...
+ *
+ * @param Title $title
+ * @param string $sortkey The human-readable sortkey (before transforming to icu or whatever).
*/
function getSubcategorySortChar( $title, $sortkey ) {
global $wgContLang;
if ( $title->getPrefixedText() == $sortkey ) {
- $firstChar = $wgContLang->firstChar( $title->getDBkey() );
+ $word = $title->getDBkey();
} else {
- $firstChar = $wgContLang->firstChar( $sortkey );
+ $word = $sortkey;
}
+ $firstChar = $this->collation->getFirstLetter( $word );
+
return $wgContLang->convert( $firstChar );
}
@@ -187,14 +224,25 @@ class CategoryViewer {
* Add a page in the image namespace
*/
function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) {
+ global $wgContLang;
if ( $this->showGallery ) {
- if ( $this->flip ) {
+ $flip = $this->flip['file'];
+ if ( $flip ) {
$this->gallery->insert( $title );
} else {
$this->gallery->add( $title );
}
} else {
- $this->addPage( $title, $sortkey, $pageLength, $isRedirect );
+ $link = $this->getSkin()->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_start_char[] = $wgContLang->convert(
+ $this->collation->getFirstLetter( $sortkey ) );
}
}
@@ -203,74 +251,104 @@ class CategoryViewer {
*/
function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
global $wgContLang;
- $this->articles[] = $isRedirect
- ? '<span class="redirect-in-category">' .
- $this->getSkin()->link(
- $title,
- null,
- array(),
- array(),
- array( 'known', 'noclasses' )
- ) . '</span>'
- : $this->getSkin()->makeSizeLinkObj( $pageLength, $title );
- $this->articles_start_char[] = $wgContLang->convert( $wgContLang->firstChar( $sortkey ) );
+
+ $link = $this->getSkin()->link( $title );
+ if ( $isRedirect ) {
+ // This seems kind of pointless given 'mw-redirect' class,
+ // but keeping for back-compatiability with user css.
+ $link = '<span class="redirect-in-category">' . $link . '</span>';
+ }
+ $this->articles[] = $link;
+
+ $this->articles_start_char[] = $wgContLang->convert(
+ $this->collation->getFirstLetter( $sortkey ) );
}
function finaliseCategoryState() {
- if ( $this->flip ) {
+ if ( $this->flip['subcat'] ) {
$this->children = array_reverse( $this->children );
$this->children_start_char = array_reverse( $this->children_start_char );
+ }
+ if ( $this->flip['page'] ) {
$this->articles = array_reverse( $this->articles );
$this->articles_start_char = array_reverse( $this->articles_start_char );
}
+ if ( !$this->showGallery && $this->flip['file'] ) {
+ $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
+ $this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
+ }
}
function doCategoryQuery() {
$dbr = wfGetDB( DB_SLAVE, 'category' );
- if ( $this->from != '' ) {
- $pageCondition = 'cl_sortkey >= ' . $dbr->addQuotes( $this->from );
- $this->flip = false;
- } elseif ( $this->until != '' ) {
- $pageCondition = 'cl_sortkey < ' . $dbr->addQuotes( $this->until );
- $this->flip = true;
- } else {
- $pageCondition = '1 = 1';
- $this->flip = false;
- }
- $res = $dbr->select(
- array( 'page', 'categorylinks', 'category' ),
- array( 'page_title', 'page_namespace', 'page_len', 'page_is_redirect', 'cl_sortkey',
- 'cat_id', 'cat_title', 'cat_subcats', 'cat_pages', 'cat_files' ),
- array( $pageCondition, 'cl_to' => $this->title->getDBkey() ),
- __METHOD__,
- array( 'ORDER BY' => $this->flip ? 'cl_sortkey DESC' : 'cl_sortkey',
- 'USE INDEX' => array( 'categorylinks' => 'cl_sortkey' ),
- 'LIMIT' => $this->limit + 1 ),
- array( 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ),
- 'category' => array( 'LEFT JOIN', 'cat_title = page_title AND page_namespace = ' . NS_CATEGORY ) )
+ $this->nextPage = array(
+ 'page' => null,
+ 'subcat' => null,
+ 'file' => null,
);
+ $this->flip = array( 'page' => false, 'subcat' => false, 'file' => false );
+
+ foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
+ # Get the sortkeys for start/end, if applicable. Note that if
+ # the collation in the database differs from the one
+ # set in $wgCategoryCollation, pagination might go totally haywire.
+ $extraConds = array( 'cl_type' => $type );
+ if ( $this->from[$type] !== null ) {
+ $extraConds[] = 'cl_sortkey >= '
+ . $dbr->addQuotes( $this->collation->getSortKey( $this->from[$type] ) );
+ } elseif ( $this->until[$type] !== null ) {
+ $extraConds[] = 'cl_sortkey < '
+ . $dbr->addQuotes( $this->collation->getSortKey( $this->until[$type] ) );
+ $this->flip[$type] = true;
+ }
- $count = 0;
- $this->nextPage = null;
+ $res = $dbr->select(
+ array( 'page', 'categorylinks', 'category' ),
+ array( 'page_id', 'page_title', 'page_namespace', 'page_len',
+ 'page_is_redirect', 'cl_sortkey', 'cat_id', 'cat_title',
+ 'cat_subcats', 'cat_pages', 'cat_files',
+ 'cl_sortkey_prefix', 'cl_collation' ),
+ array( 'cl_to' => $this->title->getDBkey() ) + $extraConds,
+ __METHOD__,
+ array(
+ 'USE INDEX' => array( 'categorylinks' => 'cl_sortkey' ),
+ 'LIMIT' => $this->limit + 1,
+ 'ORDER BY' => $this->flip[$type] ? 'cl_sortkey DESC' : 'cl_sortkey',
+ ),
+ array(
+ 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ),
+ 'category' => array( 'LEFT JOIN', 'cat_title = page_title AND page_namespace = ' . NS_CATEGORY )
+ )
+ );
- while ( $x = $dbr->fetchObject ( $res ) ) {
- if ( ++$count > $this->limit ) {
- // We've reached the one extra which shows that there are
- // additional pages to be had. Stop here...
- $this->nextPage = $x->cl_sortkey;
- break;
- }
+ $count = 0;
+ foreach ( $res as $row ) {
+ $title = Title::newFromRow( $row );
+ if ( $row->cl_collation === '' ) {
+ // Hack to make sure that while updating from 1.16 schema
+ // and db is inconsistent, that the sky doesn't fall.
+ // See r83544. Could perhaps be removed in a couple decades...
+ $humanSortkey = $row->cl_sortkey;
+ } else {
+ $humanSortkey = $title->getCategorySortkey( $row->cl_sortkey_prefix );
+ }
- $title = Title::makeTitle( $x->page_namespace, $x->page_title );
+ if ( ++$count > $this->limit ) {
+ # We've reached the one extra which shows that there
+ # are additional pages to be had. Stop here...
+ $this->nextPage[$type] = $humanSortkey;
+ break;
+ }
- if ( $title->getNamespace() == NS_CATEGORY ) {
- $cat = Category::newFromRow( $x, $title );
- $this->addSubcategoryObject( $cat, $x->cl_sortkey, $x->page_len );
- } elseif ( $this->showGallery && $title->getNamespace() == NS_FILE ) {
- $this->addImage( $title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect );
- } else {
- $this->addPage( $title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect );
+ if ( $title->getNamespace() == NS_CATEGORY ) {
+ $cat = Category::newFromRow( $row, $title );
+ $this->addSubcategoryObject( $cat, $humanSortkey, $row->page_len );
+ } elseif ( $title->getNamespace() == NS_FILE ) {
+ $this->addImage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
+ } else {
+ $this->addPage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
+ }
}
}
}
@@ -294,7 +372,9 @@ class CategoryViewer {
$r .= "<div id=\"mw-subcategories\">\n";
$r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n";
$r .= $countmsg;
+ $r .= $this->getSectionPagingLinks( 'subcat' );
$r .= $this->formatList( $this->children, $this->children_start_char );
+ $r .= $this->getSectionPagingLinks( 'subcat' );
$r .= "\n</div>";
}
return $r;
@@ -318,36 +398,57 @@ class CategoryViewer {
$r = "<div id=\"mw-pages\">\n";
$r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
$r .= $countmsg;
+ $r .= $this->getSectionPagingLinks( 'page' );
$r .= $this->formatList( $this->articles, $this->articles_start_char );
+ $r .= $this->getSectionPagingLinks( 'page' );
$r .= "\n</div>";
}
return $r;
}
function getImageSection() {
- if ( $this->showGallery && ! $this->gallery->isEmpty() ) {
+ $r = '';
+ $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery );
+ if ( $rescnt > 0 ) {
$dbcnt = $this->cat->getFileCount();
- $rescnt = $this->gallery->count();
$countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' );
- return "<div id=\"mw-category-media\">\n" .
- '<h2>' . wfMsg( 'category-media-header', htmlspecialchars( $this->title->getText() ) ) . "</h2>\n" .
- $countmsg . $this->gallery->toHTML() . "\n</div>";
- } else {
- return '';
+ $r .= "<div id=\"mw-category-media\">\n";
+ $r .= '<h2>' . wfMsg( 'category-media-header', htmlspecialchars( $this->title->getText() ) ) . "</h2>\n";
+ $r .= $countmsg;
+ $r .= $this->getSectionPagingLinks( 'file' );
+ if ( $this->showGallery ) {
+ $r .= $this->gallery->toHTML();
+ } else {
+ $r .= $this->formatList( $this->imgsNoGallery, $this->imgsNoGallery_start_char );
+ }
+ $r .= $this->getSectionPagingLinks( 'file' );
+ $r .= "\n</div>";
}
+ return $r;
}
- function getCategoryBottom() {
- if ( $this->until != '' ) {
- return $this->pagingLinks( $this->title, $this->nextPage, $this->until, $this->limit );
- } elseif ( $this->nextPage != '' || $this->from != '' ) {
- return $this->pagingLinks( $this->title, $this->from, $this->nextPage, $this->limit );
+ /**
+ * Get the paging links for a section (subcats/pages/files), to go at the top and bottom
+ * of the output.
+ *
+ * @param $type String: 'page', 'subcat', or 'file'
+ * @return String: HTML output, possibly empty if there are no other pages
+ */
+ private function getSectionPagingLinks( $type ) {
+ if ( $this->until[$type] !== null ) {
+ return $this->pagingLinks( $this->nextPage[$type], $this->until[$type], $type );
+ } elseif ( $this->nextPage[$type] !== null || $this->from[$type] !== null ) {
+ return $this->pagingLinks( $this->from[$type], $this->nextPage[$type], $type );
} else {
return '';
}
}
+ function getCategoryBottom() {
+ return '';
+ }
+
/**
* Format a list of articles chunked by letter, either as a
* bullet list or a columnar format, depending on the length.
@@ -360,10 +461,10 @@ class CategoryViewer {
*/
function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
if ( count ( $articles ) > $cutoff ) {
- return $this->columnList( $articles, $articles_start_char );
+ return self::columnList( $articles, $articles_start_char );
} elseif ( count( $articles ) > 0 ) {
// for short lists of articles in categories.
- return $this->shortList( $articles, $articles_start_char );
+ return self::shortList( $articles, $articles_start_char );
}
return '';
}
@@ -383,7 +484,7 @@ class CategoryViewer {
* @return String
* @private
*/
- function columnList( $articles, $articles_start_char ) {
+ 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 */ );
@@ -435,7 +536,7 @@ class CategoryViewer {
* @return String
* @private
*/
- function shortList( $articles, $articles_start_char ) {
+ static function shortList( $articles, $articles_start_char ) {
$r = '<h3>' . htmlspecialchars( $articles_start_char[0] ) . "</h3>\n";
$r .= '<ul><li>' . $articles[0] . '</li>';
for ( $index = 1; $index < count( $articles ); $index++ )
@@ -452,26 +553,27 @@ class CategoryViewer {
}
/**
- * @param $title Title object
- * @param $first String
- * @param $last String
- * @param $limit Int
- * @param $query Array: additional query options to pass
- * @return String
- * @private
+ * Create paging links, as a helper method to getSectionPagingLinks().
+ *
+ * @param $first String The 'until' parameter for the generated URL
+ * @param $last String The 'from' parameter for the genererated URL
+ * @param $type String A prefix for parameters, 'page' or 'subcat' or
+ * 'file'
+ * @return String HTML
*/
- function pagingLinks( $title, $first, $last, $limit, $query = array() ) {
+ private function pagingLinks( $first, $last, $type = '' ) {
global $wgLang;
$sk = $this->getSkin();
- $limitText = $wgLang->formatNum( $limit );
+ $limitText = $wgLang->formatNum( $this->limit );
$prevLink = wfMsgExt( 'prevn', array( 'escape', 'parsemag' ), $limitText );
if ( $first != '' ) {
- $prevQuery = $query;
- $prevQuery['until'] = $first;
+ $prevQuery = $this->query;
+ $prevQuery["{$type}until"] = $first;
+ unset( $prevQuery["{$type}from"] );
$prevLink = $sk->linkKnown(
- $title,
+ $this->title,
$prevLink,
array(),
$prevQuery
@@ -481,10 +583,11 @@ class CategoryViewer {
$nextLink = wfMsgExt( 'nextn', array( 'escape', 'parsemag' ), $limitText );
if ( $last != '' ) {
- $lastQuery = $query;
- $lastQuery['from'] = $last;
+ $lastQuery = $this->query;
+ $lastQuery["{$type}from"] = $last;
+ unset( $lastQuery["{$type}until"] );
$nextLink = $sk->linkKnown(
- $title,
+ $this->title,
$nextLink,
array(),
$lastQuery
@@ -496,8 +599,8 @@ class CategoryViewer {
/**
* What to do if the category table conflicts with the number of results
- * returned? This function says what. It works the same whether the
- * things being counted are articles, subcategories, or files.
+ * returned? This function says what. Each type is considered independantly
+ * of the other types.
*
* Note for grepping: uses the messages category-article-count,
* category-article-count-limited, category-subcat-count,
@@ -520,15 +623,28 @@ class CategoryViewer {
# than $this->limit and there's no offset. In this case we still
# know the right figure.
# 3) We have no idea.
- $totalrescnt = count( $this->articles ) + count( $this->children ) +
- ( $this->showGallery ? $this->gallery->count() : 0 );
- if ( $dbcnt == $rescnt || ( ( $totalrescnt == $this->limit || $this->from
- || $this->until ) && $dbcnt > $rescnt ) )
+ # Check if there's a "from" or "until" for anything
+
+ // This is a little ugly, but we seem to use different names
+ // for the paging types then for the messages.
+ if ( $type === 'article' ) {
+ $pagingType = 'page';
+ } else {
+ $pagingType = $type;
+ }
+
+ $fromOrUntil = false;
+ if ( $this->from[$pagingType] !== null || $this->until[$pagingType] !== null ) {
+ $fromOrUntil = true;
+ }
+
+ if ( $dbcnt == $rescnt || ( ( $rescnt == $this->limit || $fromOrUntil )
+ && $dbcnt > $rescnt ) )
{
# Case 1: seems sane.
$totalcnt = $dbcnt;
- } elseif ( $totalrescnt < $this->limit && !$this->from && !$this->until ) {
+ } elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
# Case 2: not sane, but salvageable. Use the number of results.
# Since there are fewer than 200, we can also take this opportunity
# to refresh the incorrect category table entry -- which should be
diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php
index 5ac8a9be..1f08b7f8 100644
--- a/includes/Categoryfinder.php
+++ b/includes/Categoryfinder.php
@@ -10,14 +10,14 @@
* # Determines whether the article with the page_id 12345 is in both
* # "Category 1" and "Category 2" or their subcategories, respectively
*
- * $cf = new Categoryfinder ;
- * $cf->seed (
- * array ( 12345 ) ,
- * array ( "Category 1","Category 2" ) ,
- * "AND"
- * ) ;
- * $a = $cf->run() ;
- * print implode ( "," , $a ) ;
+ * $cf = new Categoryfinder;
+ * $cf->seed(
+ * array( 12345 ),
+ * array( 'Category 1', 'Category 2' ),
+ * 'AND'
+ * );
+ * $a = $cf->run();
+ * print implode( ',' , $a );
* </code>
*
*/
@@ -43,7 +43,7 @@ class Categoryfinder {
* @param $categories FIXME
* @param $mode String: FIXME, default 'AND'.
*/
- function seed( $article_ids, $categories, $mode = "AND" ) {
+ function seed( $article_ids, $categories, $mode = 'AND' ) {
$this->articles = $article_ids;
$this->next = $article_ids;
$this->mode = $mode;
@@ -64,9 +64,9 @@ class Categoryfinder {
* then checks the articles if they match the conditions
* @return array of page_ids (those given to seed() that match the conditions)
*/
- function run () {
+ function run() {
$this->dbr = wfGetDB( DB_SLAVE );
- while ( count ( $this->next ) > 0 ) {
+ while ( count( $this->next ) > 0 ) {
$this->scan_next_layer();
}
@@ -90,7 +90,7 @@ class Categoryfinder {
* @param $path used to check for recursion loops
* @return bool Does this match the conditions?
*/
- function check( $id , &$conds, $path = array() ) {
+ function check( $id, &$conds, $path = array() ) {
// Check for loops and stop!
if ( in_array( $id, $path ) ) {
return false;
@@ -114,13 +114,13 @@ class Categoryfinder {
# Is this a condition?
if ( isset( $conds[$pname] ) ) {
# This key is in the category list!
- if ( $this->mode == "OR" ) {
+ if ( $this->mode == 'OR' ) {
# One found, that's enough!
$conds = array();
return true;
} else {
# Assuming "AND" as default
- unset( $conds[$pname] ) ;
+ unset( $conds[$pname] );
if ( count( $conds ) == 0 ) {
# All conditions met, done
return true;
@@ -131,7 +131,7 @@ class Categoryfinder {
# Not done yet, try sub-parents
if ( !isset( $this->name2id[$pname] ) ) {
# No sub-parent
- continue ;
+ continue;
}
$done = $this->check( $this->name2id[$pname], $conds, $path );
if ( $done || count( $conds ) == 0 ) {
@@ -152,10 +152,10 @@ class Categoryfinder {
/* FROM */ 'categorylinks',
/* SELECT */ '*',
/* WHERE */ array( 'cl_from' => $this->next ),
- __METHOD__ . "-1"
+ __METHOD__ . '-1'
);
- while ( $o = $this->dbr->fetchObject( $res ) ) {
- $k = $o->cl_to ;
+ foreach ( $res as $o ) {
+ $k = $o->cl_to;
# Update parent tree
if ( !isset( $this->parents[$o->cl_from] ) ) {
@@ -164,9 +164,13 @@ class Categoryfinder {
$this->parents[$o->cl_from][$k] = $o;
# Ignore those we already have
- if ( in_array ( $k , $this->deadend ) ) continue;
+ if ( in_array( $k, $this->deadend ) ) {
+ continue;
+ }
- if ( isset ( $this->name2id[$k] ) ) continue;
+ if ( isset( $this->name2id[$k] ) ) {
+ continue;
+ }
# Hey, new category!
$layer[$k] = $k;
@@ -175,14 +179,14 @@ class Categoryfinder {
$this->next = array();
# Find the IDs of all category pages in $layer, if they exist
- if ( count ( $layer ) > 0 ) {
+ if ( count( $layer ) > 0 ) {
$res = $this->dbr->select(
/* FROM */ 'page',
/* SELECT */ array( 'page_id', 'page_title' ),
/* WHERE */ array( 'page_namespace' => NS_CATEGORY , 'page_title' => $layer ),
- __METHOD__ . "-2"
+ __METHOD__ . '-2'
);
- while ( $o = $this->dbr->fetchObject( $res ) ) {
+ foreach ( $res as $o ) {
$id = $o->page_id;
$name = $o->page_title;
$this->name2id[$name] = $id;
diff --git a/includes/Cdb.php b/includes/Cdb.php
index ab429872..60477485 100644
--- a/includes/Cdb.php
+++ b/includes/Cdb.php
@@ -1,4 +1,9 @@
<?php
+/**
+ * Native CDB file reader and writer
+ *
+ * @file
+ */
/**
* Read from a CDB file.
@@ -93,7 +98,7 @@ class CdbReader_DBA {
function __construct( $fileName ) {
$this->handle = dba_open( $fileName, 'r-', 'cdb' );
if ( !$this->handle ) {
- throw new MWException( 'Unable to open DB file "' . $fileName . '"' );
+ throw new MWException( 'Unable to open CDB file "' . $fileName . '"' );
}
}
@@ -120,7 +125,7 @@ class CdbWriter_DBA {
$this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
$this->handle = dba_open( $this->tmpFileName, 'n', 'cdb_make' );
if ( !$this->handle ) {
- throw new MWException( 'Unable to open DB file for write "' . $fileName . '"' );
+ throw new MWException( 'Unable to open CDB file for write "' . $fileName . '"' );
}
}
diff --git a/includes/Cdb_PHP.php b/includes/Cdb_PHP.php
index 49294f71..1485cc66 100644
--- a/includes/Cdb_PHP.php
+++ b/includes/Cdb_PHP.php
@@ -1,11 +1,12 @@
<?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
+ *
+ * @file
*/
/**
@@ -96,7 +97,7 @@ class CdbReader_PHP extends CdbReader {
function __construct( $fileName ) {
$this->handle = fopen( $fileName, 'rb' );
if ( !$this->handle ) {
- throw new MWException( 'Unable to open DB file "' . $fileName . '"' );
+ throw new MWException( 'Unable to open CDB file "' . $fileName . '"' );
}
$this->findStart();
}
@@ -137,7 +138,7 @@ class CdbReader_PHP extends CdbReader {
$buf = fread( $this->handle, $length );
if ( $buf === false || strlen( $buf ) !== $length ) {
- throw new MWException( __METHOD__.': read from cdb file failed, file may be corrupted' );
+ throw new MWException( __METHOD__.': read from CDB file failed, file may be corrupted' );
}
return $buf;
}
@@ -223,7 +224,7 @@ class CdbWriter_PHP extends CdbWriter {
$this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
$this->handle = fopen( $this->tmpFileName, 'wb' );
if ( !$this->handle ) {
- throw new MWException( 'Unable to open DB file for write "' . $fileName . '"' );
+ throw new MWException( 'Unable to open CDB file for write "' . $fileName . '"' );
}
$this->hplist = array();
$this->numentries = 0;
diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php
index 8dce679b..7f0fee21 100644
--- a/includes/ChangeTags.php
+++ b/includes/ChangeTags.php
@@ -119,7 +119,6 @@ class ChangeTags {
}
// Figure out which conditions can be done.
- $join_field = '';
if ( in_array( 'recentchanges', $tables ) ) {
$join_cond = 'rc_id';
} elseif( in_array( 'logging', $tables ) ) {
@@ -168,10 +167,10 @@ class ChangeTags {
return $data;
}
- $html = implode( '&nbsp;', $data );
+ $html = implode( '&#160;', $data );
$html .= "\n" . Xml::element( 'input', array( 'type' => 'submit', 'value' => wfMsg( 'tag-filter-submit' ) ) );
- $html .= "\n" . Xml::hidden( 'title', $wgTitle-> getPrefixedText() );
- $html = Xml::tags( 'form', array( 'action' => $wgTitle->getLocalURL(), 'method' => 'get' ), $html );
+ $html .= "\n" . Html::hidden( 'title', $title->getPrefixedText() );
+ $html = Xml::tags( 'form', array( 'action' => $title->getLocalURL(), 'method' => 'get' ), $html );
return $html;
}
@@ -181,16 +180,17 @@ class ChangeTags {
// Caching...
global $wgMemc;
$key = wfMemcKey( 'valid-tags' );
-
- if ( $tags = $wgMemc->get( $key ) )
+ $tags = $wgMemc->get( $key );
+ if ( $tags ) {
return $tags;
+ }
$emptyTags = array();
// Some DB stuff
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'valid_tag', 'vt_tag', array(), __METHOD__ );
- while( $row = $res->fetchObject() ) {
+ foreach ( $res as $row ) {
$emptyTags[] = $row->vt_tag;
}
diff --git a/includes/ChangesFeed.php b/includes/ChangesFeed.php
index bc50fe02..f07b6505 100644
--- a/includes/ChangesFeed.php
+++ b/includes/ChangesFeed.php
@@ -27,8 +27,8 @@ class ChangesFeed {
* @return ChannelFeed subclass or false on failure
*/
public function getFeedObject( $title, $description ) {
- global $wgSitename, $wgContLanguageCode, $wgFeedClasses, $wgTitle;
- $feedTitle = "$wgSitename - {$title} [$wgContLanguageCode]";
+ global $wgSitename, $wgLanguageCode, $wgFeedClasses, $wgTitle;
+ $feedTitle = "$wgSitename - {$title} [$wgLanguageCode]";
if( !isset($wgFeedClasses[$this->format] ) )
return false;
return new $wgFeedClasses[$this->format](
@@ -45,18 +45,17 @@ class ChangesFeed {
* @return null or true
*/
public function execute( $feed, $rows, $lastmod, $opts ) {
- global $messageMemc, $wgFeedCacheTimeout;
- global $wgSitename, $wgLang;
+ global $wgLang, $wgRenderHashAppend;
if ( !FeedUtils::checkFeedOutput( $this->format ) ) {
return;
}
- $timekey = wfMemcKey( $this->type, $this->format, 'timestamp' );
- $optionsHash = md5( serialize( $opts->getAllValues() ) );
+ $optionsHash = md5( serialize( $opts->getAllValues() ) ) . $wgRenderHashAppend;
+ $timekey = wfMemcKey( $this->type, $this->format, $wgLang->getCode(), $optionsHash, 'timestamp' );
$key = wfMemcKey( $this->type, $this->format, $wgLang->getCode(), $optionsHash );
- FeedUtils::checkPurge($timekey, $key);
+ FeedUtils::checkPurge( $timekey, $key );
/*
* Bumping around loading up diffs can be pretty slow, so where
@@ -102,7 +101,8 @@ class ChangesFeed {
* @return feed's content on cache hit or false on cache miss
*/
public function loadFromCache( $lastmod, $timekey, $key ) {
- global $wgFeedCacheTimeout, $messageMemc;
+ global $wgFeedCacheTimeout, $wgOut, $messageMemc;
+
$feedLastmod = $messageMemc->get( $timekey );
if( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) {
@@ -119,6 +119,9 @@ class ChangesFeed {
if( $feedAge < $wgFeedCacheTimeout || $feedLastmodUnix > $lastmodUnix) {
wfDebug( "RC: loading feed from cache ($key; $feedLastmod; $lastmod)...\n" );
+ if ( $feedLastmodUnix < $lastmodUnix ) {
+ $wgOut->setLastModified( $feedLastmod ); // bug 21916
+ }
return $messageMemc->get( $key );
} else {
wfDebug( "RC: cached feed timestamp check failed ($feedLastmod; $lastmod)\n" );
diff --git a/includes/ChangesList.php b/includes/ChangesList.php
index 9f092991..b8bc4f55 100644
--- a/includes/ChangesList.php
+++ b/includes/ChangesList.php
@@ -1,4 +1,12 @@
<?php
+/**
+ * Classes to show various lists of changes:
+ * - watchlist
+ * - related changes
+ * - recent changes
+ *
+ * @file
+ */
/**
* @todo document
@@ -17,13 +25,9 @@ class RCCacheEntry extends RecentChange {
}
/**
- * Class to show various lists of changes:
- * - what links here
- * - related changes
- * - recent changes
+ * Base class for all changes lists
*/
class ChangesList {
- # Called by history lists and recent changes
public $skin;
protected $watchlist = false;
@@ -44,11 +48,13 @@ class ChangesList {
* @return ChangesList derivative
*/
public static function newFromUser( &$user ) {
+ global $wgRequest;
+
$sk = $user->getSkin();
$list = null;
if( wfRunHooks( 'FetchChangesList', array( &$user, &$sk, &$list ) ) ) {
- return $user->getOption( 'usenewrc' ) ?
- new EnhancedChangesList( $sk ) : new OldChangesList( $sk );
+ $new = $wgRequest->getBool( 'enhanced', $user->getOption( 'usenewrc' ) );
+ return $new ? new EnhancedChangesList( $sk ) : new OldChangesList( $sk );
} else {
return $list;
}
@@ -85,7 +91,7 @@ class ChangesList {
* @param $bot Boolean
* @return String
*/
- protected function recentChangesFlags( $new, $minor, $patrolled, $nothing = '&nbsp;', $bot = false ) {
+ protected function recentChangesFlags( $new, $minor, $patrolled, $nothing = '&#160;', $bot = false ) {
$f = $new ? self::flag( 'newpage' ) : $nothing;
$f .= $minor ? self::flag( 'minor' ) : $nothing;
$f .= $bot ? self::flag( 'bot' ) : $nothing;
@@ -124,47 +130,6 @@ class ChangesList {
}
/**
- * Some explanatory wrapper text for the given flag, to be used in a legend
- * explaining what the flags mean. For instance, "N - new page". See
- * also flag().
- *
- * @param $key String: 'newpage', 'unpatrolled', 'minor', or 'bot'
- * @return String: Raw HTML
- */
- private static function flagLine( $key ) {
- return wfMsgExt( "recentchanges-legend-$key", array( 'escapenoentities',
- 'replaceafter' ), self::flag( $key ) );
- }
-
- /**
- * A handy legend to tell users what the little "m", "b", and so on mean.
- *
- * @return String: Raw HTML
- */
- public static function flagLegend() {
- global $wgGroupPermissions, $wgLang;
-
- $flags = array( self::flagLine( 'newpage' ),
- self::flagLine( 'minor' ) );
-
- # Don't show info on bot edits unless there's a bot group of some kind
- foreach ( $wgGroupPermissions as $rights ) {
- if ( isset( $rights['bot'] ) && $rights['bot'] ) {
- $flags[] = self::flagLine( 'bot' );
- break;
- }
- }
-
- if ( self::usePatrol() ) {
- $flags[] = self::flagLine( 'unpatrolled' );
- }
-
- return '<div class="mw-rc-label-legend">' .
- wfMsgExt( 'recentchanges-label-legend', 'parseinline',
- $wgLang->commaList( $flags ) ) . '</div>';
- }
-
- /**
* Returns text for the start of the tabular part of RC
* @return String
*/
@@ -595,14 +560,14 @@ class EnhancedChangesList extends ChangesList {
* @return String
*/
public function beginRecentChangesList() {
- global $wgStylePath, $wgStyleVersion;
+ global $wgOut;
$this->rc_cache = array();
$this->rcMoveIndex = 0;
$this->rcCacheIndex = 0;
$this->lastdate = '';
$this->rclistOpen = false;
- $script = Html::linkedScript( $wgStylePath . "/common/enhancedchanges.js?$wgStyleVersion" );
- return $script;
+ $wgOut->addModules( 'mediawiki.legacy.enhancedchanges' );
+ return '';
}
/**
* Format a line for enhanced recentchange (aka with javascript and block of lines).
@@ -628,7 +593,7 @@ class EnhancedChangesList extends ChangesList {
# Process current cache
$ret = $this->recentChangesBlock();
$this->rc_cache = array();
- $ret .= Xml::element( 'h4', null, $date );
+ $ret .= Xml::element( 'h4', null, $date ) . "\n";
$this->lastdate = $date;
}
@@ -771,7 +736,15 @@ class EnhancedChangesList extends ChangesList {
wfProfileIn( __METHOD__ );
- $r = '<table class="mw-enhanced-rc"><tr>';
+ # Add the namespace and title of the block as part of the class
+ if ( $block[0]->mAttribs['rc_log_type'] ) {
+ # Log entry
+ $classes = 'mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-log-' . $block[0]->mAttribs['rc_log_type'] . '-' . $block[0]->mAttribs['rc_title'] );
+ } else {
+ $classes = 'mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-ns' . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
+ }
+ $r = Html::openElement( 'table', array( 'class' => $classes ) ) .
+ Html::openElement( 'tr' );
# Collate list of users
$userlinks = array();
@@ -841,13 +814,13 @@ class EnhancedChangesList extends ChangesList {
$tl = "<span id='mw-rc-openarrow-$jsid' class='mw-changeslist-expanded' style='visibility:hidden'><a href='#' $toggleLink title='$expandTitle'>" . $this->sideArrow() . "</a></span>";
$tl .= "<span id='mw-rc-closearrow-$jsid' class='mw-changeslist-hidden' style='display:none'><a href='#' $toggleLink title='$closeTitle'>" . $this->downArrow() . "</a></span>";
- $r .= '<td class="mw-enhanced-rc">'.$tl.'&nbsp;';
+ $r .= '<td class="mw-enhanced-rc">'.$tl.'&#160;';
# Main line
- $r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, '&nbsp;', $bot );
+ $r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, '&#160;', $bot );
# Timestamp
- $r .= '&nbsp;'.$block[0]->timestamp.'&nbsp;</td><td style="padding:0px;">';
+ $r .= '&#160;'.$block[0]->timestamp.'&#160;</td><td style="padding:0px;">';
# Article link
if( $namehidden ) {
@@ -951,8 +924,8 @@ class EnhancedChangesList extends ChangesList {
#$r .= '<tr><td valign="top">'.$this->spacerArrow();
$r .= '<tr><td style="vertical-align:top;font-family:monospace; padding:0px;">';
$r .= $this->spacerIndent() . $this->spacerIndent();
- $r .= $this->recentChangesFlags( $rc_new, $rc_minor, $rcObj->unpatrolled, '&nbsp;', $rc_bot );
- $r .= '&nbsp;</td><td style="vertical-align:top; padding:0px;"><span style="font-family:monospace">';
+ $r .= $this->recentChangesFlags( $rc_new, $rc_minor, $rcObj->unpatrolled, '&#160;', $rc_bot );
+ $r .= '&#160;</td><td style="vertical-align:top; padding:0px;"><span style="font-family:monospace">';
$params = $queryParams;
@@ -1067,7 +1040,7 @@ class EnhancedChangesList extends ChangesList {
* @return String: HTML <td> tag
*/
protected function spacerIndent() {
- return '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
+ return '&#160;&#160;&#160;&#160;&#160;';
}
/**
@@ -1082,19 +1055,27 @@ class EnhancedChangesList extends ChangesList {
# Extract fields from DB into the function scope (rc_xxxx variables)
// FIXME: Would be good to replace this extract() call with something
// that explicitly initializes variables.
- $classes = array(); // TODO implement
+ // TODO implement
extract( $rcObj->mAttribs );
$query['curid'] = $rc_cur_id;
- $r = '<table class="mw-enhanced-rc"><tr>';
- $r .= '<td class="mw-enhanced-rc">' . $this->spacerArrow() . '&nbsp;';
+ if( $rc_log_type ) {
+ # Log entry
+ $classes = 'mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-log-' . $rc_log_type . '-' . $rcObj->mAttribs['rc_title'] );
+ } else {
+ $classes = 'mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-ns' . $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
+ }
+ $r = Html::openElement( 'table', array( 'class' => $classes ) ) .
+ Html::openElement( 'tr' );
+
+ $r .= '<td class="mw-enhanced-rc">' . $this->spacerArrow() . '&#160;';
# Flag and Timestamp
if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
- $r .= '&nbsp;&nbsp;&nbsp;&nbsp;'; // 4 flags -> 4 spaces
+ $r .= '&#160;&#160;&#160;&#160;'; // 4 flags -> 4 spaces
} else {
- $r .= $this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $rcObj->unpatrolled, '&nbsp;', $rc_bot );
+ $r .= $this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $rcObj->unpatrolled, '&#160;', $rc_bot );
}
- $r .= '&nbsp;'.$rcObj->timestamp.'&nbsp;</td><td style="padding:0px;">';
+ $r .= '&#160;'.$rcObj->timestamp.'&#160;</td><td style="padding:0px;">';
# Article or log link
if( $rc_log_type ) {
$logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
@@ -1140,6 +1121,7 @@ class EnhancedChangesList extends ChangesList {
$this->insertComment( $r, $rcObj );
$this->insertRollback( $r, $rcObj );
# Tags
+ $classes = explode( ' ', $classes );
$this->insertTags( $r, $rcObj, $classes );
# Show how many people are watching this if enabled
$r .= $this->numberofWatchingusers($rcObj->numberofWatchingusers);
diff --git a/includes/Collation.php b/includes/Collation.php
new file mode 100644
index 00000000..f00b568f
--- /dev/null
+++ b/includes/Collation.php
@@ -0,0 +1,307 @@
+<?php
+
+abstract class Collation {
+ static $instance;
+
+ static function singleton() {
+ if ( !self::$instance ) {
+ global $wgCategoryCollation;
+ self::$instance = self::factory( $wgCategoryCollation );
+ }
+ return self::$instance;
+ }
+
+ static function factory( $collationName ) {
+ switch( $collationName ) {
+ case 'uppercase':
+ return new UppercaseCollation;
+ case 'uca-default':
+ return new IcuCollation( 'root' );
+ default:
+ throw new MWException( __METHOD__.": unknown collation type \"$collationName\"" );
+ }
+ }
+
+ /**
+ * Given a string, convert it to a (hopefully short) key that can be used
+ * for efficient sorting. A binary sort according to the sortkeys
+ * corresponds to a logical sort of the corresponding strings. Current
+ * code expects that a line feed character should sort before all others, but
+ * has no other particular expectations (and that one can be changed if
+ * necessary).
+ *
+ * @param string $string UTF-8 string
+ * @return string Binary sortkey
+ */
+ abstract function getSortKey( $string );
+
+ /**
+ * Given a string, return the logical "first letter" to be used for
+ * grouping on category pages and so on. This has to be coordinated
+ * carefully with convertToSortkey(), or else the sorted list might jump
+ * back and forth between the same "initial letters" or other pathological
+ * behavior. For instance, if you just return the first character, but "a"
+ * sorts the same as "A" based on getSortKey(), then you might get a
+ * list like
+ *
+ * == A ==
+ * * [[Aardvark]]
+ *
+ * == a ==
+ * * [[antelope]]
+ *
+ * == A ==
+ * * [[Ape]]
+ *
+ * etc., assuming for the sake of argument that $wgCapitalLinks is false.
+ *
+ * @param string $string UTF-8 string
+ * @return string UTF-8 string corresponding to the first letter of input
+ */
+ abstract function getFirstLetter( $string );
+}
+
+class UppercaseCollation extends Collation {
+ var $lang;
+ function __construct() {
+ // Get a language object so that we can use the generic UTF-8 uppercase
+ // function there
+ $this->lang = Language::factory( 'en' );
+ }
+
+ function getSortKey( $string ) {
+ return $this->lang->uc( $string );
+ }
+
+ function getFirstLetter( $string ) {
+ if ( $string[0] == "\0" ) {
+ $string = substr( $string, 1 );
+ }
+ return $this->lang->ucfirst( $this->lang->firstChar( $string ) );
+ }
+}
+
+class IcuCollation extends Collation {
+ var $primaryCollator, $mainCollator, $locale;
+ var $firstLetterData;
+
+ /**
+ * Unified CJK blocks.
+ *
+ * The same definition of a CJK block must be used for both Collation and
+ * generateCollationData.php. These blocks are omitted from the first
+ * letter data, as an optimisation measure and because the default UCA table
+ * is pretty useless for sorting Chinese text anyway. Japanese and Korean
+ * blocks are not included here, because they are smaller and more useful.
+ */
+ static $cjkBlocks = array(
+ array( 0x2E80, 0x2EFF ), // CJK Radicals Supplement
+ array( 0x2F00, 0x2FDF ), // Kangxi Radicals
+ array( 0x2FF0, 0x2FFF ), // Ideographic Description Characters
+ array( 0x3000, 0x303F ), // CJK Symbols and Punctuation
+ array( 0x31C0, 0x31EF ), // CJK Strokes
+ array( 0x3200, 0x32FF ), // Enclosed CJK Letters and Months
+ array( 0x3300, 0x33FF ), // CJK Compatibility
+ array( 0x3400, 0x4DBF ), // CJK Unified Ideographs Extension A
+ array( 0x4E00, 0x9FFF ), // CJK Unified Ideographs
+ array( 0xF900, 0xFAFF ), // CJK Compatibility Ideographs
+ array( 0xFE30, 0xFE4F ), // CJK Compatibility Forms
+ array( 0x20000, 0x2A6DF ), // CJK Unified Ideographs Extension B
+ array( 0x2A700, 0x2B73F ), // CJK Unified Ideographs Extension C
+ array( 0x2B740, 0x2B81F ), // CJK Unified Ideographs Extension D
+ array( 0x2F800, 0x2FA1F ), // CJK Compatibility Ideographs Supplement
+ );
+
+ const RECORD_LENGTH = 14;
+
+ function __construct( $locale ) {
+ if ( !extension_loaded( 'intl' ) ) {
+ throw new MWException( 'An ICU collation was requested, ' .
+ 'but the intl extension is not available.' );
+ }
+ $this->locale = $locale;
+ $this->mainCollator = Collator::create( $locale );
+ if ( !$this->mainCollator ) {
+ throw new MWException( "Invalid ICU locale specified for collation: $locale" );
+ }
+
+ $this->primaryCollator = Collator::create( $locale );
+ $this->primaryCollator->setStrength( Collator::PRIMARY );
+ }
+
+ function getSortKey( $string ) {
+ // intl extension produces non null-terminated
+ // strings. Appending '' fixes it so that it doesn't generate
+ // a warning on each access in debug php.
+ wfSuppressWarnings();
+ $key = $this->mainCollator->getSortKey( $string ) . '';
+ wfRestoreWarnings();
+ return $key;
+ }
+
+ function getPrimarySortKey( $string ) {
+ wfSuppressWarnings();
+ $key = $this->primaryCollator->getSortKey( $string ) . '';
+ wfRestoreWarnings();
+ return $key;
+ }
+
+ function getFirstLetter( $string ) {
+ $string = strval( $string );
+ if ( $string === '' ) {
+ return '';
+ }
+
+ // Check for CJK
+ $firstChar = mb_substr( $string, 0, 1, 'UTF-8' );
+ if ( ord( $firstChar ) > 0x7f
+ && self::isCjk( utf8ToCodepoint( $firstChar ) ) )
+ {
+ return $firstChar;
+ }
+
+ $sortKey = $this->getPrimarySortKey( $string );
+
+ // Do a binary search to find the correct letter to sort under
+ $min = $this->findLowerBound(
+ array( $this, 'getSortKeyByLetterIndex' ),
+ $this->getFirstLetterCount(),
+ 'strcmp',
+ $sortKey );
+
+ if ( $min === false ) {
+ // Before the first letter
+ return '';
+ }
+ return $this->getLetterByIndex( $min );
+ }
+
+ function getFirstLetterData() {
+ if ( $this->firstLetterData !== null ) {
+ return $this->firstLetterData;
+ }
+
+ $cache = wfGetCache( CACHE_ANYTHING );
+ $cacheKey = wfMemcKey( 'first-letters', $this->locale );
+ $cacheEntry = $cache->get( $cacheKey );
+
+ if ( $cacheEntry ) {
+ $this->firstLetterData = $cacheEntry;
+ return $this->firstLetterData;
+ }
+
+ // Generate data from serialized data file
+
+ $letters = wfGetPrecompiledData( "first-letters-{$this->locale}.ser" );
+ if ( $letters === false ) {
+ throw new MWException( "MediaWiki does not support ICU locale " .
+ "\"{$this->locale}\"" );
+ }
+
+ // Sort the letters.
+ //
+ // It's impossible to have the precompiled data file properly sorted,
+ // because the sort order changes depending on ICU version. If the
+ // array is not properly sorted, the binary search will return random
+ // results.
+ //
+ // We also take this opportunity to remove primary collisions.
+ $letterMap = array();
+ foreach ( $letters as $letter ) {
+ $key = $this->getPrimarySortKey( $letter );
+ if ( isset( $letterMap[$key] ) ) {
+ // Primary collision
+ // Keep whichever one sorts first in the main collator
+ if ( $this->mainCollator->compare( $letter, $letterMap[$key] ) < 0 ) {
+ $letterMap[$key] = $letter;
+ }
+ } else {
+ $letterMap[$key] = $letter;
+ }
+ }
+ ksort( $letterMap, SORT_STRING );
+ $data = array(
+ 'chars' => array_values( $letterMap ),
+ 'keys' => array_keys( $letterMap )
+ );
+
+ // Reduce memory usage before caching
+ unset( $letterMap );
+
+ // Save to cache
+ $this->firstLetterData = $data;
+ $cache->set( $cacheKey, $data, 86400 * 7 /* 1 week */ );
+ return $data;
+ }
+
+ function getLetterByIndex( $index ) {
+ if ( $this->firstLetterData === null ) {
+ $this->getFirstLetterData();
+ }
+ return $this->firstLetterData['chars'][$index];
+ }
+
+ function getSortKeyByLetterIndex( $index ) {
+ if ( $this->firstLetterData === null ) {
+ $this->getFirstLetterData();
+ }
+ return $this->firstLetterData['keys'][$index];
+ }
+
+ function getFirstLetterCount() {
+ if ( $this->firstLetterData === null ) {
+ $this->getFirstLetterData();
+ }
+ return count( $this->firstLetterData['chars'] );
+ }
+
+ /**
+ * Do a binary search, and return the index of the largest item that sorts
+ * less than or equal to the target value.
+ *
+ * @param $valueCallback A function to call to get the value with
+ * a given array index.
+ * @param $valueCount The number of items accessible via $valueCallback,
+ * indexed from 0 to $valueCount - 1
+ * @param $comparisonCallback A callback to compare two values, returning
+ * -1, 0 or 1 in the style of strcmp().
+ * @param $target The target value to find.
+ *
+ * @return The item index of the lower bound, or false if the target value
+ * sorts before all items.
+ */
+ function findLowerBound( $valueCallback, $valueCount, $comparisonCallback, $target ) {
+ $min = 0;
+ $max = $valueCount - 1;
+ do {
+ $mid = $min + ( ( $max - $min ) >> 1 );
+ $item = call_user_func( $valueCallback, $mid );
+ $comparison = call_user_func( $comparisonCallback, $target, $item );
+ if ( $comparison > 0 ) {
+ $min = $mid;
+ } elseif ( $comparison == 0 ) {
+ $min = $mid;
+ break;
+ } else {
+ $max = $mid;
+ }
+ } while ( $min < $max - 1 );
+
+ if ( $min == 0 && $max == 0 && $comparison > 0 ) {
+ // Before the first item
+ return false;
+ } else {
+ return $min;
+ }
+ }
+
+ static function isCjk( $codepoint ) {
+ foreach ( self::$cjkBlocks as $block ) {
+ if ( $codepoint >= $block[0] && $codepoint <= $block[1] ) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
diff --git a/includes/ConfEditor.php b/includes/ConfEditor.php
index f862ebb7..b08b77df 100644
--- a/includes/ConfEditor.php
+++ b/includes/ConfEditor.php
@@ -72,7 +72,7 @@ class ConfEditor {
var $pathInfo;
/**
- * Next serial number for whitespace placeholder paths (@extra-N)
+ * Next serial number for whitespace placeholder paths (\@extra-N)
*/
var $serial;
@@ -104,7 +104,7 @@ class ConfEditor {
/**
* Edit the text. Returns the edited text.
- * @param array $ops Array of operations.
+ * @param $ops Array of operations.
*
* Operations are given as an associative array, with members:
* type: One of delete, set, append or insert (required)
@@ -176,7 +176,7 @@ class ConfEditor {
// Has it got a comma already?
if ( strpos( $lastEltPath, '@extra' ) === false && !$lastEltInfo['hasComma'] ) {
// No comma, insert one after the value region
- list( $start, $end ) = $this->findValueRegion( $lastEltPath );
+ list( , $end ) = $this->findValueRegion( $lastEltPath );
$this->replaceSourceRegion( $end - 1, $end - 1, ',' );
}
@@ -184,7 +184,7 @@ class ConfEditor {
list( $start, $end ) = $this->findDeletionRegion( $lastEltPath );
if ( $key === null ) {
- list( $indent, $arrowIndent ) = $this->getIndent( $start );
+ list( $indent, ) = $this->getIndent( $start );
$textToInsert = "$indent$value,";
} else {
list( $indent, $arrowIndent ) =
@@ -202,12 +202,12 @@ class ConfEditor {
if ( $firstEltPath === false ) {
throw new MWException( "Can't find array element of \"$path\"" );
}
- list( $start, $end ) = $this->findDeletionRegion( $firstEltPath );
+ list( $start, ) = $this->findDeletionRegion( $firstEltPath );
$info = $this->pathInfo[$firstEltPath];
// Make the text to insert
if ( $key === null ) {
- list( $indent, $arrowIndent ) = $this->getIndent( $start );
+ list( $indent, ) = $this->getIndent( $start );
$textToInsert = "$indent$value,";
} else {
list( $indent, $arrowIndent ) =
@@ -336,7 +336,7 @@ class ConfEditor {
// Split all copy operations with a source corresponding to the region
// in question.
$newEdits = array();
- foreach ( $this->edits as $i => $edit ) {
+ foreach ( $this->edits as $edit ) {
if ( $edit[0] !== 'copy' ) {
$newEdits[] = $edit;
continue;
@@ -427,7 +427,7 @@ class ConfEditor {
*/
function findValueRegion( $pathName ) {
if ( !isset( $this->pathInfo[$pathName] ) ) {
- throw new MWEXception( "Can't find path \"$pathName\"" );
+ throw new MWException( "Can't find path \"$pathName\"" );
}
$path = $this->pathInfo[$pathName];
if ( $path['valueStartByte'] === false || $path['valueEndByte'] === false ) {
@@ -438,7 +438,7 @@ class ConfEditor {
/**
* Find the path name of the last element in the array.
- * If the array is empty, this will return the @extra interstitial element.
+ * If the array is empty, this will return the \@extra interstitial element.
* If the specified path is not found or is not an array, it will return false.
*/
function findLastArrayElement( $path ) {
@@ -474,7 +474,7 @@ class ConfEditor {
/*
* Find the path name of first element in the array.
- * If the array is empty, this will return the @extra interstitial element.
+ * If the array is empty, this will return the \@extra interstitial element.
* If the specified path is not found or is not an array, it will return false.
*/
function findFirstArrayElement( $path ) {
@@ -510,7 +510,6 @@ class ConfEditor {
$indent = false;
}
if ( $indent !== false && $arrowPos !== false ) {
- $textToInsert = "$indent$key ";
$arrowIndentLength = $arrowPos - $pos - $indentLength - strlen( $key );
if ( $arrowIndentLength > 0 ) {
$arrowIndent = str_repeat( ' ', $arrowIndentLength );
@@ -537,7 +536,7 @@ class ConfEditor {
switch ( $state ) {
case 'file':
- $token = $this->expect( T_OPEN_TAG );
+ $this->expect( T_OPEN_TAG );
$token = $this->skipSpace();
if ( $token->isEnd() ) {
break 2;
@@ -836,7 +835,6 @@ class ConfEditor {
* not call except from popPath() or nextPath().
*/
function endPath() {
- $i = count( $this->pathStack ) - 1;
$key = '';
foreach ( $this->pathStack as $pathInfo ) {
if ( $key !== '' ) {
@@ -878,7 +876,7 @@ class ConfEditor {
/**
* Go to the next path on the same level. This ends the current path and
- * starts a new one. If $path is @next, the new path is set to the next
+ * starts a new one. If $path is \@next, the new path is set to the next
* numeric array element.
*/
function nextPath( $path ) {
diff --git a/includes/Credits.php b/includes/Credits.php
index 91ba3f16..e4c8be54 100644
--- a/includes/Credits.php
+++ b/includes/Credits.php
@@ -1,50 +1,51 @@
<?php
/**
- * Credits.php -- formats credits for articles
+ * Formats credits for articles
+ *
* Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ * You should have received a copy of the GNU General Public 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
* @author <evan@wikitravel.org>
*/
class Credits {
-
/**
* This is largely cadged from PageHistory::history
* @param $article Article object
*/
public static function showPage( Article $article ) {
global $wgOut;
-
+
wfProfileIn( __METHOD__ );
-
+
$wgOut->setPageTitle( $article->mTitle->getPrefixedText() );
$wgOut->setSubtitle( wfMsg( 'creditspage' ) );
$wgOut->setArticleFlag( false );
$wgOut->setArticleRelated( true );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
-
- if( $article->mTitle->getArticleID() == 0 ) {
+
+ if ( $article->mTitle->getArticleID() == 0 ) {
$s = wfMsg( 'nocredits' );
} else {
- $s = self::getCredits($article, -1 );
+ $s = self::getCredits( $article, -1 );
}
-
+
$wgOut->addHTML( $s );
-
+
wfProfileOut( __METHOD__ );
}
@@ -59,7 +60,7 @@ class Credits {
wfProfileIn( __METHOD__ );
$s = '';
- if( isset( $cnt ) && $cnt != 0 ){
+ if ( isset( $cnt ) && $cnt != 0 ) {
$s = self::getAuthor( $article );
if ( $cnt > 1 || $cnt < 0 ) {
$s .= ' ' . self::getContributors( $article, $cnt - 1, $showIfMax );
@@ -74,13 +75,13 @@ class Credits {
* Get the last author with the last modification time
* @param $article Article object
*/
- protected static function getAuthor( Article $article ){
+ protected static function getAuthor( Article $article ) {
global $wgLang;
$user = User::newFromId( $article->getUser() );
$timestamp = $article->getTimestamp();
- if( $timestamp ){
+ if ( $timestamp ) {
$d = $wgLang->date( $article->getTimestamp(), true );
$t = $wgLang->time( $article->getTimestamp(), true );
} else {
@@ -99,62 +100,72 @@ class Credits {
*/
protected static function getContributors( Article $article, $cnt, $showIfMax ) {
global $wgLang, $wgHiddenPrefs;
-
+
$contributors = $article->getContributors();
-
+
$others_link = false;
-
+
# Hmm... too many to fit!
- if( $cnt > 0 && $contributors->count() > $cnt ){
+ if ( $cnt > 0 && $contributors->count() > $cnt ) {
$others_link = self::othersLink( $article );
- if( !$showIfMax )
- return wfMsg( 'othercontribs', $others_link );
+ if ( !$showIfMax )
+ return wfMsgExt( 'othercontribs', 'parsemag', $others_link, $contributors->count() );
}
-
+
$real_names = array();
$user_names = array();
$anon_ips = array();
-
+
# Sift for real versus user names
- foreach( $contributors as $user ) {
+ foreach ( $contributors as $user ) {
$cnt--;
- if( $user->isLoggedIn() ){
+ if ( $user->isLoggedIn() ) {
$link = self::link( $user );
- if( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() )
+ if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
$real_names[] = $link;
- else
+ } else {
$user_names[] = $link;
+ }
} else {
$anon_ips[] = self::link( $user );
}
- if( $cnt == 0 ) break;
+
+ if ( $cnt == 0 ) {
+ break;
+ }
}
-
+
if ( count( $real_names ) ) {
$real = $wgLang->listToText( $real_names );
} else {
$real = false;
}
-
+
# "ThisSite user(s) A, B and C"
- if( count( $user_names ) ){
- $user = wfMsgExt( 'siteusers', array( 'parsemag' ),
- $wgLang->listToText( $user_names ), count( $user_names ) );
+ if ( count( $user_names ) ) {
+ $user = wfMsgExt(
+ 'siteusers',
+ 'parsemag',
+ $wgLang->listToText( $user_names ), count( $user_names )
+ );
} else {
$user = false;
}
- if( count( $anon_ips ) ){
- $anon = wfMsgExt( 'anonusers', array( 'parsemag' ),
- $wgLang->listToText( $anon_ips ), count( $anon_ips ) );
+ if ( count( $anon_ips ) ) {
+ $anon = wfMsgExt(
+ 'anonusers',
+ 'parsemag',
+ $wgLang->listToText( $anon_ips ), count( $anon_ips )
+ );
} else {
$anon = false;
}
-
+
# This is the big list, all mooshed together. We sift for blank strings
$fulllist = array();
- foreach( array( $real, $user, $anon, $others_link ) as $s ){
- if( $s ){
+ foreach ( array( $real, $user, $anon, $others_link ) as $s ) {
+ if ( $s ) {
array_push( $fulllist, $s );
}
}
@@ -163,7 +174,9 @@ class Credits {
$creds = $wgLang->listToText( $fulllist );
# "Based on work by ..."
- return strlen( $creds ) ? wfMsg( 'othercontribs', $creds ) : '';
+ return strlen( $creds )
+ ? wfMsgExt( 'othercontribs', 'parsemag', $creds, count( $fulllist ) )
+ : '';
}
/**
@@ -173,10 +186,11 @@ class Credits {
*/
protected static function link( User $user ) {
global $wgUser, $wgHiddenPrefs;
- if( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() )
+ if ( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() ) {
$real = $user->getRealName();
- else
+ } else {
$real = false;
+ }
$skin = $wgUser->getSkin();
$page = $user->isAnon() ?
@@ -188,20 +202,20 @@ class Credits {
/**
* Get a link to $user's user page
- * @param $user_name String: user name
- * @param $linkText String: optional display
+ * @param $user User object
* @return String: html
*/
protected static function userLink( User $user ) {
$link = self::link( $user );
- if( $user->isAnon() ){
+ if ( $user->isAnon() ) {
return wfMsgExt( 'anonuser', array( 'parseinline', 'replaceafter' ), $link );
} else {
global $wgHiddenPrefs;
- if( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() )
+ if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
return $link;
- else
- return wfMsgExt( 'siteuser', array( 'parseinline', 'replaceafter' ), $link );
+ } else {
+ return wfMsgExt( 'siteuser', 'parsemag', $link, $user->getName() );
+ }
}
}
@@ -213,6 +227,12 @@ class Credits {
protected static function othersLink( Article $article ) {
global $wgUser;
$skin = $wgUser->getSkin();
- return $skin->link( $article->getTitle(), wfMsgHtml( 'others' ), array(), array( 'action' => 'credits' ), array( 'known' ) );
+ return $skin->link(
+ $article->getTitle(),
+ wfMsgHtml( 'others' ),
+ array(),
+ array( 'action' => 'credits' ),
+ array( 'known' )
+ );
}
}
diff --git a/includes/DatabaseFunctions.php b/includes/DatabaseFunctions.php
deleted file mode 100644
index 2df56115..00000000
--- a/includes/DatabaseFunctions.php
+++ /dev/null
@@ -1,412 +0,0 @@
-<?php
-/**
- * Legacy database functions, for compatibility with pre-1.3 code
- * NOTE: this file is no longer loaded by default.
- * @file
- * @ingroup Database
- */
-
-/**
- * Usually aborts on failure
- * If errors are explicitly ignored, returns success
- * @param $sql String: SQL query
- * @param $db Mixed: database handler
- * @param $fname String: name of the php function calling
- */
-function wfQuery( $sql, $db, $fname = '' ) {
- if ( !is_numeric( $db ) ) {
- # Someone has tried to call this the old way
- throw new FatalError( wfMsgNoDB( 'wrong_wfQuery_params', $db, $sql ) );
- }
- $c = wfGetDB( $db );
- if ( $c !== false ) {
- return $c->query( $sql, $fname );
- } else {
- return false;
- }
-}
-
-/**
- *
- * @param $sql String: SQL query
- * @param $dbi
- * @param $fname String: name of the php function calling
- * @return Array: first row from the database
- */
-function wfSingleQuery( $sql, $dbi, $fname = '' ) {
- $db = wfGetDB( $dbi );
- $res = $db->query($sql, $fname );
- $row = $db->fetchRow( $res );
- $ret = $row[0];
- $db->freeResult( $res );
- return $ret;
-}
-
-/**
- * Turns on (false) or off (true) the automatic generation and sending
- * of a "we're sorry, but there has been a database error" page on
- * database errors. Default is on (false). When turned off, the
- * code should use wfLastErrno() and wfLastError() to handle the
- * situation as appropriate.
- *
- * @param $newstate
- * @param $dbi
- * @return Returns the previous state.
- */
-function wfIgnoreSQLErrors( $newstate, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->ignoreErrors( $newstate );
- } else {
- return null;
- }
-}
-
-/**#@+
- * @param $res Database result handler
- * @param $dbi
-*/
-
-/**
- * Free a database result
- * @return Bool: whether result is sucessful or not.
- */
-function wfFreeResult( $res, $dbi = DB_LAST )
-{
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- $db->freeResult( $res );
- return true;
- } else {
- return false;
- }
-}
-
-/**
- * Get an object from a database result
- * @return object|false object we requested
- */
-function wfFetchObject( $res, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->fetchObject( $res, $dbi = DB_LAST );
- } else {
- return false;
- }
-}
-
-/**
- * Get a row from a database result
- * @return object|false row we requested
- */
-function wfFetchRow( $res, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->fetchRow ( $res, $dbi = DB_LAST );
- } else {
- return false;
- }
-}
-
-/**
- * Get a number of rows from a database result
- * @return integer|false number of rows
- */
-function wfNumRows( $res, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->numRows( $res, $dbi = DB_LAST );
- } else {
- return false;
- }
-}
-
-/**
- * Get the number of fields from a database result
- * @return integer|false number of fields
- */
-function wfNumFields( $res, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->numFields( $res );
- } else {
- return false;
- }
-}
-
-/**
- * Return name of a field in a result
- * @param $res Mixed: Ressource link see Database::fieldName()
- * @param $n Integer: id of the field
- * @param $dbi Default DB_LAST
- * @return string|false name of field
- */
-function wfFieldName( $res, $n, $dbi = DB_LAST )
-{
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->fieldName( $res, $n, $dbi = DB_LAST );
- } else {
- return false;
- }
-}
-/**#@-*/
-
-/**
- * @todo document function
- * @see Database::insertId()
- */
-function wfInsertId( $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->insertId();
- } else {
- return false;
- }
-}
-
-/**
- * @todo document function
- * @see Database::dataSeek()
- */
-function wfDataSeek( $res, $row, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->dataSeek( $res, $row );
- } else {
- return false;
- }
-}
-
-/**
- * Get the last error number
- * @see Database::lastErrno()
- */
-function wfLastErrno( $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->lastErrno();
- } else {
- return false;
- }
-}
-
-/**
- * Get the last error
- * @see Database::lastError()
- */
-function wfLastError( $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->lastError();
- } else {
- return false;
- }
-}
-
-/**
- * Get the number of affected rows
- * @see Database::affectedRows()
- */
-function wfAffectedRows( $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->affectedRows();
- } else {
- return false;
- }
-}
-
-/**
- * Get the last query ran
- * @see Database::lastQuery
- */
-function wfLastDBquery( $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->lastQuery();
- } else {
- return false;
- }
-}
-
-/**
- * @see Database::Set()
- * @todo document function
- * @param $table
- * @param $var
- * @param $value
- * @param $cond
- * @param $dbi Default DB_MASTER
- */
-function wfSetSQL( $table, $var, $value, $cond, $dbi = DB_MASTER )
-{
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->set( $table, $var, $value, $cond );
- } else {
- return false;
- }
-}
-
-
-/**
- * Simple select wrapper, return one field
- * @see Database::selectField()
- * @param $table
- * @param $var
- * @param $cond Default ''
- * @param $dbi Default DB_LAST
- */
-function wfGetSQL( $table, $var, $cond='', $dbi = DB_LAST )
-{
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->selectField( $table, $var, $cond );
- } else {
- return false;
- }
-}
-
-/**
- * Does a given field exist on the specified table?
- * @see Database::fieldExists()
- * @param $table
- * @param $field
- * @param $dbi Default DB_LAST
- * @return Result of Database::fieldExists() or false.
- */
-function wfFieldExists( $table, $field, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->fieldExists( $table, $field );
- } else {
- return false;
- }
-}
-
-/**
- * Does the requested index exist on the specified table?
- * @see Database::indexExists()
- * @param $table String
- * @param $index
- * @param $dbi Default DB_LAST
- * @return Result of Database::indexExists() or false.
- */
-function wfIndexExists( $table, $index, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->indexExists( $table, $index );
- } else {
- return false;
- }
-}
-
-/**
- * @see Database::insert()
- * @todo document function
- * @param $table String
- * @param $array Array
- * @param $fname String, default 'wfInsertArray'.
- * @param $dbi Default DB_MASTER
- * @return result of Database::insert() or false.
- */
-function wfInsertArray( $table, $array, $fname = 'wfInsertArray', $dbi = DB_MASTER ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->insert( $table, $array, $fname );
- } else {
- return false;
- }
-}
-
-/**
- * @see Database::getArray()
- * @todo document function
- * @param $table String
- * @param $vars
- * @param $conds
- * @param $fname String, default 'wfGetArray'.
- * @param $dbi Default DB_LAST
- * @return result of Database::getArray() or false.
- */
-function wfGetArray( $table, $vars, $conds, $fname = 'wfGetArray', $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->getArray( $table, $vars, $conds, $fname );
- } else {
- return false;
- }
-}
-
-/**
- * @see Database::update()
- * @param $table String
- * @param $values
- * @param $conds
- * @param $fname String, default 'wfUpdateArray'
- * @param $dbi Default DB_MASTER
- * @return Result of Database::update()) or false;
- * @todo document function
- */
-function wfUpdateArray( $table, $values, $conds, $fname = 'wfUpdateArray', $dbi = DB_MASTER ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- $db->update( $table, $values, $conds, $fname );
- return true;
- } else {
- return false;
- }
-}
-
-/**
- * Get fully usable table name
- * @see Database::tableName()
- */
-function wfTableName( $name, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->tableName( $name );
- } else {
- return false;
- }
-}
-
-/**
- * @todo document function
- * @see Database::strencode()
- */
-function wfStrencode( $s, $dbi = DB_LAST ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->strencode( $s );
- } else {
- return false;
- }
-}
-
-/**
- * @todo document function
- * @see Database::nextSequenceValue()
- */
-function wfNextSequenceValue( $seqName, $dbi = DB_MASTER ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->nextSequenceValue( $seqName );
- } else {
- return false;
- }
-}
-
-/**
- * @todo document function
- * @see Database::useIndexClause()
- */
-function wfUseIndexClause( $index, $dbi = DB_SLAVE ) {
- $db = wfGetDB( $dbi );
- if ( $db !== false ) {
- return $db->useIndexClause( $index );
- } else {
- return false;
- }
-}
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 54a96d44..ae6fc1b7 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -1,5 +1,6 @@
<?php
/**
+ * @file
*
* NEVER EDIT THIS FILE
*
@@ -14,80 +15,78 @@
*
* Documentation is in the source and on:
* http://www.mediawiki.org/wiki/Manual:Configuration_settings
- *
*/
-# This is not a valid entry point, perform no further processing unless MEDIAWIKI is defined
+/**
+ * @cond file_level_code
+ * This is not a valid entry point, perform no further processing unless MEDIAWIKI is defined
+ */
if( !defined( 'MEDIAWIKI' ) ) {
echo "This file is part of MediaWiki and is not a valid entry point\n";
die( 1 );
}
-/**
- * Create a site configuration object
- * Not used for much in a default install
- */
+# Create a site configuration object. Not used for much in a default install
if ( !defined( 'MW_PHP4' ) ) {
require_once( "$IP/includes/SiteConfiguration.php" );
$wgConf = new SiteConfiguration;
}
+/** @endcond */
/** MediaWiki version number */
-$wgVersion = '1.16.5';
+$wgVersion = '1.17.0';
/** Name of the site. It must be changed in LocalSettings.php */
$wgSitename = 'MediaWiki';
/**
- * Name of the project namespace. If left set to false, $wgSitename will be
- * used instead.
- */
-$wgMetaNamespace = false;
-
-/**
- * Name of the project talk namespace.
+ * URL of the server. It will be automatically built including https mode.
*
- * Normally you can ignore this and it will be something like
- * $wgMetaNamespace . "_talk". In some languages, you may want to set this
- * manually for grammatical reasons. It is currently only respected by those
- * languages where it might be relevant and where no automatic grammar converter
- * exists.
+ * Example:
+ * <code>
+ * $wgServer = http://example.com
+ * </code>
+ *
+ * This is usually detected correctly by MediaWiki. If MediaWiki detects the
+ * wrong server, it will redirect incorrectly after you save a page. In that
+ * case, set this variable to fix it.
*/
-$wgMetaNamespaceTalk = false;
-
-
-/** URL of the server. It will be automatically built including https mode */
$wgServer = '';
+/** @cond file_level_code */
if( isset( $_SERVER['SERVER_NAME'] ) ) {
- $wgServerName = $_SERVER['SERVER_NAME'];
+ $serverName = $_SERVER['SERVER_NAME'];
} elseif( isset( $_SERVER['HOSTNAME'] ) ) {
- $wgServerName = $_SERVER['HOSTNAME'];
+ $serverName = $_SERVER['HOSTNAME'];
} elseif( isset( $_SERVER['HTTP_HOST'] ) ) {
- $wgServerName = $_SERVER['HTTP_HOST'];
+ $serverName = $_SERVER['HTTP_HOST'];
} elseif( isset( $_SERVER['SERVER_ADDR'] ) ) {
- $wgServerName = $_SERVER['SERVER_ADDR'];
+ $serverName = $_SERVER['SERVER_ADDR'];
} else {
- $wgServerName = 'localhost';
+ $serverName = 'localhost';
}
-# check if server use https:
$wgProto = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
-$wgServer = $wgProto.'://' . $wgServerName;
+$wgServer = $wgProto.'://' . $serverName;
# If the port is a non-standard one, add it to the URL
if( isset( $_SERVER['SERVER_PORT'] )
- && !strpos( $wgServerName, ':' )
- && ( ( $wgProto == 'http' && $_SERVER['SERVER_PORT'] != 80 )
+ && !strpos( $serverName, ':' )
+ && ( ( $wgProto == 'http' && $_SERVER['SERVER_PORT'] != 80 )
|| ( $wgProto == 'https' && $_SERVER['SERVER_PORT'] != 443 ) ) ) {
$wgServer .= ":" . $_SERVER['SERVER_PORT'];
}
+/** @endcond */
+/************************************************************************//**
+ * @name Script path settings
+ * @{
+ */
/**
* The path we should point to.
- * It might be a virtual path in case with use apache mod_rewrite for example
+ * It might be a virtual path in case with use apache mod_rewrite for example.
*
* This *needs* to be set correctly.
*
@@ -117,85 +116,207 @@ $wgUsePathInfo =
( strpos( php_sapi_name(), 'apache2filter' ) === false ) &&
( strpos( php_sapi_name(), 'isapi' ) === false );
+/**
+ * The extension to append to script names by default. This can either be .php
+ * or .php5.
+ *
+ * Some hosting providers use PHP 4 for *.php files, and PHP 5 for *.php5. This
+ * variable is provided to support those providers.
+ */
+$wgScriptExtension = '.php';
-/**@{
- * Script users will request to get articles
- * ATTN: Old installations used wiki.phtml and redirect.phtml - make sure that
- * LocalSettings.php is correctly set!
+/**
+ * The URL path to index.php.
*
- * Will be set based on $wgScriptPath in Setup.php if not overridden in
- * LocalSettings.php. Generally you should not need to change this unless you
- * don't like seeing "index.php".
+ * Defaults to "{$wgScriptPath}/index{$wgScriptExtension}".
*/
-$wgScriptExtension = '.php'; ///< extension to append to script names by default
-$wgScript = false; ///< defaults to "{$wgScriptPath}/index{$wgScriptExtension}"
-$wgRedirectScript = false; ///< defaults to "{$wgScriptPath}/redirect{$wgScriptExtension}"
-/**@}*/
+$wgScript = false;
+/**
+ * The URL path to redirect.php. This is a script that is used by the Nostalgia
+ * skin.
+ *
+ * Defaults to "{$wgScriptPath}/redirect{$wgScriptExtension}".
+ */
+$wgRedirectScript = false; ///< defaults to
-/**@{
+/**
+ * The URL path to load.php.
+ *
+ * Defaults to "{$wgScriptPath}/load{$wgScriptExtension}".
+ */
+$wgLoadScript = false;
+
+/**@}*/
+
+/************************************************************************//**
+ * @name URLs and file paths
+ *
* These various web and file path variables are set to their defaults
* in Setup.php if they are not explicitly set from LocalSettings.php.
* If you do override them, be sure to set them all!
*
* These will relatively rarely need to be set manually, unless you are
* splitting style sheets or images outside the main document root.
+ *
+ * In this section, a "path" is usually a host-relative URL, i.e. a URL without
+ * the host part, that starts with a slash. In most cases a full URL is also
+ * acceptable. A "directory" is a local file path.
+ *
+ * In both paths and directories, trailing slashes should not be included.
+ *
+ * @{
*/
+
/**
- * asset paths as seen by users
+ * The URL path of the skins directory. Defaults to "{$wgScriptPath}/skins"
*/
-$wgStylePath = false; ///< defaults to "{$wgScriptPath}/skins"
-$wgExtensionAssetsPath = false; ///< defaults to "{$wgScriptPath}/extensions"
+$wgStylePath = false;
+$wgStyleSheetPath = &$wgStylePath;
/**
- * filesystem stylesheets directory
+ * The URL path of the skins directory. Should not point to an external domain.
+ * Defaults to "{$wgScriptPath}/skins".
+ */
+$wgLocalStylePath = false;
+
+/**
+ * The URL path of the extensions directory.
+ * Defaults to "{$wgScriptPath}/extensions".
+ */
+$wgExtensionAssetsPath = false;
+
+/**
+ * Filesystem stylesheets directory. Defaults to "{$IP}/skins"
+ */
+$wgStyleDirectory = false;
+
+/**
+ * The URL path for primary article page views. This path should contain $1,
+ * which is replaced by the article title.
+ *
+ * Defaults to "{$wgScript}/$1" or "{$wgScript}?title=$1", depending on
+ * $wgUsePathInfo.
+ */
+$wgArticlePath = false;
+
+/**
+ * The URL path for the images directory. Defaults to "{$wgScriptPath}/images"
+ */
+$wgUploadPath = false;
+
+/**
+ * The filesystem path of the images directory. Defaults to "{$IP}/images".
+ */
+$wgUploadDirectory = false;
+
+/**
+ * The URL path of the wiki logo. The logo size should be 135x135 pixels.
+ * Defaults to "{$wgStylePath}/common/images/wiki.png".
+ */
+$wgLogo = false;
+
+/**
+ * The URL path of the shortcut icon.
*/
-$wgStyleDirectory = false; ///< defaults to "{$IP}/skins"
-$wgStyleSheetPath = &$wgStylePath;
-$wgArticlePath = false; ///< default to "{$wgScript}/$1" or "{$wgScript}?title=$1", depending on $wgUsePathInfo
-$wgUploadPath = false; ///< defaults to "{$wgScriptPath}/images"
-$wgUploadDirectory = false; ///< defaults to "{$IP}/images"
-$wgHashedUploadDirectory = true;
-$wgLogo = false; ///< defaults to "{$wgStylePath}/common/images/wiki.png"
$wgFavicon = '/favicon.ico';
-$wgAppleTouchIcon = false; ///< This one'll actually default to off. For iPhone and iPod Touch web app bookmarks
-$wgMathPath = false; ///< defaults to "{$wgUploadPath}/math"
-$wgMathDirectory = false; ///< defaults to "{$wgUploadDirectory}/math"
-$wgTmpDirectory = false; ///< defaults to "{$wgUploadDirectory}/tmp"
-$wgUploadBaseUrl = "";
-/**@}*/
/**
- * Directory for caching data in the local filesystem. Should not be accessible
- * from the web. Set this to false to not use any local caches.
+ * The URL path of the icon for iPhone and iPod Touch web app bookmarks.
+ * Defaults to no icon.
+ */
+$wgAppleTouchIcon = false;
+
+/**
+ * The URL path of the math directory. Defaults to "{$wgUploadPath}/math".
*
- * Note: if multiple wikis share the same localisation cache directory, they
- * must all have the same set of extensions. You can set a directory just for
- * the localisation cache using $wgLocalisationCacheConf['storeDirectory'].
+ * See http://www.mediawiki.org/wiki/Manual:Enable_TeX for details about how to
+ * set up mathematical formula display.
*/
-$wgCacheDirectory = false;
+$wgMathPath = false;
/**
- * Default value for chmoding of new directories.
+ * The filesystem path of the math directory.
+ * Defaults to "{$wgUploadDirectory}/math".
+ *
+ * See http://www.mediawiki.org/wiki/Manual:Enable_TeX for details about how to
+ * set up mathematical formula display.
*/
-$wgDirectoryMode = 0777;
+$wgMathDirectory = false;
/**
- * New file storage paths; currently used only for deleted files.
- * Set it like this:
+ * The local filesystem path to a temporary directory. This is not required to
+ * be web accessible.
*
- * $wgFileStore['deleted']['directory'] = '/var/wiki/private/deleted';
+ * Defaults to "{$wgUploadDirectory}/tmp".
+ */
+$wgTmpDirectory = false;
+
+/**
+ * If set, this URL is added to the start of $wgUploadPath to form a complete
+ * upload URL.
+ */
+$wgUploadBaseUrl = "";
+
+/**
+ * To enable remote on-demand scaling, set this to the thumbnail base URL.
+ * Full thumbnail URL will be like $wgUploadStashScalerBaseUrl/e/e6/Foo.jpg/123px-Foo.jpg
+ * where 'e6' are the first two characters of the MD5 hash of the file name.
+ * If $wgUploadStashScalerBaseUrl is set to false, thumbs are rendered locally as needed.
+ */
+$wgUploadStashScalerBaseUrl = false;
+
+/**
+ * To set 'pretty' URL paths for actions other than
+ * plain page views, add to this array. For instance:
+ * 'edit' => "$wgScriptPath/edit/$1"
*
+ * There must be an appropriate script or rewrite rule
+ * in place to handle these URLs.
+ */
+$wgActionPaths = array();
+
+/**@}*/
+
+/************************************************************************//**
+ * @name Files and file uploads
+ * @{
+ */
+
+/** Uploads have to be specially set up to be secure */
+$wgEnableUploads = false;
+
+/** Allows to move images and other media files */
+$wgAllowImageMoving = true;
+
+/**
+ * These are additional characters that should be replaced with '-' in file names
+ */
+$wgIllegalFileChars = ":";
+
+/**
+ * @deprecated use $wgDeletedDirectory
*/
$wgFileStore = array();
-$wgFileStore['deleted']['directory'] = false;///< Defaults to $wgUploadDirectory/deleted
-$wgFileStore['deleted']['url'] = null; ///< Private
-$wgFileStore['deleted']['hash'] = 3; ///< 3-level subdirectory split
-$wgImgAuthDetails = false; ///< defaults to false - only set to true if you use img_auth and want the user to see details on why access failed
-$wgImgAuthPublicTest = true; ///< defaults to true - if public read is turned on, no need for img_auth, config error unless other access is used
+/**
+ * What directory to place deleted uploads in
+ */
+$wgDeletedDirectory = false; // Defaults to $wgUploadDirectory/deleted
+
+/**
+ * Set this to true if you use img_auth and want the user to see details on why access failed.
+ */
+$wgImgAuthDetails = false;
-/**@{
+/**
+ * If this is enabled, img_auth.php will not allow image access unless the wiki
+ * is private. This improves security when image uploads are hosted on a
+ * separate domain.
+ */
+$wgImgAuthPublicTest = true;
+
+/**
* File repository structures
*
* $wgLocalFileRepo is a single repository structure, and $wgForeignFileRepos is
@@ -203,59 +324,62 @@ $wgImgAuthPublicTest = true; ///< defaults to true - if public read is turned on
* array of properties configuring the repository.
*
* Properties required for all repos:
- * class The class name for the repository. May come from the core or an extension.
+ * - class The class name for the repository. May come from the core or an extension.
* The core repository classes are LocalRepo, ForeignDBRepo, FSRepo.
*
- * name A unique name for the repository.
+ * - name A unique name for the repository.
*
* For most core repos:
- * url Base public URL
- * hashLevels The number of directory levels for hash-based division of files
- * thumbScriptUrl The URL for thumb.php (optional, not recommended)
- * transformVia404 Whether to skip media file transformation on parse and rely on a 404
+ * - url Base public URL
+ * - hashLevels The number of directory levels for hash-based division of files
+ * - thumbScriptUrl The URL for thumb.php (optional, not recommended)
+ * - transformVia404 Whether to skip media file transformation on parse and rely on a 404
* handler instead.
- * initialCapital Equivalent to $wgCapitalLinks (or $wgCapitalLinkOverrides[NS_FILE],
+ * - initialCapital Equivalent to $wgCapitalLinks (or $wgCapitalLinkOverrides[NS_FILE],
* determines whether filenames implicitly start with a capital letter.
* The current implementation may give incorrect description page links
* when the local $wgCapitalLinks and initialCapital are mismatched.
- * pathDisclosureProtection
+ * - pathDisclosureProtection
* May be 'paranoid' to remove all parameters from error messages, 'none' to
* leave the paths in unchanged, or 'simple' to replace paths with
* placeholders. Default for LocalRepo is 'simple'.
- * fileMode This allows wikis to set the file mode when uploading/moving files. Default
+ * - fileMode This allows wikis to set the file mode when uploading/moving files. Default
* is 0644.
- * directory The local filesystem directory where public files are stored. Not used for
+ * - directory The local filesystem directory where public files are stored. Not used for
* some remote repos.
- * thumbDir The base thumbnail directory. Defaults to <directory>/thumb.
- * thumbUrl The base thumbnail URL. Defaults to <url>/thumb.
+ * - thumbDir The base thumbnail directory. Defaults to <directory>/thumb.
+ * - thumbUrl The base thumbnail URL. Defaults to <url>/thumb.
*
*
* These settings describe a foreign MediaWiki installation. They are optional, and will be ignored
* for local repositories:
- * descBaseUrl URL of image description pages, e.g. http://en.wikipedia.org/wiki/Image:
- * scriptDirUrl URL of the MediaWiki installation, equivalent to $wgScriptPath, e.g.
- * http://en.wikipedia.org/w
- *
- * articleUrl Equivalent to $wgArticlePath, e.g. http://en.wikipedia.org/wiki/$1
- * fetchDescription Fetch the text of the remote file description page. Equivalent to
+ * - descBaseUrl URL of image description pages, e.g. http://en.wikipedia.org/wiki/File:
+ * - scriptDirUrl URL of the MediaWiki installation, equivalent to $wgScriptPath, e.g.
+ * http://en.wikipedia.org/w
+ * - scriptExtension Script extension of the MediaWiki installation, equivalent to
+ * $wgScriptExtension, e.g. .php5 defaults to .php
+ *
+ * - articleUrl Equivalent to $wgArticlePath, e.g. http://en.wikipedia.org/wiki/$1
+ * - fetchDescription Fetch the text of the remote file description page. Equivalent to
* $wgFetchCommonsDescriptions.
*
* ForeignDBRepo:
- * dbType, dbServer, dbUser, dbPassword, dbName, dbFlags
+ * - dbType, dbServer, dbUser, dbPassword, dbName, dbFlags
* equivalent to the corresponding member of $wgDBservers
- * tablePrefix Table prefix, the foreign wiki's $wgDBprefix
- * hasSharedCache True if the wiki's shared cache is accessible via the local $wgMemc
+ * - tablePrefix Table prefix, the foreign wiki's $wgDBprefix
+ * - hasSharedCache True if the wiki's shared cache is accessible via the local $wgMemc
*
* ForeignAPIRepo:
- * apibase Use for the foreign API's URL
- * apiThumbCacheExpiry How long to locally cache thumbs for
+ * - apibase Use for the foreign API's URL
+ * - apiThumbCacheExpiry How long to locally cache thumbs for
*
* The default is to initialise these arrays from the MW<1.11 backwards compatible settings:
* $wgUploadPath, $wgThumbnailScriptPath, $wgSharedUploadDirectory, etc.
*/
$wgLocalFileRepo = false;
+
+/** @see $wgLocalFileRepo */
$wgForeignFileRepos = array();
-/**@}*/
/**
* Use Commons as a remote file repository. Essentially a wrapper, when this
@@ -265,76 +389,398 @@ $wgForeignFileRepos = array();
$wgUseInstantCommons = false;
/**
- * Allowed title characters -- regex character class
- * Don't change this unless you know what you're doing
+ * Show EXIF data, on by default if available.
+ * Requires PHP's EXIF extension: http://www.php.net/manual/en/ref.exif.php
*
- * Problematic punctuation:
- * []{}|# Are needed for link syntax, never enable these
- * <> Causes problems with HTML escaping, don't use
- * % Enabled by default, minor problems with path to query rewrite rules, see below
- * + Enabled by default, but doesn't work with path to query rewrite rules, corrupted by apache
- * ? Enabled by default, but doesn't work with path to PATH_INFO rewrites
+ * NOTE FOR WINDOWS USERS:
+ * To enable EXIF functions, add the folloing lines to the
+ * "Windows extensions" section of php.ini:
*
- * All three of these punctuation problems can be avoided by using an alias, instead of a
- * rewrite rule of either variety.
+ * extension=extensions/php_mbstring.dll
+ * extension=extensions/php_exif.dll
+ */
+$wgShowEXIF = function_exists( 'exif_read_data' );
+
+/**
+ * If you operate multiple wikis, you can define a shared upload path here.
+ * Uploads to this wiki will NOT be put there - they will be put into
+ * $wgUploadDirectory.
+ * If $wgUseSharedUploads is set, the wiki will look in the shared repository if
+ * no file of the given name is found in the local repository (for [[File:..]],
+ * [[Media:..]] links). Thumbnails will also be looked for and generated in this
+ * directory.
*
- * The problem with % is that when using a path to query rewrite rule, URLs are
- * double-unescaped: once by Apache's path conversion code, and again by PHP. So
- * %253F, for example, becomes "?". Our code does not double-escape to compensate
- * for this, indeed double escaping would break if the double-escaped title was
- * passed in the query string rather than the path. This is a minor security issue
- * because articles can be created such that they are hard to view or edit.
+ * Note that these configuration settings can now be defined on a per-
+ * repository basis for an arbitrary number of file repositories, using the
+ * $wgForeignFileRepos variable.
+ */
+$wgUseSharedUploads = false;
+/** Full path on the web server where shared uploads can be found */
+$wgSharedUploadPath = "http://commons.wikimedia.org/shared/images";
+/** Fetch commons image description pages and display them on the local wiki? */
+$wgFetchCommonsDescriptions = false;
+/** Path on the file system where shared uploads can be found. */
+$wgSharedUploadDirectory = "/var/www/wiki3/images";
+/** DB name with metadata about shared directory. Set this to false if the uploads do not come from a wiki. */
+$wgSharedUploadDBname = false;
+/** Optional table prefix used in database. */
+$wgSharedUploadDBprefix = '';
+/** Cache shared metadata in memcached. Don't do this if the commons wiki is in a different memcached domain */
+$wgCacheSharedUploads = true;
+/**
+* Allow for upload to be copied from an URL. Requires Special:Upload?source=web
+* The timeout for copy uploads is set by $wgHTTPTimeout.
+*/
+$wgAllowCopyUploads = false;
+/**
+ * Allow asynchronous copy uploads.
+ * This feature is experimental.
+ */
+$wgAllowAsyncCopyUploads = false;
+
+/**
+ * Max size for uploads, in bytes. Applies to all uploads.
+ */
+$wgMaxUploadSize = 1024*1024*100; # 100MB
+
+/**
+ * Point the upload navigation link to an external URL
+ * Useful if you want to use a shared repository by default
+ * without disabling local uploads (use $wgEnableUploads = false for that)
+ * e.g. $wgUploadNavigationUrl = 'http://commons.wikimedia.org/wiki/Special:Upload';
+ */
+$wgUploadNavigationUrl = false;
+
+/**
+ * Point the upload link for missing files to an external URL, as with
+ * $wgUploadNavigationUrl. The URL will get (?|&)wpDestFile=<filename>
+ * appended to it as appropriate.
+ */
+$wgUploadMissingFileUrl = false;
+
+/**
+ * Give a path here to use thumb.php for thumbnail generation on client request, instead of
+ * generating them on render and outputting a static URL. This is necessary if some of your
+ * apache servers don't have read/write access to the thumbnail path.
*
- * In some rare cases you may wish to remove + for compatibility with old links.
+ * Example:
+ * $wgThumbnailScriptPath = "{$wgScriptPath}/thumb{$wgScriptExtension}";
+ */
+$wgThumbnailScriptPath = false;
+$wgSharedThumbnailScriptPath = false;
+
+/**
+ * Set this to false if you do not want MediaWiki to divide your images
+ * directory into many subdirectories, for improved performance.
*
- * Theoretically 0x80-0x9F of ISO 8859-1 should be disallowed, but
- * this breaks interlanguage links
+ * It's almost always good to leave this enabled. In previous versions of
+ * MediaWiki, some users set this to false to allow images to be added to the
+ * wiki by simply copying them into $wgUploadDirectory and then running
+ * maintenance/rebuildImages.php to register them in the database. This is no
+ * longer recommended, use maintenance/importImages.php instead.
+ *
+ * Note that this variable may be ignored if $wgLocalFileRepo is set.
*/
-$wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+";
-$wgIllegalFileChars = ":"; // These are additional characters that should be replaced with '-' in file names
+$wgHashedUploadDirectory = true;
+/**
+ * Set the following to false especially if you have a set of files that need to
+ * be accessible by all wikis, and you do not want to use the hash (path/a/aa/)
+ * directory layout.
+ */
+$wgHashedSharedUploadDirectory = true;
+
+/**
+ * Base URL for a repository wiki. Leave this blank if uploads are just stored
+ * in a shared directory and not meant to be accessible through a separate wiki.
+ * Otherwise the image description pages on the local wiki will link to the
+ * image description page on this wiki.
+ *
+ * Please specify the namespace, as in the example below.
+ */
+$wgRepositoryBaseUrl = "http://commons.wikimedia.org/wiki/File:";
/**
- * The external URL protocols
+ * This is the list of preferred extensions for uploading files. Uploading files
+ * with extensions not in this list will trigger a warning.
+ *
+ * WARNING: If you add any OpenOffice or Microsoft Office file formats here,
+ * such as odt or doc, and untrusted users are allowed to upload files, then
+ * your wiki will be vulnerable to cross-site request forgery (CSRF).
*/
-$wgUrlProtocols = array(
- 'http://',
- 'https://',
- 'ftp://',
- 'irc://',
- 'gopher://',
- 'telnet://', // Well if we're going to support the above.. -ævar
- 'nntp://', // @bug 3808 RFC 1738
- 'worldwind://',
- 'mailto:',
- 'news:',
- 'svn://',
+$wgFileExtensions = array( 'png', 'gif', 'jpg', 'jpeg' );
+
+/** Files with these extensions will never be allowed as uploads. */
+$wgFileBlacklist = array(
+ # HTML may contain cookie-stealing JavaScript and web bugs
+ 'html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht',
+ # PHP scripts may execute arbitrary code on the server
+ 'php', 'phtml', 'php3', 'php4', 'php5', 'phps',
+ # Other types that may be interpreted by some servers
+ 'shtml', 'jhtml', 'pl', 'py', 'cgi',
+ # May contain harmful executables for Windows victims
+ 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl' );
+
+/**
+ * Files with these mime types will never be allowed as uploads
+ * if $wgVerifyMimeType is enabled.
+ */
+$wgMimeTypeBlacklist = array(
+ # HTML may contain cookie-stealing JavaScript and web bugs
+ 'text/html', 'text/javascript', 'text/x-javascript', 'application/x-shellscript',
+ # PHP scripts may execute arbitrary code on the server
+ 'application/x-php', 'text/x-php',
+ # Other types that may be interpreted by some servers
+ 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh',
+ # Client-side hazards on Internet Explorer
+ 'text/scriptlet', 'application/x-msdownload',
+ # Windows metafile, client-side vulnerability on some systems
+ 'application/x-msmetafile',
+ # A ZIP file may be a valid Java archive containing an applet which exploits the
+ # same-origin policy to steal cookies
+ 'application/zip',
+
+ # MS Office OpenXML and other Open Package Conventions files are zip files
+ # and thus blacklisted just as other zip files. If you remove these entries
+ # from the blacklist in your local configuration, a malicious file upload
+ # will be able to compromise the wiki's user accounts, and the user
+ # accounts of any other website in the same cookie domain.
+ 'application/x-opc+zip',
+ 'application/msword',
+ 'application/vnd.ms-powerpoint',
+ 'application/vnd.msexcel',
);
-/** internal name of virus scanner. This servers as a key to the $wgAntivirusSetup array.
- * Set this to NULL to disable virus scanning. If not null, every file uploaded will be scanned for viruses.
+/**
+ * This is a flag to determine whether or not to check file extensions on upload.
+ *
+ * WARNING: setting this to false is insecure for public wikis.
*/
-$wgAntivirus= null;
+$wgCheckFileExtensions = true;
-/** Configuration for different virus scanners. This an associative array of associative arrays:
- * it contains on setup array per known scanner type. The entry is selected by $wgAntivirus, i.e.
- * valid values for $wgAntivirus are the keys defined in this array.
+/**
+ * If this is turned off, users may override the warning for files not covered
+ * by $wgFileExtensions.
+ *
+ * WARNING: setting this to false is insecure for public wikis.
+ */
+$wgStrictFileExtensions = true;
+
+/** Warn if uploaded files are larger than this (in bytes), or false to disable*/
+$wgUploadSizeWarning = false;
+
+/**
+ * list of trusted media-types and mime types.
+ * Use the MEDIATYPE_xxx constants to represent media types.
+ * This list is used by File::isSafeFile
+ *
+ * Types not listed here will have a warning about unsafe content
+ * displayed on the images description page. It would also be possible
+ * to use this for further restrictions, like disabling direct
+ * [[media:...]] links for non-trusted formats.
+ */
+$wgTrustedMediaFormats = array(
+ MEDIATYPE_BITMAP, //all bitmap formats
+ MEDIATYPE_AUDIO, //all audio formats
+ MEDIATYPE_VIDEO, //all plain video formats
+ "image/svg+xml", //svg (only needed if inline rendering of svg is not supported)
+ "application/pdf", //PDF files
+ #"application/x-shockwave-flash", //flash/shockwave movie
+);
+
+/**
+ * Plugins for media file type handling.
+ * Each entry in the array maps a MIME type to a class name
+ */
+$wgMediaHandlers = array(
+ 'image/jpeg' => 'BitmapHandler',
+ 'image/png' => 'PNGHandler',
+ 'image/gif' => 'GIFHandler',
+ 'image/tiff' => 'TiffHandler',
+ 'image/x-ms-bmp' => 'BmpHandler',
+ 'image/x-bmp' => 'BmpHandler',
+ 'image/svg+xml' => 'SvgHandler', // official
+ 'image/svg' => 'SvgHandler', // compat
+ 'image/vnd.djvu' => 'DjVuHandler', // official
+ 'image/x.djvu' => 'DjVuHandler', // compat
+ 'image/x-djvu' => 'DjVuHandler', // compat
+);
+
+/**
+ * Resizing can be done using PHP's internal image libraries or using
+ * ImageMagick or another third-party converter, e.g. GraphicMagick.
+ * These support more file formats than PHP, which only supports PNG,
+ * GIF, JPG, XBM and WBMP.
+ *
+ * Use Image Magick instead of PHP builtin functions.
+ */
+$wgUseImageMagick = false;
+/** The convert command shipped with ImageMagick */
+$wgImageMagickConvertCommand = '/usr/bin/convert';
+
+/** Sharpening parameter to ImageMagick */
+$wgSharpenParameter = '0x0.4';
+
+/** Reduction in linear dimensions below which sharpening will be enabled */
+$wgSharpenReductionThreshold = 0.85;
+
+/**
+ * Temporary directory used for ImageMagick. The directory must exist. Leave
+ * this set to false to let ImageMagick decide for itself.
+ */
+$wgImageMagickTempDir = false;
+
+/**
+ * Use another resizing converter, e.g. GraphicMagick
+ * %s will be replaced with the source path, %d with the destination
+ * %w and %h will be replaced with the width and height.
+ *
+ * Example for GraphicMagick:
+ * <code>
+ * $wgCustomConvertCommand = "gm convert %s -resize %wx%h %d"
+ * </code>
+ *
+ * Leave as false to skip this.
+ */
+$wgCustomConvertCommand = false;
+
+/**
+ * Scalable Vector Graphics (SVG) may be uploaded as images.
+ * Since SVG support is not yet standard in browsers, it is
+ * necessary to rasterize SVGs to PNG as a fallback format.
+ *
+ * An external program is required to perform this conversion.
+ */
+$wgSVGConverters = array(
+ 'ImageMagick' => '$path/convert -background white -thumbnail $widthx$height\! $input PNG:$output',
+ 'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output',
+ 'inkscape' => '$path/inkscape -z -w $width -f $input -e $output',
+ 'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input',
+ 'rsvg' => '$path/rsvg -w$width -h$height $input $output',
+ 'imgserv' => '$path/imgserv-wrapper -i svg -o png -w$width $input $output',
+ );
+/** Pick a converter defined in $wgSVGConverters */
+$wgSVGConverter = 'ImageMagick';
+/** If not in the executable PATH, specify the SVG converter path. */
+$wgSVGConverterPath = '';
+/** Don't scale a SVG larger than this */
+$wgSVGMaxSize = 2048;
+/** Don't read SVG metadata beyond this point.
+ * Default is 1024*256 bytes */
+$wgSVGMetadataCutoff = 262144;
+
+/**
+ * MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't
+ * perform basic stuff like MIME detection and which are vulnerable to further idiots uploading
+ * crap files as images. When this directive is on, <title> will be allowed in files with
+ * an "image/svg+xml" MIME type. You should leave this disabled if your web server is misconfigured
+ * and doesn't send appropriate MIME types for SVG images.
+ */
+$wgAllowTitlesInSVG = false;
+
+/**
+ * Don't thumbnail an image if it will use too much working memory.
+ * Default is 50 MB if decompressed to RGBA form, which corresponds to
+ * 12.5 million pixels or 3500x3500
+ */
+$wgMaxImageArea = 1.25e7;
+/**
+ * Force thumbnailing of animated GIFs above this size to a single
+ * frame instead of an animated thumbnail. As of MW 1.17 this limit
+ * is checked against the total size of all frames in the animation.
+ * It probably makes sense to keep this equal to $wgMaxImageArea.
+ */
+$wgMaxAnimatedGifArea = 1.25e7;
+/**
+ * Browsers don't support TIFF inline generally...
+ * For inline display, we need to convert to PNG or JPEG.
+ * Note scaling should work with ImageMagick, but may not with GD scaling.
*
- * The configuration array for each scanner contains the following keys: "command", "codemap", "messagepattern";
+ * Example:
+ * <code>
+ * // PNG is lossless, but inefficient for photos
+ * $wgTiffThumbnailType = array( 'png', 'image/png' );
+ * // JPEG is good for photos, but has no transparency support. Bad for diagrams.
+ * $wgTiffThumbnailType = array( 'jpg', 'image/jpeg' );
+ * </code>
+ */
+ $wgTiffThumbnailType = false;
+
+/**
+ * If rendered thumbnail files are older than this timestamp, they
+ * will be rerendered on demand as if the file didn't already exist.
+ * Update if there is some need to force thumbs and SVG rasterizations
+ * to rerender, such as fixes to rendering bugs.
+ */
+$wgThumbnailEpoch = '20030516000000';
+
+/**
+ * If set, inline scaled images will still produce <img> tags ready for
+ * output instead of showing an error message.
*
- * "command" is the full command to call the virus scanner - %f will be replaced with the name of the
- * file to scan. If not present, the filename will be appended to the command. Note that this must be
- * overwritten if the scanner is not in the system path; in that case, plase set
- * $wgAntivirusSetup[$wgAntivirus]['command'] to the desired command with full path.
+ * This may be useful if errors are transitory, especially if the site
+ * is configured to automatically render thumbnails on request.
*
- * "codemap" is a mapping of exit code to return codes of the detectVirus function in SpecialUpload.
- * An exit code mapped to AV_SCAN_FAILED causes the function to consider the scan to be failed. This will pass
- * the file if $wgAntivirusRequired is not set.
- * An exit code mapped to AV_SCAN_ABORTED causes the function to consider the file to have an usupported format,
- * which is probably imune to virusses. This causes the file to pass.
- * An exit code mapped to AV_NO_VIRUS will cause the file to pass, meaning no virus was found.
- * All other codes (like AV_VIRUS_FOUND) will cause the function to report a virus.
- * You may use "*" as a key in the array to catch all exit codes not mapped otherwise.
+ * On the other hand, it may obscure error conditions from debugging.
+ * Enable the debug log or the 'thumbnail' log group to make sure errors
+ * are logged to a file for review.
+ */
+$wgIgnoreImageErrors = false;
+
+/**
+ * Allow thumbnail rendering on page view. If this is false, a valid
+ * thumbnail URL is still output, but no file will be created at
+ * the target location. This may save some time if you have a
+ * thumb.php or 404 handler set up which is faster than the regular
+ * webserver(s).
+ */
+$wgGenerateThumbnailOnParse = true;
+
+/**
+* Show thumbnails for old images on the image description page
+*/
+$wgShowArchiveThumbnails = true;
+
+/** Obsolete, always true, kept for compatibility with extensions */
+$wgUseImageResize = true;
+
+
+/**
+ * Internal name of virus scanner. This servers as a key to the
+ * $wgAntivirusSetup array. Set this to NULL to disable virus scanning. If not
+ * null, every file uploaded will be scanned for viruses.
+ */
+$wgAntivirus= null;
+
+/**
+ * Configuration for different virus scanners. This an associative array of
+ * associative arrays. It contains one setup array per known scanner type.
+ * The entry is selected by $wgAntivirus, i.e.
+ * valid values for $wgAntivirus are the keys defined in this array.
+ *
+ * The configuration array for each scanner contains the following keys:
+ * "command", "codemap", "messagepattern":
+ *
+ * "command" is the full command to call the virus scanner - %f will be
+ * replaced with the name of the file to scan. If not present, the filename
+ * will be appended to the command. Note that this must be overwritten if the
+ * scanner is not in the system path; in that case, plase set
+ * $wgAntivirusSetup[$wgAntivirus]['command'] to the desired command with full
+ * path.
+ *
+ * "codemap" is a mapping of exit code to return codes of the detectVirus
+ * function in SpecialUpload.
+ * - An exit code mapped to AV_SCAN_FAILED causes the function to consider
+ * the scan to be failed. This will pass the file if $wgAntivirusRequired
+ * is not set.
+ * - An exit code mapped to AV_SCAN_ABORTED causes the function to consider
+ * the file to have an usupported format, which is probably imune to
+ * virusses. This causes the file to pass.
+ * - An exit code mapped to AV_NO_VIRUS will cause the file to pass, meaning
+ * no virus was found.
+ * - All other codes (like AV_VIRUS_FOUND) will cause the function to report
+ * a virus.
+ * - You may use "*" as a key in the array to catch all exit codes not mapped otherwise.
*
* "messagepattern" is a perl regular expression to extract the meaningful part of the scanners
* output. The relevant part should be matched as group one (\1).
@@ -373,13 +819,13 @@ $wgAntivirusSetup = array(
/** Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected. */
-$wgAntivirusRequired= true;
+$wgAntivirusRequired = true;
/** Determines if the mime type of uploaded files should be checked */
-$wgVerifyMimeType= true;
+$wgVerifyMimeType = true;
/** Sets the mime type definition file to use by MimeMagic.php. */
-$wgMimeTypeFile= "includes/mime.types";
+$wgMimeTypeFile = "includes/mime.types";
#$wgMimeTypeFile= "/etc/mime.types";
#$wgMimeTypeFile= null; #use built-in defaults only.
@@ -387,25 +833,30 @@ $wgMimeTypeFile= "includes/mime.types";
$wgMimeInfoFile= "includes/mime.info";
#$wgMimeInfoFile= null; #use built-in defaults only.
-/** Switch for loading the FileInfo extension by PECL at runtime.
+/**
+ * Switch for loading the FileInfo extension by PECL at runtime.
* This should be used only if fileinfo is installed as a shared object
- * or a dynamic libary
+ * or a dynamic library.
*/
-$wgLoadFileinfoExtension= false;
+$wgLoadFileinfoExtension = false;
/** Sets an external mime detector program. The command must print only
* the mime type to standard output.
* The name of the file to process will be appended to the command given here.
* If not set or NULL, mime_content_type will be used if available.
+ * Example:
+ * <code>
+ * #$wgMimeDetectorCommand = "file -bi"; # use external mime detector (Linux)
+ * </code>
*/
-$wgMimeDetectorCommand= null; # use internal mime_content_type function, available since php 4.3.0
-#$wgMimeDetectorCommand= "file -bi"; #use external mime detector (Linux)
+$wgMimeDetectorCommand = null;
-/** Switch for trivial mime detection. Used by thumb.php to disable all fance
+/**
+ * Switch for trivial mime detection. Used by thumb.php to disable all fancy
* things, because only a few types of images are needed and file extensions
* can be trusted.
*/
-$wgTrivialMimeDetection= false;
+$wgTrivialMimeDetection = false;
/**
* Additional XML types we can allow via mime-detection.
@@ -420,117 +871,136 @@ $wgXMLMimeTypes = array(
);
/**
- * To set 'pretty' URL paths for actions other than
- * plain page views, add to this array. For instance:
- * 'edit' => "$wgScriptPath/edit/$1"
- *
- * There must be an appropriate script or rewrite rule
- * in place to handle these URLs.
+ * Limit images on image description pages to a user-selectable limit. In order
+ * to reduce disk usage, limits can only be selected from a list.
+ * The user preference is saved as an array offset in the database, by default
+ * the offset is set with $wgDefaultUserOptions['imagesize']. Make sure you
+ * change it if you alter the array (see bug 8858).
+ * This is the list of settings the user can choose from:
*/
-$wgActionPaths = array();
+$wgImageLimits = array (
+ array(320,240),
+ array(640,480),
+ array(800,600),
+ array(1024,768),
+ array(1280,1024),
+ array(10000,10000) );
/**
- * If you operate multiple wikis, you can define a shared upload path here.
- * Uploads to this wiki will NOT be put there - they will be put into
- * $wgUploadDirectory.
- * If $wgUseSharedUploads is set, the wiki will look in the shared repository if
- * no file of the given name is found in the local repository (for [[Image:..]],
- * [[Media:..]] links). Thumbnails will also be looked for and generated in this
- * directory.
- *
- * Note that these configuration settings can now be defined on a per-
- * repository basis for an arbitrary number of file repositories, using the
- * $wgForeignFileRepos variable.
+ * Adjust thumbnails on image pages according to a user setting. In order to
+ * reduce disk usage, the values can only be selected from a list. This is the
+ * list of settings the user can choose from:
*/
-$wgUseSharedUploads = false;
-/** Full path on the web server where shared uploads can be found */
-$wgSharedUploadPath = "http://commons.wikimedia.org/shared/images";
-/** Fetch commons image description pages and display them on the local wiki? */
-$wgFetchCommonsDescriptions = false;
-/** Path on the file system where shared uploads can be found. */
-$wgSharedUploadDirectory = "/var/www/wiki3/images";
-/** DB name with metadata about shared directory. Set this to false if the uploads do not come from a wiki. */
-$wgSharedUploadDBname = false;
-/** Optional table prefix used in database. */
-$wgSharedUploadDBprefix = '';
-/** Cache shared metadata in memcached. Don't do this if the commons wiki is in a different memcached domain */
-$wgCacheSharedUploads = true;
+$wgThumbLimits = array(
+ 120,
+ 150,
+ 180,
+ 200,
+ 250,
+ 300
+);
+
/**
-* Allow for upload to be copied from an URL. Requires Special:Upload?source=web
-* The timeout for copy uploads is set by $wgHTTPTimeout.
-*/
-$wgAllowCopyUploads = false;
+ * Default parameters for the <gallery> tag
+ */
+$wgGalleryOptions = array (
+ 'imagesPerRow' => 0, // Default number of images per-row in the gallery. 0 -> Adapt to screensize
+ 'imageWidth' => 120, // Width of the cells containing images in galleries (in "px")
+ 'imageHeight' => 120, // Height of the cells containing images in galleries (in "px")
+ 'captionLength' => 20, // Length of caption to truncate (in characters)
+ 'showBytes' => true, // Show the filesize in bytes in categories
+);
/**
- * Max size for uploads, in bytes. Currently only works for uploads from URL
- * via CURL (see $wgAllowCopyUploads). The only way to impose limits on
- * normal uploads is currently to edit php.ini.
+ * Adjust width of upright images when parameter 'upright' is used
+ * This allows a nicer look for upright images without the need to fix the width
+ * by hardcoded px in wiki sourcecode.
*/
-$wgMaxUploadSize = 1024*1024*100; # 100MB
+$wgThumbUpright = 0.75;
/**
- * Point the upload navigation link to an external URL
- * Useful if you want to use a shared repository by default
- * without disabling local uploads (use $wgEnableUploads = false for that)
- * e.g. $wgUploadNavigationUrl = 'http://commons.wikimedia.org/wiki/Special:Upload';
- *
- * This also affects images inline images that do not exist. In that case the URL will get
- * (?|&)wpDestFile=<filename> appended to it as appropriate.
+ * Default value for chmoding of new directories.
*/
-$wgUploadNavigationUrl = false;
+$wgDirectoryMode = 0777;
/**
- * Give a path here to use thumb.php for thumbnail generation on client request, instead of
- * generating them on render and outputting a static URL. This is necessary if some of your
- * apache servers don't have read/write access to the thumbnail path.
- *
- * Example:
- * $wgThumbnailScriptPath = "{$wgScriptPath}/thumb{$wgScriptExtension}";
+ * DJVU settings
+ * Path of the djvudump executable
+ * Enable this and $wgDjvuRenderer to enable djvu rendering
*/
-$wgThumbnailScriptPath = false;
-$wgSharedThumbnailScriptPath = false;
+# $wgDjvuDump = 'djvudump';
+$wgDjvuDump = null;
/**
- * Set the following to false especially if you have a set of files that need to
- * be accessible by all wikis, and you do not want to use the hash (path/a/aa/)
- * directory layout.
+ * Path of the ddjvu DJVU renderer
+ * Enable this and $wgDjvuDump to enable djvu rendering
*/
-$wgHashedSharedUploadDirectory = true;
+# $wgDjvuRenderer = 'ddjvu';
+$wgDjvuRenderer = null;
/**
- * Base URL for a repository wiki. Leave this blank if uploads are just stored
- * in a shared directory and not meant to be accessible through a separate wiki.
- * Otherwise the image description pages on the local wiki will link to the
- * image description page on this wiki.
+ * Path of the djvutxt DJVU text extraction utility
+ * Enable this and $wgDjvuDump to enable text layer extraction from djvu files
+ */
+# $wgDjvuTxt = 'djvutxt';
+$wgDjvuTxt = null;
+
+/**
+ * Path of the djvutoxml executable
+ * This works like djvudump except much, much slower as of version 3.5.
*
- * Please specify the namespace, as in the example below.
+ * For now I recommend you use djvudump instead. The djvuxml output is
+ * probably more stable, so we'll switch back to it as soon as they fix
+ * the efficiency problem.
+ * http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
*/
-$wgRepositoryBaseUrl = "http://commons.wikimedia.org/wiki/Image:";
+# $wgDjvuToXML = 'djvutoxml';
+$wgDjvuToXML = null;
-#
-# Email settings
-#
/**
- * Site admin email address
- * Default to wikiadmin@SERVER_NAME
+ * Shell command for the DJVU post processor
+ * Default: pnmtopng, since ddjvu generates ppm output
+ * Set this to false to output the ppm file directly.
+ */
+$wgDjvuPostProcessor = 'pnmtojpeg';
+/**
+ * File extension for the DJVU post processor output
+ */
+$wgDjvuOutputExtension = 'jpg';
+
+/** @} */ # end of file uploads }
+
+/************************************************************************//**
+ * @name Email settings
+ * @{
+ */
+
+/**
+ * Site admin email address.
*/
-$wgEmergencyContact = 'wikiadmin@' . $wgServerName;
+$wgEmergencyContact = 'wikiadmin@' . $serverName;
/**
- * Password reminder email address
- * The address we should use as sender when a user is requesting his password
- * Default to apache@SERVER_NAME
+ * Password reminder email address.
+ *
+ * The address we should use as sender when a user is requesting his password.
*/
-$wgPasswordSender = 'MediaWiki Mail <apache@' . $wgServerName . '>';
+$wgPasswordSender = 'apache@' . $serverName;
+
+unset( $serverName ); # Don't leak local variables to global scope
/**
- * dummy address which should be accepted during mail send action
- * It might be necessay to adapt the address or to set it equal
- * to the $wgEmergencyContact address
+ * Password reminder name
*/
-#$wgNoReplyAddress = $wgEmergencyContact;
-$wgNoReplyAddress = 'reply@not.possible';
+$wgPasswordSenderName = 'MediaWiki Mail';
+
+/**
+ * Dummy address which should be accepted during mail send action.
+ * It might be necessary to adapt the address or to set it equal
+ * to the $wgEmergencyContact address.
+ */
+$wgNoReplyAddress = 'reply@not.possible';
/**
* Set to true to enable the e-mail basic features:
@@ -581,18 +1051,97 @@ $wgNewPasswordExpiry = 3600 * 24 * 7;
*/
$wgSMTP = false;
+/**
+ * Additional email parameters, will be passed as the last argument to mail() call.
+ */
+$wgAdditionalMailParams = null;
+
+/**
+ * True: from page editor if s/he opted-in. False: Enotif mails appear to come
+ * from $wgEmergencyContact
+ */
+$wgEnotifFromEditor = false;
+
+// TODO move UPO to preferences probably ?
+# If set to true, users get a corresponding option in their preferences and can choose to enable or disable at their discretion
+# If set to false, the corresponding input form on the user preference page is suppressed
+# It call this to be a "user-preferences-option (UPO)"
+
+/**
+ * Require email authentication before sending mail to an email addres. This is
+ * highly recommended. It prevents MediaWiki from being used as an open spam
+ * relay.
+ */
+$wgEmailAuthentication = true;
+
+/**
+ * Allow users to enable email notification ("enotif") on watchlist changes.
+ */
+$wgEnotifWatchlist = false;
+
+/**
+ * Allow users to enable email notification ("enotif") when someone edits their
+ * user talk page.
+ */
+$wgEnotifUserTalk = false;
+
+/**
+ * Set the Reply-to address in notifications to the editor's address, if user
+ * allowed this in the preferences.
+ */
+$wgEnotifRevealEditorAddress = false;
+
+/**
+ * Send notification mails on minor edits to watchlist pages. This is enabled
+ * by default. Does not affect user talk notifications.
+ */
+$wgEnotifMinorEdits = true;
+
+/**
+ * Send a generic mail instead of a personalised mail for each user. This
+ * always uses UTC as the time zone, and doesn't include the username.
+ *
+ * For pages with many users watching, this can significantly reduce mail load.
+ * Has no effect when using sendmail rather than SMTP.
+ */
+$wgEnotifImpersonal = false;
+
+/**
+ * Maximum number of users to mail at once when using impersonal mail. Should
+ * match the limit on your mail server.
+ */
+$wgEnotifMaxRecips = 500;
+
+/**
+ * Send mails via the job queue. This can be useful to reduce the time it
+ * takes to save a page that a lot of people are watching.
+ */
+$wgEnotifUseJobQ = false;
+
+/**
+ * Use real name instead of username in e-mail "from" field.
+ */
+$wgEnotifUseRealName = false;
+
+/**
+ * Array of usernames who will be sent a notification email for every change
+ * which occurs on a wiki.
+ */
+$wgUsersNotifiedOnAllChanges = array();
+
-/**@{
- * Database settings
+/** @} */ # end of email settings
+
+/************************************************************************//**
+ * @name Database settings
+ * @{
*/
-/** database host name or ip address */
+/** Database host name or IP address */
$wgDBserver = 'localhost';
-/** database port number (for PostgreSQL) */
+/** Database port number (for PostgreSQL) */
$wgDBport = 5432;
-/** name of the database */
+/** Name of the database */
$wgDBname = 'my_wiki';
-/** */
-$wgDBconnection = '';
/** Database username */
$wgDBuser = 'wikiuser';
/** Database user's password */
@@ -600,11 +1149,13 @@ $wgDBpassword = '';
/** Database type */
$wgDBtype = 'mysql';
-/** Separate username and password for maintenance tasks. Leave as null to use the default */
+/** Separate username for maintenance tasks. Leave as null to use the default. */
$wgDBadminuser = null;
+/** Separate password for maintenance tasks. Leave as null to use the default. */
$wgDBadminpassword = null;
-/** Search type
+/**
+ * Search type.
* Leave as null to select the default search engine for the
* selected database type (eg SearchMySQL), or set to a class
* name to override to a custom search engine.
@@ -616,6 +1167,14 @@ $wgDBprefix = '';
/** MySQL table options to use during installation or update */
$wgDBTableOptions = 'ENGINE=InnoDB';
+/**
+ * SQL Mode - default is turning off all modes, including strict, if set.
+ * null can be used to skip the setting for performance reasons and assume
+ * DBA has done his best job.
+ * String override can be used for some additional fun :-)
+ */
+$wgSQLMode = '';
+
/** Mediawiki schema */
$wgDBmwschema = 'mediawiki';
/** Tsearch2 schema */
@@ -624,12 +1183,6 @@ $wgDBts2schema = 'public';
/** To override default SQLite data directory ($docroot/../data) */
$wgSQLiteDataDir = '';
-/** Default directory mode for SQLite data directory on creation.
- * Note that this is different from the default directory mode used
- * elsewhere.
- */
-$wgSQLiteDataDirMode = 0700;
-
/**
* Make all database connections secretly go to localhost. Fool the load balancer
* thinking there is an arbitrarily large cluster of servers to connect to.
@@ -637,17 +1190,11 @@ $wgSQLiteDataDirMode = 0700;
*/
$wgAllDBsAreLocalhost = false;
-/**@}*/
-
-
-/** Live high performance sites should disable this - some checks acquire giant mysql locks */
-$wgCheckDBSchema = true;
-
-
/**
* Shared database for multiple wikis. Commonly used for storing a user table
* for single sign-on. The server for this database must be the same as for the
* main database.
+ *
* For backwards compatibility the shared prefix is set to the same as the local
* prefix, and the user table is listed in the default list of shared tables.
* The user_properties table is also added so that users will continue to have their
@@ -657,33 +1204,39 @@ $wgCheckDBSchema = true;
* datbase. However it is advised to limit what tables you do share as many of
* MediaWiki's tables may have side effects if you try to share them.
* EXPERIMENTAL
+ *
+ * $wgSharedPrefix is the table prefix for the shared database. It defaults to
+ * $wgDBprefix.
*/
$wgSharedDB = null;
-$wgSharedPrefix = false; # Defaults to $wgDBprefix
+
+/** @see $wgSharedDB */
+$wgSharedPrefix = false;
+/** @see $wgSharedDB */
$wgSharedTables = array( 'user', 'user_properties' );
/**
* Database load balancer
* This is a two-dimensional array, an array of server info structures
* Fields are:
- * host: Host name
- * dbname: Default database name
- * user: DB user
- * password: DB password
- * type: "mysql" or "postgres"
- * load: ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0
- * groupLoads: array of load ratios, the key is the query group name. A query may belong
- * to several groups, the most specific group defined here is used.
- *
- * flags: bit field
- * DBO_DEFAULT -- turns on DBO_TRX only if !$wgCommandLineMode (recommended)
- * DBO_DEBUG -- equivalent of $wgDebugDumpSql
- * DBO_TRX -- wrap entire request in a transaction
- * DBO_IGNORE -- ignore errors (not useful in LocalSettings.php)
- * DBO_NOBUFFER -- turn off buffering (not useful in LocalSettings.php)
- *
- * max lag: (optional) Maximum replication lag before a slave will taken out of rotation
- * max threads: (optional) Maximum number of running threads
+ * - host: Host name
+ * - dbname: Default database name
+ * - user: DB user
+ * - password: DB password
+ * - type: "mysql" or "postgres"
+ * - load: ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0
+ * - groupLoads: array of load ratios, the key is the query group name. A query may belong
+ * to several groups, the most specific group defined here is used.
+ *
+ * - flags: bit field
+ * - DBO_DEFAULT -- turns on DBO_TRX only if !$wgCommandLineMode (recommended)
+ * - DBO_DEBUG -- equivalent of $wgDebugDumpSql
+ * - DBO_TRX -- wrap entire request in a transaction
+ * - DBO_IGNORE -- ignore errors (not useful in LocalSettings.php)
+ * - DBO_NOBUFFER -- turn off buffering (not useful in LocalSettings.php)
+ *
+ * - max lag: (optional) Maximum replication lag before a slave will taken out of rotation
+ * - max threads: (optional) Maximum number of running threads
*
* These and any other user-defined properties will be assigned to the mLBInfo member
* variable of the Database object.
@@ -697,7 +1250,9 @@ $wgSharedTables = array( 'user', 'user_properties' );
* accidental misconfiguration or MediaWiki bugs, set read_only=1 on all your
* slaves in my.cnf. You can set read_only mode at runtime using:
*
+ * <code>
* SET @@read_only=1;
+ * </code>
*
* Since the effect of writing to a slave is so damaging and difficult to clean
* up, we at Wikimedia set read_only=1 in my.cnf on all our DB servers, even
@@ -763,38 +1318,218 @@ $wgDBmysql5 = false;
*/
$wgLocalDatabases = array();
-/** @{
- * Object cache settings
- * See Defines.php for types
+/**
+ * If lag is higher than $wgSlaveLagWarning, show a warning in some special
+ * pages (like watchlist). If the lag is higher than $wgSlaveLagCritical,
+ * show a more obvious warning.
+ */
+$wgSlaveLagWarning = 10;
+/** @see $wgSlaveLagWarning */
+$wgSlaveLagCritical = 30;
+
+/**
+ * Use old names for change_tags indices.
+ */
+$wgOldChangeTagsIndex = false;
+
+/**@}*/ # End of DB settings }
+
+
+/************************************************************************//**
+ * @name Text storage
+ * @{
+ */
+
+/**
+ * We can also compress text stored in the 'text' table. If this is set on, new
+ * revisions will be compressed on page save if zlib support is available. Any
+ * compressed revisions will be decompressed on load regardless of this setting
+ * *but will not be readable at all* if zlib support is not available.
+ */
+$wgCompressRevisions = false;
+
+/**
+ * External stores allow including content
+ * from non database sources following URL links
+ *
+ * Short names of ExternalStore classes may be specified in an array here:
+ * $wgExternalStores = array("http","file","custom")...
+ *
+ * CAUTION: Access to database might lead to code execution
+ */
+$wgExternalStores = false;
+
+/**
+ * An array of external mysql servers, e.g.
+ * $wgExternalServers = array( 'cluster1' => array( 'srv28', 'srv29', 'srv30' ) );
+ * Used by LBFactory_Simple, may be ignored if $wgLBFactoryConf is set to another class.
+ */
+$wgExternalServers = array();
+
+/**
+ * The place to put new revisions, false to put them in the local text table.
+ * Part of a URL, e.g. DB://cluster1
+ *
+ * Can be an array instead of a single string, to enable data distribution. Keys
+ * must be consecutive integers, starting at zero. Example:
+ *
+ * $wgDefaultExternalStore = array( 'DB://cluster1', 'DB://cluster2' );
+ *
+ */
+$wgDefaultExternalStore = false;
+
+/**
+ * Revision text may be cached in $wgMemc to reduce load on external storage
+ * servers and object extraction overhead for frequently-loaded revisions.
+ *
+ * Set to 0 to disable, or number of seconds before cache expiry.
+ */
+$wgRevisionCacheExpiry = 0;
+
+/** @} */ # end text storage }
+
+/************************************************************************//**
+ * @name Performance hacks and limits
+ * @{
+ */
+/** Disable database-intensive features */
+$wgMiserMode = false;
+/** Disable all query pages if miser mode is on, not just some */
+$wgDisableQueryPages = false;
+/** Number of rows to cache in 'querycache' table when miser mode is on */
+$wgQueryCacheLimit = 1000;
+/** Number of links to a page required before it is deemed "wanted" */
+$wgWantedPagesThreshold = 1;
+/** Enable slow parser functions */
+$wgAllowSlowParserFunctions = false;
+
+/**
+ * Do DELETE/INSERT for link updates instead of incremental
+ */
+$wgUseDumbLinkUpdate = false;
+
+/**
+ * Anti-lock flags - bitfield
+ * - ALF_PRELOAD_LINKS:
+ * Preload links during link update for save
+ * - ALF_PRELOAD_EXISTENCE:
+ * Preload cur_id during replaceLinkHolders
+ * - 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
+ * - ALF_NO_BLOCK_LOCK:
+ * As for ALF_LINK_LOCK, this flag is a necessity for high-traffic
+ * wikis.
+ */
+$wgAntiLockFlags = 0;
+
+/**
+ * Maximum article size in kilobytes
+ */
+$wgMaxArticleSize = 2048;
+
+/**
+ * The minimum amount of memory that MediaWiki "needs"; MediaWiki will try to
+ * raise PHP's memory limit if it's below this amount.
+ */
+$wgMemoryLimit = "50M";
+
+/** @} */ # end performance hacks }
+
+/************************************************************************//**
+ * @name Cache settings
+ * @{
+ */
+
+/**
+ * Directory for caching data in the local filesystem. Should not be accessible
+ * from the web. Set this to false to not use any local caches.
+ *
+ * Note: if multiple wikis share the same localisation cache directory, they
+ * must all have the same set of extensions. You can set a directory just for
+ * the localisation cache using $wgLocalisationCacheConf['storeDirectory'].
+ */
+$wgCacheDirectory = false;
+
+/**
+ * Main cache type. This should be a cache with fast access, but it may have
+ * limited space. By default, it is disabled, since the database is not fast
+ * enough to make it worthwhile.
+ *
+ * The options are:
+ *
+ * - CACHE_ANYTHING: Use anything, as long as it works
+ * - CACHE_NONE: Do not cache
+ * - CACHE_DB: Store cache objects in the DB
+ * - CACHE_MEMCACHED: MemCached, must specify servers in $wgMemCachedServers
+ * - CACHE_ACCEL: eAccelerator, APC, XCache or WinCache
+ * - CACHE_DBA: Use PHP's DBA extension to store in a DBM-style
+ * database. This is slow, and is not recommended for
+ * anything other than debugging.
+ *
+ * @see $wgMessageCacheType, $wgParserCacheType
*/
$wgMainCacheType = CACHE_NONE;
+
+/**
+ * The cache type for storing the contents of the MediaWiki namespace. This
+ * cache is used for a small amount of data which is expensive to regenerate.
+ *
+ * For available types see $wgMainCacheType.
+ */
$wgMessageCacheType = CACHE_ANYTHING;
+
+/**
+ * The cache type for storing article HTML. This is used to store data which
+ * is expensive to regenerate, and benefits from having plenty of storage space.
+ *
+ * For available types see $wgMainCacheType.
+ */
$wgParserCacheType = CACHE_ANYTHING;
-/**@}*/
+/**
+ * The expiry time for the parser cache, in seconds. The default is 86.4k
+ * seconds, otherwise known as a day.
+ */
$wgParserCacheExpireTime = 86400;
-// Select which DBA handler <http://www.php.net/manual/en/dba.requirements.php> to use as CACHE_DBA backend
+/**
+ * Select which DBA handler <http://www.php.net/manual/en/dba.requirements.php> to use as CACHE_DBA backend
+ */
$wgDBAhandler = 'db3';
+/**
+ * Store sessions in MemCached. This can be useful to improve performance, or to
+ * avoid the locking behaviour of PHP's default session handler, which tends to
+ * prevent multiple requests for the same user from acting concurrently.
+ */
$wgSessionsInMemcached = false;
-/** This is used for setting php's session.save_handler. In practice, you will
+/**
+ * This is used for setting php's session.save_handler. In practice, you will
* almost never need to change this ever. Other options might be 'user' or
* 'session_mysql.' Setting to null skips setting this entirely (which might be
- * useful if you're doing cross-application sessions, see bug 11381) */
+ * useful if you're doing cross-application sessions, see bug 11381)
+ */
$wgSessionHandler = 'files';
-/**@{
- * Memcached-specific settings
- * See docs/memcached.txt
- */
-$wgUseMemCached = false;
-$wgMemCachedDebug = false; ///< Will be set to false in Setup.php, if the server isn't working
+/** If enabled, will send MemCached debugging information to $wgDebugLogFile */
+$wgMemCachedDebug = false;
+
+/** The list of MemCached servers and port numbers */
$wgMemCachedServers = array( '127.0.0.1:11000' );
+
+/**
+ * Use persistent connections to MemCached, which are shared across multiple
+ * requests.
+ */
$wgMemCachedPersistent = false;
-$wgMemCachedTimeout = 100000; //Data timeout in microseconds
-/**@}*/
+
+/**
+ * Read/write timeout for MemCached server communication, in microseconds.
+ */
+$wgMemCachedTimeout = 100000;
/**
* Set this to true to make a local copy of the message cache, for use in
@@ -837,17 +1572,222 @@ $wgLocalisationCacheConf = array(
'manualRecache' => false,
);
-# Language settings
-#
+/** Allow client-side caching of pages */
+$wgCachePages = true;
+
+/**
+ * Set this to current time to invalidate all prior cached pages. Affects both
+ * client- and server-side caching.
+ * You can get the current date on your server by using the command:
+ * date +%Y%m%d%H%M%S
+ */
+$wgCacheEpoch = '20030516000000';
+
+/**
+ * Bump this number when changing the global style sheets and JavaScript.
+ * It should be appended in the query string of static CSS and JS includes,
+ * to ensure that client-side caches do not keep obsolete copies of global
+ * styles.
+ */
+$wgStyleVersion = '301';
+
+/**
+ * This will cache static pages for non-logged-in users to reduce
+ * database traffic on public sites.
+ * Must set $wgShowIPinHeader = false
+ */
+$wgUseFileCache = false;
+
+/**
+ * Directory where the cached page will be saved.
+ * Defaults to "$wgCacheDirectory/html".
+ */
+$wgFileCacheDirectory = false;
+
+/**
+ * Depth of the subdirectory hierarchy to be created under
+ * $wgFileCacheDirectory. The subdirectories will be named based on
+ * the MD5 hash of the title. A value of 0 means all cache files will
+ * be put directly into the main file cache directory.
+ */
+$wgFileCacheDepth = 2;
+
+/**
+ * Keep parsed pages in a cache (objectcache table or memcached)
+ * to speed up output of the same page viewed by another user with the
+ * same options.
+ *
+ * This can provide a significant speedup for medium to large pages,
+ * so you probably want to keep it on. Extensions that conflict with the
+ * parser cache should disable the cache on a per-page basis instead.
+ */
+$wgEnableParserCache = true;
+
+/**
+ * Append a configured value to the parser cache and the sitenotice key so
+ * that they can be kept separate for some class of activity.
+ */
+$wgRenderHashAppend = '';
+
+/**
+ * If on, the sidebar navigation links are cached for users with the
+ * current language set. This can save a touch of load on a busy site
+ * by shaving off extra message lookups.
+ *
+ * However it is also fragile: changing the site configuration, or
+ * having a variable $wgArticlePath, can produce broken links that
+ * don't update as expected.
+ */
+$wgEnableSidebarCache = false;
+
+/**
+ * Expiry time for the sidebar cache, in seconds
+ */
+$wgSidebarCacheExpiry = 86400;
+
+/**
+ * When using the file cache, we can store the cached HTML gzipped to save disk
+ * space. Pages will then also be served compressed to clients that support it.
+ * THIS IS NOT COMPATIBLE with ob_gzhandler which is now enabled if supported in
+ * the default LocalSettings.php! If you enable this, remove that setting first.
+ *
+ * Requires zlib support enabled in PHP.
+ */
+$wgUseGzip = false;
+
+/**
+ * Whether MediaWiki should send an ETag header. Seems to cause
+ * broken behavior with Squid 2.6, see bug 7098.
+ */
+$wgUseETag = false;
+
+/** Clock skew or the one-second resolution of time() can occasionally cause cache
+ * problems when the user requests two pages within a short period of time. This
+ * variable adds a given number of seconds to vulnerable timestamps, thereby giving
+ * a grace period.
+ */
+$wgClockSkewFudge = 5;
+
+/**
+ * Invalidate various caches when LocalSettings.php changes. This is equivalent
+ * to setting $wgCacheEpoch to the modification time of LocalSettings.php, as
+ * was previously done in the default LocalSettings.php file.
+ *
+ * On high-traffic wikis, this should be set to false, to avoid the need to
+ * check the file modification time, and to avoid the performance impact of
+ * unnecessary cache invalidations.
+ */
+$wgInvalidateCacheOnLocalSettingsChange = true;
+
+/** @} */ # end of cache settings
+
+/************************************************************************//**
+ * @name HTTP proxy (Squid) settings
+ *
+ * Many of these settings apply to any HTTP proxy used in front of MediaWiki,
+ * although they are referred to as Squid settings for historical reasons.
+ *
+ * Achieving a high hit ratio with an HTTP proxy requires special
+ * configuration. See http://www.mediawiki.org/wiki/Manual:Squid_caching for
+ * more details.
+ *
+ * @{
+ */
+
+/**
+ * Enable/disable Squid.
+ * See http://www.mediawiki.org/wiki/Manual:Squid_caching
+ */
+$wgUseSquid = false;
+
+/** If you run Squid3 with ESI support, enable this (default:false): */
+$wgUseESI = false;
+
+/** Send X-Vary-Options header for better caching (requires patched Squid) */
+$wgUseXVO = false;
+
+/**
+ * Internal server name as known to Squid, if different. Example:
+ * <code>
+ * $wgInternalServer = 'http://yourinternal.tld:8000';
+ * </code>
+ */
+$wgInternalServer = $wgServer;
+
+/**
+ * Cache timeout for the squid, will be sent as s-maxage (without ESI) or
+ * Surrogate-Control (with ESI). Without ESI, you should strip out s-maxage in
+ * the Squid config. 18000 seconds = 5 hours, more cache hits with 2678400 = 31
+ * days
+ */
+$wgSquidMaxage = 18000;
+
+/**
+ * Default maximum age for raw CSS/JS accesses
+ */
+$wgForcedRawSMaxage = 300;
+
+/**
+ * List of proxy servers to purge on changes; default port is 80. Use IP addresses.
+ *
+ * When MediaWiki is running behind a proxy, it will trust X-Forwarded-For
+ * headers sent/modified from these proxies when obtaining the remote IP address
+ *
+ * For a list of trusted servers which *aren't* purged, see $wgSquidServersNoPurge.
+ */
+$wgSquidServers = array();
+
+/**
+ * As above, except these servers aren't purged on page changes; use to set a
+ * list of trusted proxies, etc.
+ */
+$wgSquidServersNoPurge = array();
+
+/** Maximum number of titles to purge in any one client operation */
+$wgMaxSquidPurgeTitles = 400;
+
+/**
+ * HTCP multicast address. Set this to a multicast IP address to enable HTCP.
+ *
+ * Note that MediaWiki uses the old non-RFC compliant HTCP format, which was
+ * present in the earliest Squid implementations of the protocol.
+ */
+$wgHTCPMulticastAddress = false;
+
+/**
+ * HTCP multicast port.
+ * @see $wgHTCPMulticastAddress
+ */
+$wgHTCPPort = 4827;
+
+/**
+ * HTCP multicast TTL.
+ * @see $wgHTCPMulticastAddress
+ */
+$wgHTCPMulticastTTL = 1;
+
+/** Should forwarded Private IPs be accepted? */
+$wgUsePrivateIPs = false;
+
+/** @} */ # end of HTTP proxy settings
+
+/************************************************************************//**
+ * @name Language, regional and character encoding settings
+ * @{
+ */
+
/** Site language code, should be one of ./languages/Language(.*).php */
$wgLanguageCode = 'en';
/**
* Some languages need different word forms, usually for different cases.
- * Used in Language::convertGrammar().
+ * Used in Language::convertGrammar(). Example:
+ *
+ * <code>
+ * $wgGrammarForms['en']['genitive']['car'] = 'car\'s';
+ * </code>
*/
$wgGrammarForms = array();
-#$wgGrammarForms['en']['genitive']['car'] = 'car\'s';
/** Treat language links as magic connectors, not inline links */
$wgInterwikiMagic = true;
@@ -863,28 +1803,48 @@ $wgExtraLanguageNames = array();
* These codes are leftoffs from renames, or other legacy things.
* Also, qqq is a dummy "language" for documenting messages.
*/
-$wgDummyLanguageCodes = array( 'qqq', 'als', 'be-x-old', 'dk', 'fiu-vro', 'iu', 'nb', 'simple', 'tp' );
+$wgDummyLanguageCodes = array(
+ 'als',
+ 'bat-smg',
+ 'be-x-old',
+ 'dk',
+ 'fiu-vro',
+ 'iu',
+ 'nb',
+ 'qqq',
+ 'simple',
+ 'tp',
+);
-/** We speak UTF-8 all the time now, unless some oddities happen */
+/** @deprecated Since MediaWiki 1.5, this must always be set to UTF-8. */
$wgInputEncoding = 'UTF-8';
+/** @deprecated Since MediaWiki 1.5, this must always be set to UTF-8. */
$wgOutputEncoding = 'UTF-8';
+
+/**
+ * Character set for use in the article edit box. Language-specific encodings
+ * may be defined.
+ *
+ * This historic feature is one of the first that was added by former MediaWiki
+ * team leader Brion Vibber, and is used to support the Esperanto x-system.
+ */
$wgEditEncoding = '';
/**
- * Set this to true to replace Arabic presentation forms with their standard
+ * Set this to true to replace Arabic presentation forms with their standard
* forms in the U+0600-U+06FF block. This only works if $wgLanguageCode is
* set to "ar".
*
- * Note that pages with titles containing presentation forms will become
+ * Note that pages with titles containing presentation forms will become
* inaccessible, run maintenance/cleanupTitles.php to fix this.
*/
$wgFixArabicUnicode = true;
/**
* Set this to true to replace ZWJ-based chillu sequences in Malayalam text
- * with their Unicode 5.1 equivalents. This only works if $wgLanguageCode is
- * set to "ml". Note that some clients (even new clients as of 2010) do not
- * support these characters.
+ * with their Unicode 5.1 equivalents. This only works if $wgLanguageCode is
+ * set to "ml". Note that some clients (even new clients as of 2010) do not
+ * support these characters.
*
* If you enable this on an existing wiki, run maintenance/cleanupTitles.php to
* fix any ZWJ sequences in existing page titles.
@@ -892,25 +1852,71 @@ $wgFixArabicUnicode = true;
$wgFixMalayalamUnicode = true;
/**
- * Locale for LC_CTYPE, to work around http://bugs.php.net/bug.php?id=45132
- * For Unix-like operating systems, set this to to a locale that has a UTF-8
- * character set. Only the character set is relevant.
+ * Set this to always convert certain Unicode sequences to modern ones
+ * regardless of the content language. This has a small performance
+ * impact.
+ *
+ * See $wgFixArabicUnicode and $wgFixMalayalamUnicode for conversion
+ * details.
+ *
+ * @since 1.17
*/
-$wgShellLocale = 'en_US.utf8';
+$wgAllUnicodeFixes = false;
/**
- * Set this to eg 'ISO-8859-1' to perform character set
- * conversion when loading old revisions not marked with
- * "utf-8" flag. Use this when converting wiki to UTF-8
- * without the burdensome mass conversion of old text data.
+ * Set this to eg 'ISO-8859-1' to perform character set conversion when
+ * loading old revisions not marked with "utf-8" flag. Use this when
+ * converting a wiki from MediaWiki 1.4 or earlier to UTF-8 without the
+ * burdensome mass conversion of old text data.
*
- * NOTE! This DOES NOT touch any fields other than old_text.
- * Titles, comments, user names, etc still must be converted
- * en masse in the database before continuing as a UTF-8 wiki.
+ * NOTE! This DOES NOT touch any fields other than old_text.Titles, comments,
+ * user names, etc still must be converted en masse in the database before
+ * continuing as a UTF-8 wiki.
*/
$wgLegacyEncoding = false;
/**
+ * Browser Blacklist for unicode non compliant browsers. Contains a list of
+ * regexps : "/regexp/" matching problematic browsers. These browsers will
+ * be served encoded unicode in the edit box instead of real unicode.
+ */
+$wgBrowserBlackList = array(
+ /**
+ * Netscape 2-4 detection
+ * The minor version may contain strings such as "Gold" or "SGoldC-SGI"
+ * Lots of non-netscape user agents have "compatible", so it's useful to check for that
+ * with a negative assertion. The [UIN] identifier specifies the level of security
+ * in a Netscape/Mozilla browser, checking for it rules out a number of fakers.
+ * The language string is unreliable, it is missing on NS4 Mac.
+ *
+ * Reference: http://www.psychedelix.com/agents/index.shtml
+ */
+ '/^Mozilla\/2\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
+ '/^Mozilla\/3\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
+ '/^Mozilla\/4\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
+
+ /**
+ * MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>, Þ to <THORN> and Ð to <ETH>
+ *
+ * Known useragents:
+ * - Mozilla/4.0 (compatible; MSIE 5.0; Mac_PowerPC)
+ * - Mozilla/4.0 (compatible; MSIE 5.15; Mac_PowerPC)
+ * - Mozilla/4.0 (compatible; MSIE 5.23; Mac_PowerPC)
+ * - [...]
+ *
+ * @link http://en.wikipedia.org/w/index.php?title=User%3A%C6var_Arnfj%F6r%F0_Bjarmason%2Ftestme&diff=12356041&oldid=12355864
+ * @link http://en.wikipedia.org/wiki/Template%3AOS9
+ */
+ '/^Mozilla\/4\.0 \(compatible; MSIE \d+\.\d+; Mac_PowerPC\)/',
+
+ /**
+ * Google wireless transcoder, seems to eat a lot of chars alive
+ * http://it.wikipedia.org/w/index.php?title=Luciano_Ligabue&diff=prev&oldid=8857361
+ */
+ '/^Mozilla\/4\.0 \(compatible; MSIE 6.0; Windows NT 5.0; Google Wireless Transcoder;\)/'
+);
+
+/**
* If set to true, the MediaWiki 1.4 to 1.5 schema conversion will
* create stub reference rows in the text table instead of copying
* the full text of all current entries from 'cur' to 'text'.
@@ -927,17 +1933,173 @@ $wgLegacyEncoding = false;
*/
$wgLegacySchemaConversion = false;
+/**
+ * Enable to allow rewriting dates in page text.
+ * DOES NOT FORMAT CORRECTLY FOR MOST LANGUAGES.
+ */
+$wgUseDynamicDates = false;
+/**
+ * Enable dates like 'May 12' instead of '12 May', this only takes effect if
+ * the interface is set to English.
+ */
+$wgAmericanDates = false;
+/**
+ * For Hindi and Arabic use local numerals instead of Western style (0-9)
+ * numerals in interface.
+ */
+$wgTranslateNumerals = true;
+
+/**
+ * Translation using MediaWiki: namespace.
+ * Interface messages will be loaded from the database.
+ */
+$wgUseDatabaseMessages = true;
+
+/**
+ * Expiry time for the message cache key
+ */
+$wgMsgCacheExpiry = 86400;
+
+/**
+ * Maximum entry size in the message cache, in bytes
+ */
+$wgMaxMsgCacheEntrySize = 10000;
+
+/** Whether to enable language variant conversion. */
+$wgDisableLangConversion = false;
+
+/** Whether to enable language variant conversion for links. */
+$wgDisableTitleConversion = false;
+
+/** Whether to enable cononical language links in meta data. */
+$wgCanonicalLanguageLinks = true;
+
+/** Default variant code, if false, the default will be the language code */
+$wgDefaultLanguageVariant = false;
+
+/**
+ * Disabled variants array of language variant conversion. Example:
+ * <code>
+ * $wgDisabledVariants[] = 'zh-mo';
+ * $wgDisabledVariants[] = 'zh-my';
+ * </code>
+ *
+ * or:
+ *
+ * <code>
+ * $wgDisabledVariants = array('zh-mo', 'zh-my');
+ * </code>
+ */
+$wgDisabledVariants = array();
+
+/**
+ * Like $wgArticlePath, but on multi-variant wikis, this provides a
+ * path format that describes which parts of the URL contain the
+ * language variant. For Example:
+ *
+ * $wgLanguageCode = 'sr';
+ * $wgVariantArticlePath = '/$2/$1';
+ * $wgArticlePath = '/wiki/$1';
+ *
+ * A link to /wiki/ would be redirected to /sr/Главна_страна
+ *
+ * It is important that $wgArticlePath not overlap with possible values
+ * of $wgVariantArticlePath.
+ */
+$wgVariantArticlePath = false;
+
+/**
+ * Show a bar of language selection links in the user login and user
+ * registration forms; edit the "loginlanguagelinks" message to
+ * customise these.
+ */
+$wgLoginLanguageSelector = false;
+
+/**
+ * When translating messages with wfMsg(), it is not always clear what should
+ * be considered UI messages and what should be content messages.
+ *
+ * For example, for the English Wikipedia, there should be only one 'mainpage',
+ * so when getting the link for 'mainpage', we should treat it as site content
+ * and call wfMsgForContent(), but for rendering the text of the link, we call
+ * wfMsg(). The code behaves this way by default. However, sites like the
+ * Wikimedia Commons do offer different versions of 'mainpage' and the like for
+ * different languages. This array provides a way to override the default
+ * behavior. For example, to allow language-specific main page and community
+ * portal, set
+ *
+ * $wgForceUIMsgAsContentMsg = array( 'mainpage', 'portal-url' );
+ */
+$wgForceUIMsgAsContentMsg = array();
+
+/**
+ * Fake out the timezone that the server thinks it's in. This will be used for
+ * date display and not for what's stored in the DB. Leave to null to retain
+ * your server's OS-based timezone value.
+ *
+ * This variable is currently used only for signature formatting and for local
+ * time/date parser variables ({{LOCALTIME}} etc.)
+ *
+ * Timezones can be translated by editing MediaWiki messages of type
+ * timezone-nameinlowercase like timezone-utc.
+ *
+ * Examples:
+ * <code>
+ * $wgLocaltimezone = 'GMT';
+ * $wgLocaltimezone = 'PST8PDT';
+ * $wgLocaltimezone = 'Europe/Sweden';
+ * $wgLocaltimezone = 'CET';
+ * </code>
+ */
+$wgLocaltimezone = null;
+
+/**
+ * Set an offset from UTC in minutes to use for the default timezone setting
+ * for anonymous users and new user accounts.
+ *
+ * This setting is used for most date/time displays in the software, and is
+ * overrideable in user preferences. It is *not* used for signature timestamps.
+ *
+ * You can set it to match the configured server timezone like this:
+ * $wgLocalTZoffset = date("Z") / 60;
+ *
+ * If your server is not configured for the timezone you want, you can set
+ * this in conjunction with the signature timezone and override the PHP default
+ * timezone like so:
+ * $wgLocaltimezone="Europe/Berlin";
+ * date_default_timezone_set( $wgLocaltimezone );
+ * $wgLocalTZoffset = date("Z") / 60;
+ *
+ * Leave at NULL to show times in universal time (UTC/GMT).
+ */
+$wgLocalTZoffset = null;
+
+/** @} */ # End of language/charset settings
+
+/*************************************************************************//**
+ * @name Output format and skin settings
+ * @{
+ */
+
+/** The default Content-Type header. */
$wgMimeType = 'text/html';
+
+/** The content type used in script tags. */
$wgJsMimeType = 'text/javascript';
+
+/** The HTML document type. */
$wgDocType = '-//W3C//DTD XHTML 1.0 Transitional//EN';
+
+/** The URL of the document type declaration. */
$wgDTD = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd';
+
+/** The default xmlns attribute. */
$wgXhtmlDefaultNamespace = 'http://www.w3.org/1999/xhtml';
/**
- * Should we output an HTML5 doctype? This mode is still experimental, but
- * all indications are that it should be usable, so it's enabled by default.
- * If all goes well, it will be removed and become always true before the 1.16
- * release.
+ * Should we output an HTML5 doctype? If false, use XHTML 1.0 Transitional
+ * instead, and disable HTML5 features. This may eventually be removed and set
+ * to always true.
*/
$wgHtml5 = true;
@@ -986,106 +2148,421 @@ $wgWellFormedXml = true;
*/
$wgXhtmlNamespaces = array();
-/** Enable to allow rewriting dates in page text.
- * DOES NOT FORMAT CORRECTLY FOR MOST LANGUAGES */
-$wgUseDynamicDates = false;
-/** Enable dates like 'May 12' instead of '12 May', this only takes effect if
- * the interface is set to English
+/**
+ * Show IP address, for non-logged in users. It's necessary to switch this off
+ * for some forms of caching.
*/
-$wgAmericanDates = false;
+$wgShowIPinHeader = true;
+
/**
- * For Hindi and Arabic use local numerals instead of Western style (0-9)
- * numerals in interface.
+ * Site notice shown at the top of each page
+ *
+ * MediaWiki:Sitenotice page, which will override this. You can also
+ * provide a separate message for logged-out users using the
+ * MediaWiki:Anonnotice page.
*/
-$wgTranslateNumerals = true;
+$wgSiteNotice = '';
/**
- * Translation using MediaWiki: namespace.
- * Interface messages will be loaded from the database.
+ * A subtitle to add to the tagline, for skins that have it/
*/
-$wgUseDatabaseMessages = true;
+$wgExtraSubtitle = '';
/**
- * Expiry time for the message cache key
+ * If this is set, a "donate" link will appear in the sidebar. Set it to a URL.
*/
-$wgMsgCacheExpiry = 86400;
+$wgSiteSupportPage = '';
/**
- * Maximum entry size in the message cache, in bytes
+ * Validate the overall output using tidy and refuse
+ * to display the page if it's not valid.
*/
-$wgMaxMsgCacheEntrySize = 10000;
+$wgValidateAllHtml = false;
/**
- * If true, serialized versions of the messages arrays will be
- * read from the 'serialized' subdirectory if they are present.
- * Set to false to always use the Messages files, regardless of
- * whether they are up to date or not.
+ * Default skin, for new users and anonymous visitors. Registered users may
+ * change this to any one of the other available skins in their preferences.
+ * This has to be completely lowercase; see the "skins" directory for the list
+ * of available skins.
*/
-$wgEnableSerializedMessages = true;
+$wgDefaultSkin = 'vector';
+
+/**
+* Should we allow the user's to select their own skin that will override the default?
+* @deprecated in 1.16, use $wgHiddenPrefs[] = 'skin' to disable it
+*/
+$wgAllowUserSkin = true;
/**
- * Set to false if you are thorough system admin who always remembers to keep
- * serialized files up to date to save few mtime calls.
+ * Specify the name of a skin that should not be presented in the list of
+ * available skins. Use for blacklisting a skin which you do not want to
+ * remove from the .../skins/ directory
*/
-$wgCheckSerialized = true;
+$wgSkipSkin = '';
+/** Array for more like $wgSkipSkin. */
+$wgSkipSkins = array();
-/** Whether to enable language variant conversion. */
-$wgDisableLangConversion = false;
+/**
+ * Optionally, we can specify a stylesheet to use for media="handheld".
+ * This is recognized by some, but not all, handheld/mobile/PDA browsers.
+ * If left empty, compliant handheld browsers won't pick up the skin
+ * stylesheet, which is specified for 'screen' media.
+ *
+ * Can be a complete URL, base-relative path, or $wgStylePath-relative path.
+ * Try 'chick/main.css' to apply the Chick styles to the MonoBook HTML.
+ *
+ * Will also be switched in when 'handheld=yes' is added to the URL, like
+ * the 'printable=yes' mode for print media.
+ */
+$wgHandheldStyle = false;
-/** Whether to enable language variant conversion for links. */
-$wgDisableTitleConversion = false;
+/**
+ * If set, 'screen' and 'handheld' media specifiers for stylesheets are
+ * transformed such that they apply to the iPhone/iPod Touch Mobile Safari,
+ * which doesn't recognize 'handheld' but does support media queries on its
+ * screen size.
+ *
+ * Consider only using this if you have a *really good* handheld stylesheet,
+ * as iPhone users won't have any way to disable it and use the "grown-up"
+ * styles instead.
+ */
+$wgHandheldForIPhone = false;
-/** Default variant code, if false, the default will be the language code */
-$wgDefaultLanguageVariant = false;
+/**
+ * Allow user Javascript page?
+ * This enables a lot of neat customizations, but may
+ * increase security risk to users and server load.
+ */
+$wgAllowUserJs = false;
-/** Disabled variants array of language variant conversion.
- * example:
- * $wgDisabledVariants[] = 'zh-mo';
- * $wgDisabledVariants[] = 'zh-my';
+/**
+ * Allow user Cascading Style Sheets (CSS)?
+ * This enables a lot of neat customizations, but may
+ * increase security risk to users and server load.
+ */
+$wgAllowUserCss = false;
+
+/**
+ * Allow user-preferences implemented in CSS?
+ * This allows users to customise the site appearance to a greater
+ * degree; disabling it will improve page load times.
+ */
+$wgAllowUserCssPrefs = true;
+
+/** Use the site's Javascript page? */
+$wgUseSiteJs = true;
+
+/** Use the site's Cascading Style Sheets (CSS)? */
+$wgUseSiteCss = true;
+
+/**
+ * Set to false to disable application of access keys and tooltips,
+ * eg to avoid keyboard conflicts with system keys or as a low-level
+ * optimization.
+ */
+$wgEnableTooltipsAndAccesskeys = true;
+
+/**
+ * Break out of framesets. This can be used to prevent clickjacking attacks,
+ * or to prevent external sites from framing your site with ads.
+ */
+$wgBreakFrames = false;
+
+/**
+ * The X-Frame-Options header to send on pages sensitive to clickjacking
+ * attacks, such as edit pages. This prevents those pages from being displayed
+ * in a frame or iframe. The options are:
*
- * or:
- * $wgDisabledVariants = array('zh-mo', 'zh-my');
+ * - 'DENY': Do not allow framing. This is recommended for most wikis.
+ *
+ * - 'SAMEORIGIN': Allow framing by pages on the same domain. This can be used
+ * to allow framing within a trusted domain. This is insecure if there
+ * is a page on the same domain which allows framing of arbitrary URLs.
+ *
+ * - false: Allow all framing. This opens up the wiki to XSS attacks and thus
+ * full compromise of local user accounts. Private wikis behind a
+ * corporate firewall are especially vulnerable. This is not
+ * recommended.
+ *
+ * For extra safety, set $wgBreakFrames = true, to prevent framing on all pages,
+ * not just edit pages.
*/
-$wgDisabledVariants = array();
+$wgEditPageFrameOptions = 'DENY';
/**
- * Like $wgArticlePath, but on multi-variant wikis, this provides a
- * path format that describes which parts of the URL contain the
- * language variant. For Example:
+ * Disable output compression (enabled by default if zlib is available)
+ */
+$wgDisableOutputCompression = false;
+
+/**
+ * Should we allow a broader set of characters in id attributes, per HTML5? If
+ * not, use only HTML 4-compatible IDs. This option is for testing -- when the
+ * functionality is ready, it will be on by default with no option.
*
- * $wgLanguageCode = 'sr';
- * $wgVariantArticlePath = '/$2/$1';
- * $wgArticlePath = '/wiki/$1';
+ * Currently this appears to work fine in all browsers, but it's disabled by
+ * default because it normalizes id's a bit too aggressively, breaking preexisting
+ * content (particularly Cite). See bug 27733, bug 27694, bug 27474.
+ */
+$wgExperimentalHtmlIds = false;
+
+/**
+ * Abstract list of footer icons for skins in place of old copyrightico and poweredbyico code
+ * You can add new icons to the built in copyright or poweredby, or you can create
+ * a new block. Though note that you may need to add some custom css to get good styling
+ * of new blocks in monobook. vector and modern should work without any special css.
+ *
+ * $wgFooterIcons itself is a key/value array.
+ * The key is the name of a block that the icons will be wrapped in. The final id varies
+ * by skin; Monobook and Vector will turn poweredby into f-poweredbyico while Modern
+ * turns it into mw_poweredby.
+ * The value is either key/value array of icons or a string.
+ * In the key/value array the key may or may not be used by the skin but it can
+ * be used to find the icon and unset it or change the icon if needed.
+ * This is useful for disabling icons that are set by extensions.
+ * The value should be either a string or an array. If it is a string it will be output
+ * directly as html, however some skins may choose to ignore it. An array is the preferred format
+ * 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
+ * url: The url to use in the <a> arround the text or icon, if not set an <a> will not be outputted
+ * alt: This is the text form of the icon, it will be displayed without an image in
+ * skins like Modern or if src is not set, and will otherwise be used as
+ * the alt="" for the image. This key is required.
+ * width and height: If the icon specified by src is not of the standard size
+ * you can specify the size of image to use with these keys.
+ * Otherwise they will default to the standard 88x31.
+ */
+$wgFooterIcons = array(
+ "copyright" => array(
+ "copyright" => array(), // placeholder for the built in copyright icon
+ ),
+ "poweredby" => array(
+ "mediawiki" => array(
+ "src" => null, // Defaults to "$wgStylePath/common/images/poweredby_mediawiki_88x31.png"
+ "url" => "http://www.mediawiki.org/",
+ "alt" => "Powered by MediaWiki",
+ )
+ ),
+);
+
+/**
+ * Search form behavior for Vector skin only
+ * true = use an icon search button
+ * false = use Go & Search buttons
+ */
+$wgVectorUseSimpleSearch = false;
+
+/**
+ * Watch and unwatch as an icon rather than a link for Vector skin only
+ * true = use an icon watch/unwatch button
+ * false = use watch/unwatch text link
+ */
+$wgVectorUseIconWatch = false;
+
+/**
+ * Show the name of the current variant as a label in the variants drop-down menu
+ */
+$wgVectorShowVariantName = false;
+
+/**
+ * Display user edit counts in various prominent places.
+ */
+$wgEdititis = false;
+
+/**
+ * Experimental better directionality support.
+ */
+$wgBetterDirectionality = false;
+
+
+/** @} */ # End of output format settings }
+
+/*************************************************************************//**
+ * @name Resource loader settings
+ * @{
+ */
+
+/**
+ * Client-side resource modules. Extensions should add their module definitions
+ * here.
*
- * A link to /wiki/ would be redirected to /sr/Главна_страна
+ * Example:
+ * $wgResourceModules['ext.myExtension'] = array(
+ * 'scripts' => 'myExtension.js',
+ * 'styles' => 'myExtension.css',
+ * 'dependencies' => array( 'jquery.cookie', 'jquery.tabIndex' ),
+ * 'localBasePath' => dirname( __FILE__ ),
+ * 'remoteExtPath' => 'MyExtension',
+ * );
+ */
+$wgResourceModules = array();
+
+/**
+ * Maximum time in seconds to cache resources served by the resource loader
+ */
+$wgResourceLoaderMaxage = array(
+ 'versioned' => array(
+ // Squid/Varnish but also any other public proxy cache between the client and MediaWiki
+ 'server' => 30 * 24 * 60 * 60, // 30 days
+ // On the client side (e.g. in the browser cache).
+ 'client' => 30 * 24 * 60 * 60, // 30 days
+ ),
+ 'unversioned' => array(
+ 'server' => 5 * 60, // 5 minutes
+ 'client' => 5 * 60, // 5 minutes
+ ),
+);
+
+/**
+ * Whether to embed private modules inline with HTML output or to bypass
+ * caching and check the user parameter against $wgUser to prevent
+ * unauthorized access to private modules.
+ */
+$wgResourceLoaderInlinePrivateModules = true;
+
+/**
+ * The default debug mode (on/off) for of ResourceLoader requests. This will still
+ * be overridden when the debug URL parameter is used.
+ */
+$wgResourceLoaderDebug = false;
+
+/**
+ * Enable embedding of certain resources using Edge Side Includes. This will
+ * improve performance but only works if there is something in front of the
+ * web server (e..g a Squid or Varnish server) configured to process the ESI.
+ */
+$wgResourceLoaderUseESI = false;
+
+/**
+ * Put each statement on its own line when minifying JavaScript. This makes
+ * debugging in non-debug mode a bit easier.
+ */
+$wgResourceLoaderMinifierStatementsOnOwnLine = false;
+
+/**
+ * Maximum line length when minifying JavaScript. This is not a hard maximum:
+ * the minifier will try not to produce lines longer than this, but may be
+ * forced to do so in certain cases.
+ */
+$wgResourceLoaderMinifierMaxLineLength = 1000;
+
+/**
+ * Whether to include the mediawiki.legacy JS library (old wikibits.js), and its
+ * dependencies
+ */
+$wgIncludeLegacyJavaScript = true;
+
+/**
+ * If set to a positive number, ResourceLoader will not generate URLs whose
+ * query string is more than this many characters long, and will instead use
+ * multiple requests with shorter query strings. This degrades performance,
+ * but may be needed if your web server has a low (less than, say 1024)
+ * query string length limit or a low value for suhosin.get.max_value_length
+ * that you can't increase.
*
- * It is important that $wgArticlePath not overlap with possible values
- * of $wgVariantArticlePath.
+ * If set to a negative number, ResourceLoader will assume there is no query
+ * string length limit.
+ */
+$wgResourceLoaderMaxQueryLength = -1;
+
+/** @} */ # End of resource loader settings }
+
+
+/*************************************************************************//**
+ * @name Page title and interwiki link settings
+ * @{
*/
-$wgVariantArticlePath = false;///< defaults to false
/**
- * Show a bar of language selection links in the user login and user
- * registration forms; edit the "loginlanguagelinks" message to
- * customise these
+ * Name of the project namespace. If left set to false, $wgSitename will be
+ * used instead.
*/
-$wgLoginLanguageSelector = false;
+$wgMetaNamespace = false;
+
+/**
+ * Name of the project talk namespace.
+ *
+ * Normally you can ignore this and it will be something like
+ * $wgMetaNamespace . "_talk". In some languages, you may want to set this
+ * manually for grammatical reasons.
+ */
+$wgMetaNamespaceTalk = false;
+
+/**
+ * Additional namespaces. If the namespaces defined in Language.php and
+ * Namespace.php are insufficient, you can create new ones here, for example,
+ * to import Help files in other languages. You can also override the namespace
+ * names of existing namespaces. Extensions developers should use
+ * $wgCanonicalNamespaceNames.
+ *
+ * PLEASE NOTE: Once you delete a namespace, the pages in that namespace will
+ * no longer be accessible. If you rename it, then you can access them through
+ * the new namespace name.
+ *
+ * Custom namespaces should start at 100 to avoid conflicting with standard
+ * namespaces, and should always follow the even/odd main/talk pattern.
+ */
+#$wgExtraNamespaces =
+# array(100 => "Hilfe",
+# 101 => "Hilfe_Diskussion",
+# 102 => "Aide",
+# 103 => "Discussion_Aide"
+# );
+$wgExtraNamespaces = array();
/**
- * Whether to use zhdaemon to perform Chinese text processing
- * zhdaemon is under developement, so normally you don't want to
- * use it unless for testing
+ * Namespace aliases
+ * These are alternate names for the primary localised namespace names, which
+ * are defined by $wgExtraNamespaces and the language file. If a page is
+ * requested with such a prefix, the request will be redirected to the primary
+ * name.
+ *
+ * Set this to a map from namespace names to IDs.
+ * Example:
+ * $wgNamespaceAliases = array(
+ * 'Wikipedian' => NS_USER,
+ * 'Help' => 100,
+ * );
*/
-$wgUseZhdaemon = false;
-$wgZhdaemonHost="localhost";
-$wgZhdaemonPort=2004;
+$wgNamespaceAliases = array();
+/**
+ * Allowed title characters -- regex character class
+ * Don't change this unless you know what you're doing
+ *
+ * Problematic punctuation:
+ * - []{}|# Are needed for link syntax, never enable these
+ * - <> Causes problems with HTML escaping, don't use
+ * - % Enabled by default, minor problems with path to query rewrite rules, see below
+ * - + Enabled by default, but doesn't work with path to query rewrite rules, corrupted by apache
+ * - ? Enabled by default, but doesn't work with path to PATH_INFO rewrites
+ *
+ * All three of these punctuation problems can be avoided by using an alias, instead of a
+ * rewrite rule of either variety.
+ *
+ * The problem with % is that when using a path to query rewrite rule, URLs are
+ * double-unescaped: once by Apache's path conversion code, and again by PHP. So
+ * %253F, for example, becomes "?". Our code does not double-escape to compensate
+ * for this, indeed double escaping would break if the double-escaped title was
+ * passed in the query string rather than the path. This is a minor security issue
+ * because articles can be created such that they are hard to view or edit.
+ *
+ * In some rare cases you may wish to remove + for compatibility with old links.
+ *
+ * Theoretically 0x80-0x9F of ISO 8859-1 should be disallowed, but
+ * this breaks interlanguage links
+ */
+$wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+";
-# Miscellaneous configuration settings
-#
+/**
+ * The interwiki prefix of the current wiki, or false if it doesn't have one.
+ */
+$wgLocalInterwiki = false;
-$wgLocalInterwiki = 'w';
-$wgInterwikiExpiry = 10800; # Expiry time for cache of interwiki table
+/**
+ * Expiry time for cache of interwiki table
+ */
+$wgInterwikiExpiry = 10800;
/** Interwiki caching settings.
$wgInterwikiCache specifies path to constant database file
@@ -1121,15 +2598,110 @@ $wgInterwikiFallbackSite = 'wiki';
*/
$wgRedirectSources = false;
+/**
+ * Set this to false to avoid forcing the first letter of links to capitals.
+ * WARNING: may break links! This makes links COMPLETELY case-sensitive. Links
+ * appearing with a capital at the beginning of a sentence will *not* go to the
+ * same place as links in the middle of a sentence using a lowercase initial.
+ */
+$wgCapitalLinks = true;
-$wgShowIPinHeader = true; # For non-logged in users
-$wgMaxSigChars = 255; # Maximum number of Unicode characters in signature
-$wgMaxArticleSize = 2048; # Maximum article size in kilobytes
-# Maximum number of bytes in username. You want to run the maintenance
-# script ./maintenancecheckUsernames.php once you have changed this value
-$wgMaxNameChars = 255;
+/**
+ * @since 1.16 - This can now be set per-namespace. Some special namespaces (such
+ * as Special, see MWNamespace::$alwaysCapitalizedNamespaces for the full list) must be
+ * true by default (and setting them has no effect), due to various things that
+ * require them to be so. Also, since Talk namespaces need to directly mirror their
+ * associated content namespaces, the values for those are ignored in favor of the
+ * subject namespace's setting. Setting for NS_MEDIA is taken automatically from
+ * NS_FILE.
+ * EX: $wgCapitalLinkOverrides[ NS_FILE ] = false;
+ */
+$wgCapitalLinkOverrides = array();
+
+/** Which namespaces should support subpages?
+ * See Language.php for a list of namespaces.
+ */
+$wgNamespacesWithSubpages = array(
+ NS_TALK => true,
+ NS_USER => true,
+ NS_USER_TALK => true,
+ NS_PROJECT_TALK => true,
+ NS_FILE_TALK => true,
+ NS_MEDIAWIKI => true,
+ NS_MEDIAWIKI_TALK => true,
+ NS_TEMPLATE_TALK => true,
+ NS_HELP_TALK => true,
+ NS_CATEGORY_TALK => true
+);
+
+/**
+ * Array of namespaces which can be deemed to contain valid "content", as far
+ * as the site statistics are concerned. Useful if additional namespaces also
+ * contain "content" which should be considered when generating a count of the
+ * number of articles in the wiki.
+ */
+$wgContentNamespaces = array( NS_MAIN );
+
+/**
+ * Max number of redirects to follow when resolving redirects.
+ * 1 means only the first redirect is followed (default behavior).
+ * 0 or less means no redirects are followed.
+ */
+$wgMaxRedirects = 1;
+
+/**
+ * Array of invalid page redirect targets.
+ * Attempting to create a redirect to any of the pages in this array
+ * will make the redirect fail.
+ * Userlogout is hard-coded, so it does not need to be listed here.
+ * (bug 10569) Disallow Mypage and Mytalk as well.
+ *
+ * As of now, this only checks special pages. Redirects to pages in
+ * other namespaces cannot be invalidated by this variable.
+ */
+$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' );
+
+/** @} */ # End of title and interwiki settings }
+
+/************************************************************************//**
+ * @name Parser settings
+ * These settings configure the transformation from wikitext to HTML.
+ * @{
+ */
+
+/**
+ * Parser configuration. Associative array with the following members:
+ *
+ * class The class name
+ *
+ * preprocessorClass The preprocessor class. Two classes are currently available:
+ * Preprocessor_Hash, which uses plain PHP arrays for tempoarary
+ * storage, and Preprocessor_DOM, which uses the DOM module for
+ * temporary storage. Preprocessor_DOM generally uses less memory;
+ * the speed of the two is roughly the same.
+ *
+ * If this parameter is not given, it uses Preprocessor_DOM if the
+ * DOM module is available, otherwise it uses Preprocessor_Hash.
+ *
+ * The entire associative array will be passed through to the constructor as
+ * the first parameter. Note that only Setup.php can use this variable --
+ * the configuration will change at runtime via $wgParser member functions, so
+ * the contents of this variable will be out-of-date. The variable can only be
+ * changed during LocalSettings.php, in particular, it can't be changed during
+ * an extension setup function.
+ */
+$wgParserConf = array(
+ 'class' => 'Parser',
+ #'preprocessorClass' => 'Preprocessor_Hash',
+);
+
+/** Maximum indent level of toc. */
+$wgMaxTocLevel = 999;
-$wgMaxPPNodeCount = 1000000; # A complexity limit on template expansion
+/**
+ * A complexity limit on template expansion
+ */
+$wgMaxPPNodeCount = 1000000;
/**
* Maximum recursion depth for templates within templates.
@@ -1138,207 +2710,460 @@ $wgMaxPPNodeCount = 1000000; # A complexity limit on template expansion
* stop the parser before it hits the xdebug limit.
*/
$wgMaxTemplateDepth = 40;
+
+/** @see $wgMaxTemplateDepth */
$wgMaxPPExpandDepth = 40;
+/** The external URL protocols */
+$wgUrlProtocols = array(
+ 'http://',
+ 'https://',
+ 'ftp://',
+ 'irc://',
+ 'gopher://',
+ 'telnet://', // Well if we're going to support the above.. -ævar
+ 'nntp://', // @bug 3808 RFC 1738
+ 'worldwind://',
+ 'mailto:',
+ 'news:',
+ 'svn://',
+ 'git://',
+ 'mms://',
+);
+
/**
* If true, removes (substitutes) templates in "~~~~" signatures.
*/
$wgCleanSignatures = true;
-$wgExtraSubtitle = '';
-$wgSiteSupportPage = ''; # A page where you users can receive donations
+/** Whether to allow inline image pointing to other websites */
+$wgAllowExternalImages = false;
/**
- * Set this to a string to put the wiki into read-only mode. The text will be
- * used as an explanation to users.
+ * If the above is false, you can specify an exception here. Image URLs
+ * that start with this string are then rendered, while all others are not.
+ * You can use this to set up a trusted, simple repository of images.
+ * You may also specify an array of strings to allow multiple sites
*
- * This prevents most write operations via the web interface. Cache updates may
- * still be possible. To prevent database writes completely, use the read_only
- * option in MySQL.
+ * Examples:
+ * <code>
+ * $wgAllowExternalImagesFrom = 'http://127.0.0.1/';
+ * $wgAllowExternalImagesFrom = array( 'http://127.0.0.1/', 'http://example.com' );
+ * </code>
*/
-$wgReadOnly = null;
+$wgAllowExternalImagesFrom = '';
-/***
- * If this lock file exists (size > 0), the wiki will be forced into read-only mode.
- * Its contents will be shown to users as part of the read-only warning
- * message.
+/** If $wgAllowExternalImages is false, you can allow an on-wiki
+ * whitelist of regular expression fragments to match the image URL
+ * against. If the image matches one of the regular expression fragments,
+ * The image will be displayed.
+ *
+ * Set this to true to enable the on-wiki whitelist (MediaWiki:External image whitelist)
+ * Or false to disable it
*/
-$wgReadOnlyFile = false; ///< defaults to "{$wgUploadDirectory}/lock_yBgMBwiR";
+$wgEnableImageWhitelist = true;
/**
- * Filename for debug logging. See http://www.mediawiki.org/wiki/How_to_debug
- * The debug log file should be not be publicly accessible if it is used, as it
- * may contain private data.
+ * A different approach to the above: simply allow the <img> tag to be used.
+ * This allows you to specify alt text and other attributes, copy-paste HTML to
+ * your wiki more easily, etc. However, allowing external images in any manner
+ * will allow anyone with editing rights to snoop on your visitors' IP
+ * addresses and so forth, if they wanted to, by inserting links to images on
+ * sites they control.
*/
-$wgDebugLogFile = '';
+$wgAllowImageTag = false;
/**
- * Prefix for debug log lines
+ * $wgUseTidy: use tidy to make sure HTML output is sane.
+ * Tidy is a free tool that fixes broken HTML.
+ * See http://www.w3.org/People/Raggett/tidy/
+ *
+ * - $wgTidyBin should be set to the path of the binary and
+ * - $wgTidyConf to the path of the configuration file.
+ * - $wgTidyOpts can include any number of parameters.
+ * - $wgTidyInternal controls the use of the PECL extension to use an in-
+ * process tidy library instead of spawning a separate program.
+ * Normally you shouldn't need to override the setting except for
+ * debugging. To install, use 'pear install tidy' and add a line
+ * 'extension=tidy.so' to php.ini.
*/
-$wgDebugLogPrefix = '';
+$wgUseTidy = false;
+/** @see $wgUseTidy */
+$wgAlwaysUseTidy = false;
+/** @see $wgUseTidy */
+$wgTidyBin = 'tidy';
+/** @see $wgUseTidy */
+$wgTidyConf = $IP.'/includes/tidy.conf';
+/** @see $wgUseTidy */
+$wgTidyOpts = '';
+/** @see $wgUseTidy */
+$wgTidyInternal = extension_loaded( 'tidy' );
/**
- * If true, instead of redirecting, show a page with a link to the redirect
- * destination. This allows for the inspection of PHP error messages, and easy
- * resubmission of form data. For developer use only.
+ * Put tidy warnings in HTML comments
+ * Only works for internal tidy.
*/
-$wgDebugRedirects = false;
+$wgDebugTidy = false;
+
+/** Allow raw, unchecked HTML in <html>...</html> sections.
+ * THIS IS VERY DANGEROUS on a publically editable site, so USE wgGroupPermissions
+ * TO RESTRICT EDITING to only those that you trust
+ */
+$wgRawHtml = false;
/**
- * If true, log debugging data from action=raw.
- * This is normally false to avoid overlapping debug entries due to gen=css and
- * gen=js requests.
+ * Set a default target for external links, e.g. _blank to pop up a new window
*/
-$wgDebugRawPage = false;
+$wgExternalLinkTarget = false;
/**
- * Send debug data to an HTML comment in the output.
+ * If true, external URL links in wiki text will be given the
+ * rel="nofollow" attribute as a hint to search engines that
+ * they should not be followed for ranking purposes as they
+ * are user-supplied and thus subject to spamming.
+ */
+$wgNoFollowLinks = true;
+
+/**
+ * Namespaces in which $wgNoFollowLinks doesn't apply.
+ * See Language.php for a list of namespaces.
+ */
+$wgNoFollowNsExceptions = array();
+
+/**
+ * If this is set to an array of domains, external links to these domain names
+ * (or any subdomains) will not be set to rel="nofollow" regardless of the
+ * value of $wgNoFollowLinks. For instance:
*
- * This may occasionally be useful when supporting a non-technical end-user. It's
- * more secure than exposing the debug log file to the web, since the output only
- * contains private data for the current user. But it's not ideal for development
- * use since data is lost on fatal errors and redirects.
+ * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org' );
+ *
+ * This would add rel="nofollow" to links to de.wikipedia.org, but not
+ * en.wikipedia.org, wiktionary.org, en.wiktionary.org, us.en.wikipedia.org,
+ * etc.
*/
-$wgDebugComments = false;
+$wgNoFollowDomainExceptions = array();
/**
- * Write SQL queries to the debug log
+ * Allow DISPLAYTITLE to change title display
*/
-$wgDebugDumpSql = false;
+$wgAllowDisplayTitle = true;
/**
- * Set to an array of log group keys to filenames.
- * If set, wfDebugLog() output for that group will go to that file instead
- * of the regular $wgDebugLogFile. Useful for enabling selective logging
- * in production.
+ * For consistency, restrict DISPLAYTITLE to titles that normalize to the same
+ * canonical DB key.
*/
-$wgDebugLogGroups = array();
+$wgRestrictDisplayTitle = true;
/**
- * Display debug data at the bottom of the main content area.
- *
- * Useful for developers and technical users trying to working on a closed wiki.
+ * Maximum number of calls per parse to expensive parser functions such as
+ * PAGESINCATEGORY.
*/
-$wgShowDebug = false;
+$wgExpensiveParserFunctionLimit = 100;
/**
- * Prefix debug messages with relative timestamp. Very-poor man's profiler.
+ * Preprocessor caching threshold
*/
-$wgDebugTimestamps = false;
+$wgPreprocessorCacheThreshold = 1000;
/**
- * Print HTTP headers for every request in the debug information.
+ * Enable interwiki transcluding. Only when iw_trans=1.
*/
-$wgDebugPrintHttpHeaders = true;
+$wgEnableScaryTranscluding = false;
/**
- * Show the contents of $wgHooks in Special:Version
+ * Expiry time for interwiki transclusion
+ */
+$wgTranscludeCacheExpiry = 3600;
+
+/** @} */ # end of parser settings }
+
+/************************************************************************//**
+ * @name Statistics
+ * @{
*/
-$wgSpecialVersionShowHooks = false;
/**
- * Whether to show "we're sorry, but there has been a database error" pages.
- * Displaying errors aids in debugging, but may display information useful
- * to an attacker.
+ * Under which condition should a page in the main namespace be counted
+ * as a valid article? If $wgUseCommaCount is set to true, it will be
+ * counted if it contains at least one comma. If it is set to false
+ * (default), it will only be counted if it contains at least one [[wiki
+ * link]]. See http://www.mediawiki.org/wiki/Manual:Article_count
+ *
+ * Retroactively changing this variable will not affect
+ * the existing count (cf. maintenance/recount.sql).
*/
-$wgShowSQLErrors = false;
+$wgUseCommaCount = false;
/**
- * If true, some error messages will be colorized when running scripts on the
- * command line; this can aid picking important things out when debugging.
- * Ignored when running on Windows or when output is redirected to a file.
+ * 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.
*/
-$wgColorErrors = true;
+$wgHitcounterUpdateFreq = 1;
/**
- * If set to true, uncaught exceptions will print a complete stack trace
- * to output. This should only be used for debugging, as it may reveal
- * private information in function parameters due to PHP's backtrace
- * formatting.
+ * How many days user must be idle before he is considered inactive. Will affect
+ * the number shown on Special:Statistics and Special:ActiveUsers special page.
+ * You might want to leave this as the default value, to provide comparable
+ * numbers between different wikis.
*/
-$wgShowExceptionDetails = false;
+$wgActiveUserDays = 30;
+
+/** @} */ # End of statistics }
+
+/************************************************************************//**
+ * @name User accounts, authentication
+ * @{
+ */
+
+/** For compatibility with old installations set to false */
+$wgPasswordSalt = true;
/**
- * If true, show a backtrace for database errors
+ * Specifies the minimal length of a user password. If set to 0, empty pass-
+ * words are allowed.
*/
-$wgShowDBErrorBacktrace = false;
+$wgMinimalPasswordLength = 1;
/**
- * Expose backend server host names through the API and various HTML comments
+ * Enabes or disables JavaScript-based suggestions of password strength
*/
-$wgShowHostnames = false;
+$wgLivePasswordStrengthChecks = false;
/**
- * If set to true MediaWiki will throw notices for some possible error
- * conditions and for deprecated functions.
+ * Maximum number of Unicode characters in signature
*/
-$wgDevelopmentWarnings = false;
+$wgMaxSigChars = 255;
/**
- * Use experimental, DMOZ-like category browser
+ * Maximum number of bytes in username. You want to run the maintenance
+ * script ./maintenance/checkUsernames.php once you have changed this value.
*/
-$wgUseCategoryBrowser = false;
+$wgMaxNameChars = 255;
/**
- * Keep parsed pages in a cache (objectcache table or memcached)
- * to speed up output of the same page viewed by another user with the
- * same options.
+ * Array of usernames which may not be registered or logged in from
+ * Maintenance scripts can still use these
+ */
+$wgReservedUsernames = array(
+ 'MediaWiki default', // Default 'Main Page' and MediaWiki: message pages
+ 'Conversion script', // Used for the old Wikipedia software upgrade
+ 'Maintenance script', // Maintenance scripts which perform editing, image import script
+ 'Template namespace initialisation script', // Used in 1.2->1.3 upgrade
+ 'msg:double-redirect-fixer', // Automatic double redirect fix
+ 'msg:usermessage-editor', // Default user for leaving user messages
+ 'msg:proxyblocker', // For Special:Blockme
+);
+
+/**
+ * Settings added to this array will override the default globals for the user
+ * preferences used by anonymous visitors and newly created accounts.
+ * For instance, to disable section editing links:
+ * $wgDefaultUserOptions ['editsection'] = 0;
*
- * This can provide a significant speedup for medium to large pages,
- * so you probably want to keep it on. Extensions that conflict with the
- * parser cache should disable the cache on a per-page basis instead.
*/
-$wgEnableParserCache = true;
+$wgDefaultUserOptions = array(
+ 'ccmeonemails' => 0,
+ 'cols' => 80,
+ 'contextchars' => 50,
+ 'contextlines' => 5,
+ 'date' => 'default',
+ 'diffonly' => 0,
+ 'disablemail' => 0,
+ 'disablesuggest' => 0,
+ 'editfont' => 'default',
+ 'editondblclick' => 0,
+ 'editsection' => 1,
+ 'editsectiononrightclick' => 0,
+ 'enotifminoredits' => 0,
+ 'enotifrevealaddr' => 0,
+ 'enotifusertalkpages' => 1,
+ 'enotifwatchlistpages' => 0,
+ 'extendwatchlist' => 0,
+ 'externaldiff' => 0,
+ 'externaleditor' => 0,
+ 'fancysig' => 0,
+ 'forceeditsummary' => 0,
+ 'gender' => 'unknown',
+ 'hideminor' => 0,
+ 'hidepatrolled' => 0,
+ 'highlightbroken' => 1,
+ 'imagesize' => 2,
+ 'justify' => 0,
+ 'math' => 1,
+ 'minordefault' => 0,
+ 'newpageshidepatrolled' => 0,
+ 'nocache' => 0,
+ 'noconvertlink' => 0,
+ 'norollbackdiff' => 0,
+ 'numberheadings' => 0,
+ 'previewonfirst' => 0,
+ 'previewontop' => 1,
+ 'quickbar' => 1,
+ 'rcdays' => 7,
+ 'rclimit' => 50,
+ 'rememberpassword' => 0,
+ 'rows' => 25,
+ 'searchlimit' => 20,
+ 'showhiddencats' => 0,
+ 'showjumplinks' => 1,
+ 'shownumberswatching' => 1,
+ 'showtoc' => 1,
+ 'showtoolbar' => 1,
+ 'skin' => false,
+ 'stubthreshold' => 0,
+ 'thumbsize' => 2,
+ 'underline' => 2,
+ 'uselivepreview' => 0,
+ 'usenewrc' => 0,
+ 'watchcreations' => 0,
+ 'watchdefault' => 0,
+ 'watchdeletion' => 0,
+ 'watchlistdays' => 3.0,
+ 'watchlisthideanons' => 0,
+ 'watchlisthidebots' => 0,
+ 'watchlisthideliu' => 0,
+ 'watchlisthideminor' => 0,
+ 'watchlisthideown' => 0,
+ 'watchlisthidepatrolled' => 0,
+ 'watchmoves' => 0,
+ 'wllimit' => 250,
+);
/**
- * Append a configured value to the parser cache and the sitenotice key so
- * that they can be kept separate for some class of activity.
+ * Whether or not to allow and use real name fields.
+ * @deprecated in 1.16, use $wgHiddenPrefs[] = 'realname' below to disable real
+ * names
*/
-$wgRenderHashAppend = '';
+$wgAllowRealName = true;
+
+/** An array of preferences to not show for the user */
+$wgHiddenPrefs = array();
/**
- * If on, the sidebar navigation links are cached for users with the
- * current language set. This can save a touch of load on a busy site
- * by shaving off extra message lookups.
+ * Characters to prevent during new account creations.
+ * This is used in a regular expression character class during
+ * registration (regex metacharacters like / are escaped).
+ */
+$wgInvalidUsernameCharacters = '@';
+
+/**
+ * Character used as a delimiter when testing for interwiki userrights
+ * (In Special:UserRights, it is possible to modify users on different
+ * databases if the delimiter is used, e.g. Someuser@enwiki).
*
- * However it is also fragile: changing the site configuration, or
- * having a variable $wgArticlePath, can produce broken links that
- * don't update as expected.
+ * It is recommended that you have this delimiter in
+ * $wgInvalidUsernameCharacters above, or you will not be able to
+ * modify the user rights of those users via Special:UserRights
*/
-$wgEnableSidebarCache = false;
+$wgUserrightsInterwikiDelimiter = '@';
/**
- * Expiry time for the sidebar cache, in seconds
+ * Use some particular type of external authentication. The specific
+ * authentication module you use will normally require some extra settings to
+ * be specified.
+ *
+ * null indicates no external authentication is to be used. Otherwise,
+ * $wgExternalAuthType must be the name of a non-abstract class that extends
+ * ExternalUser.
+ *
+ * Core authentication modules can be found in includes/extauth/.
*/
-$wgSidebarCacheExpiry = 86400;
+$wgExternalAuthType = null;
/**
- * Under which condition should a page in the main namespace be counted
- * as a valid article? If $wgUseCommaCount is set to true, it will be
- * counted if it contains at least one comma. If it is set to false
- * (default), it will only be counted if it contains at least one [[wiki
- * link]]. See http://www.mediawiki.org/wiki/Manual:Article_count
+ * Configuration for the external authentication. This may include arbitrary
+ * keys that depend on the authentication mechanism. For instance,
+ * authentication against another web app might require that the database login
+ * info be provided. Check the file where your auth mechanism is defined for
+ * info on what to put here.
+ */
+$wgExternalAuthConf = array();
+
+/**
+ * When should we automatically create local accounts when external accounts
+ * already exist, if using ExternalAuth? Can have three values: 'never',
+ * 'login', 'view'. 'view' requires the external database to support cookies,
+ * and implies 'login'.
*
- * Retroactively changing this variable will not affect
- * the existing count (cf. maintenance/recount.sql).
+ * TODO: Implement 'view' (currently behaves like 'login').
*/
-$wgUseCommaCount = false;
+$wgAutocreatePolicy = 'login';
/**
- * 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.
+ * Policies for how each preference is allowed to be changed, in the presence
+ * of external authentication. The keys are preference keys, e.g., 'password'
+ * or 'emailaddress' (see Preferences.php et al.). The value can be one of the
+ * following:
+ *
+ * - local: Allow changes to this pref through the wiki interface but only
+ * apply them locally (default).
+ * - semiglobal: Allow changes through the wiki interface and try to apply them
+ * to the foreign database, but continue on anyway if that fails.
+ * - global: Allow changes through the wiki interface, but only let them go
+ * through if they successfully update the foreign database.
+ * - message: Allow no local changes for linked accounts; replace the change
+ * form with a message provided by the auth plugin, telling the user how to
+ * change the setting externally (maybe providing a link, etc.). If the auth
+ * plugin provides no message for this preference, hide it entirely.
+ *
+ * Accounts that are not linked to an external account are never affected by
+ * this setting. You may want to look at $wgHiddenPrefs instead.
+ * $wgHiddenPrefs supersedes this option.
+ *
+ * TODO: Implement message, global.
*/
-$wgHitcounterUpdateFreq = 1;
+$wgAllowPrefChange = array();
-# Basic user rights and block settings
-$wgSysopUserBans = true; # Allow sysops to ban logged-in users
-$wgSysopRangeBans = true; # Allow sysops to ban IP ranges
-$wgAutoblockExpiry = 86400; # Number of seconds before autoblock entries expire
-$wgBlockAllowsUTEdit = false; # Default setting for option on block form to allow self talkpage editing whilst blocked
-$wgSysopEmailBans = true; # Allow sysops to ban users from accessing Emailuser
+/**
+ * This is to let user authenticate using https when they come from http.
+ * Based on an idea by George Herbert on wikitech-l:
+ * http://lists.wikimedia.org/pipermail/wikitech-l/2010-October/050065.html
+ * @since 1.17
+ */
+$wgSecureLogin = false;
+
+/** @} */ # end user accounts }
+
+/************************************************************************//**
+ * @name User rights, access control and monitoring
+ * @{
+ */
+
+/**
+ * Allow sysops to ban logged-in users
+ * @deprecated since 1.17, will be made permanently true in 1.18
+ */
+$wgSysopUserBans = true;
+
+/**
+ * Allow sysops to ban IP ranges
+ * @deprecated since 1.17; set $wgBlockCIDRLimit to array( 'IPv4' => 32, 'IPv6 => 128 ) instead.
+ */
+$wgSysopRangeBans = true;
+
+/**
+ * Number of seconds before autoblock entries expire. Default 86400 = 1 day.
+ */
+$wgAutoblockExpiry = 86400;
+
+/**
+ * Set this to true to allow blocked users to edit their own user talk page.
+ */
+$wgBlockAllowsUTEdit = false;
+
+/** Allow sysops to ban users from accessing Emailuser */
+$wgSysopEmailBans = true;
+
+/**
+ * Limits on the possible sizes of range blocks.
+ *
+ * CIDR notation is hard to understand, it's easy to mistakenly assume that a
+ * /1 is a small range and a /31 is a large range. Setting this to half the
+ * number of bits avoids such errors.
+ */
$wgBlockCIDRLimit = array(
'IPv4' => 16, # Blocks larger than a /16 (64k addresses) will not be allowed
'IPv6' => 64, # 2^64 = ~1.8x10^19 addresses
@@ -1351,24 +3176,31 @@ $wgBlockCIDRLimit = array(
* logging the user out will again allow reading and editing, just as for
* anonymous visitors.
*/
-$wgBlockDisablesLogin = false; #
-
-# Pages anonymous user may see as an array, e.g.:
-# array ( "Main Page", "Wikipedia:Help");
-# Special:Userlogin and Special:Resetpass are always whitelisted.
-# NOTE: This will only work if $wgGroupPermissions['*']['read']
-# is false -- see below. Otherwise, ALL pages are accessible,
-# regardless of this setting.
-# Also note that this will only protect _pages in the wiki_.
-# Uploaded files will remain readable. Make your upload
-# directory name unguessable, or use .htaccess to protect it.
+$wgBlockDisablesLogin = false;
+
+/**
+ * Pages anonymous user may see as an array, e.g.
+ *
+ * <code>
+ * $wgWhitelistRead = array ( "Main Page", "Wikipedia:Help");
+ * </code>
+ *
+ * Special:Userlogin and Special:Resetpass are always whitelisted.
+ *
+ * NOTE: This will only work if $wgGroupPermissions['*']['read'] is false --
+ * see below. Otherwise, ALL pages are accessible, regardless of this setting.
+ *
+ * Also note that this will only protect _pages in the wiki_. Uploaded files
+ * will remain readable. You can use img_auth.php to protect uploaded files,
+ * see http://www.mediawiki.org/wiki/Manual:Image_Authorization
+ */
$wgWhitelistRead = false;
/**
* Should editors be required to have a validated e-mail
* address before being allowed to edit?
*/
-$wgEmailConfirmToEdit=false;
+$wgEmailConfirmToEdit = false;
/**
* Permission keys given to users in each group.
@@ -1389,6 +3221,7 @@ $wgEmailConfirmToEdit=false;
*/
$wgGroupPermissions = array();
+/** @cond file_level_code */
// Implicit group for all visitors
$wgGroupPermissions['*']['createaccount'] = true;
$wgGroupPermissions['*']['read'] = true;
@@ -1463,8 +3296,9 @@ $wgGroupPermissions['sysop']['markbotedits'] = true;
$wgGroupPermissions['sysop']['apihighlimits'] = true;
$wgGroupPermissions['sysop']['browsearchive'] = true;
$wgGroupPermissions['sysop']['noratelimit'] = true;
-$wgGroupPermissions['sysop']['versiondetail'] = true;
$wgGroupPermissions['sysop']['movefile'] = true;
+$wgGroupPermissions['sysop']['unblockself'] = true;
+$wgGroupPermissions['sysop']['suppressredirect'] = true;
#$wgGroupPermissions['sysop']['mergehistory'] = true;
// Permission to change users' group assignments
@@ -1491,6 +3325,8 @@ $wgGroupPermissions['bureaucrat']['noratelimit'] = true;
*/
# $wgGroupPermissions['developer']['siteadmin'] = true;
+/** @endcond */
+
/**
* Permission keys revoked from users in each group.
* This acts the same way as wgGroupPermissions above, except that
@@ -1525,14 +3361,18 @@ $wgImplicitGroups = array( '*', 'user', 'autoconfirmed' );
*
*/
$wgGroupsAddToSelf = array();
+
+/** @see $wgGroupsAddToSelf */
$wgGroupsRemoveFromSelf = array();
/**
* Set of available actions that can be restricted via action=protect
* You probably shouldn't change this.
* Translated through restriction-* messages.
+ * Title::getRestrictionTypes() will remove restrictions that are not
+ * applicable to a specific title (create and upload)
*/
-$wgRestrictionTypes = array( 'edit', 'move' );
+$wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' );
/**
* Rights which can be required for each protection level (via action=protect)
@@ -1541,8 +3381,8 @@ $wgRestrictionTypes = array( 'edit', 'move' );
* permission by manipulating this array. The ordering of elements
* dictates the order on the protection form's lists.
*
- * '' will be ignored (i.e. unprotected)
- * 'sysop' is quietly rewritten to 'protect' for backwards compatibility
+ * - '' will be ignored (i.e. unprotected)
+ * - 'sysop' is quietly rewritten to 'protect' for backwards compatibility
*/
$wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' );
@@ -1564,24 +3404,32 @@ $wgNamespaceProtection = array();
$wgNonincludableNamespaces = array();
/**
- * Number of seconds an account is required to age before
- * it's given the implicit 'autoconfirm' group membership.
- * This can be used to limit privileges of new accounts.
+ * Number of seconds an account is required to age before it's given the
+ * implicit 'autoconfirm' group membership. This can be used to limit
+ * privileges of new accounts.
*
- * Accounts created by earlier versions of the software
- * may not have a recorded creation date, and will always
- * be considered to pass the age test.
+ * Accounts created by earlier versions of the software may not have a
+ * recorded creation date, and will always be considered to pass the age test.
*
* When left at 0, all registered accounts will pass.
+ *
+ * Example:
+ * <code>
+ * $wgAutoConfirmAge = 600; // ten minutes
+ * $wgAutoConfirmAge = 3600*24; // one day
+ * </code>
*/
$wgAutoConfirmAge = 0;
-//$wgAutoConfirmAge = 600; // ten minutes
-//$wgAutoConfirmAge = 3600*24; // one day
-# Number of edits an account requires before it is autoconfirmed
-# Passing both this AND the time requirement is needed
+/**
+ * Number of edits an account requires before it is autoconfirmed.
+ * Passing both this AND the time requirement is needed. Example:
+ *
+ * <code>
+ * $wgAutoConfirmCount = 50;
+ * </code>
+ */
$wgAutoConfirmCount = 0;
-//$wgAutoConfirmCount = 50;
/**
* Automatically add a usergroup to any user who matches certain conditions.
@@ -1610,9 +3458,10 @@ $wgAutopromote = array(
);
/**
- * These settings can be used to give finer control over who can assign which
- * groups at Special:Userrights. Example configuration:
+ * $wgAddGroups and $wgRemoveGroups can be used to give finer control over who
+ * can assign which groups at Special:Userrights. Example configuration:
*
+ * @code
* // Bureaucrat can add any group
* $wgAddGroups['bureaucrat'] = true;
* // Bureaucrats can only remove bots and sysops
@@ -1621,8 +3470,10 @@ $wgAutopromote = array(
* $wgAddGroups['sysop'] = array( 'bot' );
* // Sysops can disable other sysops in an emergency, and disable bots
* $wgRemoveGroups['sysop'] = array( 'sysop', 'bot' );
+ * @endcode
*/
$wgAddGroups = array();
+/** @see $wgAddGroups */
$wgRemoveGroups = array();
/**
@@ -1637,219 +3488,178 @@ $wgAvailableRights = array();
*/
$wgDeleteRevisionsLimit = 0;
-# Proxy scanner settings
-#
+/** Number of accounts each IP address may create, 0 to disable.
+ * Requires memcached */
+$wgAccountCreationThrottle = 0;
/**
- * If you enable this, every editor's IP address will be scanned for open HTTP
- * proxies.
+ * Edits matching these regular expressions in body text
+ * will be recognised as spam and rejected automatically.
*
- * Don't enable this. Many sysops will report "hostile TCP port scans" to your
- * ISP and ask for your server to be shut down.
+ * There's no administrator override on-wiki, so be careful what you set. :)
+ * May be an array of regexes or a single string for backwards compatibility.
*
- * You have been warned.
+ * See http://en.wikipedia.org/wiki/Regular_expression
+ * Note that each regex needs a beginning/end delimiter, eg: # or /
*/
-$wgBlockOpenProxies = false;
-/** Port we want to scan for a proxy */
-$wgProxyPorts = array( 80, 81, 1080, 3128, 6588, 8000, 8080, 8888, 65506 );
-/** Script used to scan */
-$wgProxyScriptPath = "$IP/includes/proxy_check.php";
-/** */
-$wgProxyMemcExpiry = 86400;
-/** This should always be customised in LocalSettings.php */
-$wgSecretKey = false;
-/** big list of banned IP addresses, in the keys not the values */
-$wgProxyList = array();
-/** deprecated */
-$wgProxyKey = false;
-
-/** Number of accounts each IP address may create, 0 to disable.
- * Requires memcached */
-$wgAccountCreationThrottle = 0;
-
-# Client-side caching:
+$wgSpamRegex = array();
-/** Allow client-side caching of pages */
-$wgCachePages = true;
+/** Same as the above except for edit summaries */
+$wgSummarySpamRegex = array();
/**
- * Set this to current time to invalidate all prior cached pages. Affects both
- * client- and server-side caching.
- * You can get the current date on your server by using the command:
- * date +%Y%m%d%H%M%S
+ * Similarly you can get a function to do the job. The function will be given
+ * the following args:
+ * - a Title object for the article the edit is made on
+ * - the text submitted in the textarea (wpTextbox1)
+ * - the section number.
+ * The return should be boolean indicating whether the edit matched some evilness:
+ * - true : block it
+ * - false : let it through
+ *
+ * @deprecated Use hooks. See SpamBlacklist extension.
*/
-$wgCacheEpoch = '20030516000000';
+$wgFilterCallback = false;
/**
- * Bump this number when changing the global style sheets and JavaScript.
- * It should be appended in the query string of static CSS and JS includes,
- * to ensure that client-side caches do not keep obsolete copies of global
- * styles.
+ * Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies
+ * @since 1.16
*/
-$wgStyleVersion = '270';
-
-
-# Server-side caching:
+$wgEnableDnsBlacklist = false;
/**
- * This will cache static pages for non-logged-in users to reduce
- * database traffic on public sites.
- * Must set $wgShowIPinHeader = false
+ * @deprecated Use $wgEnableDnsBlacklist instead, only kept for backward
+ * compatibility
*/
-$wgUseFileCache = false;
-
-/** Directory where the cached page will be saved */
-$wgFileCacheDirectory = false; ///< defaults to "$wgCacheDirectory/html";
+$wgEnableSorbs = false;
/**
- * When using the file cache, we can store the cached HTML gzipped to save disk
- * space. Pages will then also be served compressed to clients that support it.
- * THIS IS NOT COMPATIBLE with ob_gzhandler which is now enabled if supported in
- * the default LocalSettings.php! If you enable this, remove that setting first.
- *
- * Requires zlib support enabled in PHP.
+ * List of DNS blacklists to use, if $wgEnableDnsBlacklist is true
+ * @since 1.16
*/
-$wgUseGzip = false;
-
-/** Whether MediaWiki should send an ETag header */
-$wgUseETag = false;
-
-# Email notification settings
-#
-
-/** For email notification on page changes */
-$wgPasswordSender = $wgEmergencyContact;
-
-# true: from page editor if s/he opted-in
-# false: Enotif mails appear to come from $wgEmergencyContact
-$wgEnotifFromEditor = false;
-
-// TODO move UPO to preferences probably ?
-# If set to true, users get a corresponding option in their preferences and can choose to enable or disable at their discretion
-# If set to false, the corresponding input form on the user preference page is suppressed
-# It call this to be a "user-preferences-option (UPO)"
-$wgEmailAuthentication = true; # UPO (if this is set to false, texts referring to authentication are suppressed)
-$wgEnotifWatchlist = false; # UPO
-$wgEnotifUserTalk = false; # UPO
-$wgEnotifRevealEditorAddress = false; # UPO; reply-to address may be filled with page editor's address (if user allowed this in the preferences)
-$wgEnotifMinorEdits = true; # UPO; false: "minor edits" on pages do not trigger notification mails.
-# # Attention: _every_ change on a user_talk page trigger a notification mail (if the user is not yet notified)
-
-# Send a generic mail instead of a personalised mail for each user. This
-# always uses UTC as the time zone, and doesn't include the username.
-#
-# For pages with many users watching, this can significantly reduce mail load.
-# Has no effect when using sendmail rather than SMTP;
-
-$wgEnotifImpersonal = false;
-
-# Maximum number of users to mail at once when using impersonal mail. Should
-# match the limit on your mail server.
-$wgEnotifMaxRecips = 500;
-
-# Send mails via the job queue.
-$wgEnotifUseJobQ = false;
-
-# Use real name instead of username in e-mail "from" field
-$wgEnotifUseRealName = false;
+$wgDnsBlacklistUrls = array( 'http.dnsbl.sorbs.net.' );
/**
- * Array of usernames who will be sent a notification email for every change which occurs on a wiki
+ * @deprecated Use $wgDnsBlacklistUrls instead, only kept for backward
+ * compatibility
*/
-$wgUsersNotifiedOnAllChanges = array();
-
-/** Show watching users in recent changes, watchlist and page history views */
-$wgRCShowWatchingUsers = false; # UPO
-/** Show watching users in Page views */
-$wgPageShowWatchingUsers = false;
-/** Show the amount of changed characters in recent changes */
-$wgRCShowChangedSize = true;
+$wgSorbsUrl = array();
/**
- * If the difference between the character counts of the text
- * before and after the edit is below that value, the value will be
- * highlighted on the RC page.
+ * Proxy whitelist, list of addresses that are assumed to be non-proxy despite
+ * what the other methods might say.
*/
-$wgRCChangedSizeThreshold = 500;
+$wgProxyWhitelist = array();
/**
- * Show "Updated (since my last visit)" marker in RC view, watchlist and history
- * view for watched pages with new changes */
-$wgShowUpdatedMarker = true;
+ * Simple rate limiter options to brake edit floods. Maximum number actions
+ * allowed in the given number of seconds; after that the violating client re-
+ * ceives HTTP 500 error pages until the period elapses.
+ *
+ * array( 4, 60 ) for a maximum of 4 hits in 60 seconds.
+ *
+ * This option set is experimental and likely to change. Requires memcached.
+ */
+$wgRateLimits = array(
+ 'edit' => array(
+ 'anon' => null, // for any and all anonymous edits (aggregate)
+ 'user' => null, // for each logged-in user
+ 'newbie' => null, // for each recent (autoconfirmed) account; overrides 'user'
+ 'ip' => null, // for each anon and recent account
+ 'subnet' => null, // ... with final octet removed
+ ),
+ 'move' => array(
+ 'user' => null,
+ 'newbie' => null,
+ 'ip' => null,
+ 'subnet' => null,
+ ),
+ 'mailpassword' => array(
+ 'anon' => null,
+ ),
+ 'emailuser' => array(
+ 'user' => null,
+ ),
+ );
/**
- * Default cookie expiration time. Setting to 0 makes all cookies session-only.
+ * Set to a filename to log rate limiter hits.
*/
-$wgCookieExpiration = 30*86400;
+$wgRateLimitLog = null;
-/** Clock skew or the one-second resolution of time() can occasionally cause cache
- * problems when the user requests two pages within a short period of time. This
- * variable adds a given number of seconds to vulnerable timestamps, thereby giving
- * a grace period.
+/**
+ * Array of groups which should never trigger the rate limiter
+ *
+ * @deprecated as of 1.13.0, the preferred method is using
+ * $wgGroupPermissions[]['noratelimit']. However, this will still
+ * work if desired.
+ *
+ * $wgRateLimitsExcludedGroups = array( 'sysop', 'bureaucrat' );
*/
-$wgClockSkewFudge = 5;
-
-# Squid-related settings
-#
-
-/** Enable/disable Squid */
-$wgUseSquid = false;
-
-/** If you run Squid3 with ESI support, enable this (default:false): */
-$wgUseESI = false;
-
-/** Send X-Vary-Options header for better caching (requires patched Squid) */
-$wgUseXVO = false;
-
-/** Internal server name as known to Squid, if different */
-# $wgInternalServer = 'http://yourinternal.tld:8000';
-$wgInternalServer = $wgServer;
+$wgRateLimitsExcludedGroups = array();
/**
- * Cache timeout for the squid, will be sent as s-maxage (without ESI) or
- * Surrogate-Control (with ESI). Without ESI, you should strip out s-maxage in
- * the Squid config. 18000 seconds = 5 hours, more cache hits with 2678400 = 31
- * days
+ * Array of IPs which should be excluded from rate limits.
+ * This may be useful for whitelisting NAT gateways for conferences, etc.
*/
-$wgSquidMaxage = 18000;
+$wgRateLimitsExcludedIPs = array();
/**
- * Default maximum age for raw CSS/JS accesses
+ * Log IP addresses in the recentchanges table; can be accessed only by
+ * extensions (e.g. CheckUser) or a DB admin
*/
-$wgForcedRawSMaxage = 300;
+$wgPutIPinRC = true;
/**
- * List of proxy servers to purge on changes; default port is 80. Use IP addresses.
- *
- * When MediaWiki is running behind a proxy, it will trust X-Forwarded-For
- * headers sent/modified from these proxies when obtaining the remote IP address
- *
- * For a list of trusted servers which *aren't* purged, see $wgSquidServersNoPurge.
+ * Limit password attempts to X attempts per Y seconds per IP per account.
+ * Requires memcached.
+ */
+$wgPasswordAttemptThrottle = array( 'count' => 5, 'seconds' => 300 );
+
+/** @} */ # end of user rights settings
+
+/************************************************************************//**
+ * @name Proxy scanner settings
+ * @{
*/
-$wgSquidServers = array();
/**
- * As above, except these servers aren't purged on page changes; use to set a
- * list of trusted proxies, etc.
+ * If you enable this, every editor's IP address will be scanned for open HTTP
+ * proxies.
+ *
+ * Don't enable this. Many sysops will report "hostile TCP port scans" to your
+ * ISP and ask for your server to be shut down.
+ *
+ * You have been warned.
*/
-$wgSquidServersNoPurge = array();
+$wgBlockOpenProxies = false;
+/** Port we want to scan for a proxy */
+$wgProxyPorts = array( 80, 81, 1080, 3128, 6588, 8000, 8080, 8888, 65506 );
+/** Script used to scan */
+$wgProxyScriptPath = "$IP/includes/proxy_check.php";
+/** */
+$wgProxyMemcExpiry = 86400;
+/** This should always be customised in LocalSettings.php */
+$wgSecretKey = false;
+/** big list of banned IP addresses, in the keys not the values */
+$wgProxyList = array();
+/** deprecated */
+$wgProxyKey = false;
-/** Maximum number of titles to purge in any one client operation */
-$wgMaxSquidPurgeTitles = 400;
+/** @} */ # end of proxy scanner settings
-/** HTCP multicast purging */
-$wgHTCPPort = 4827;
-$wgHTCPMulticastTTL = 1;
-# $wgHTCPMulticastAddress = "224.0.0.85";
-$wgHTCPMulticastAddress = false;
+/************************************************************************//**
+ * @name Cookie settings
+ * @{
+ */
-/** Should forwarded Private IPs be accepted? */
-$wgUsePrivateIPs = false;
+/**
+ * Default cookie expiration time. Setting to 0 makes all cookies session-only.
+ */
+$wgCookieExpiration = 30*86400;
-# Cookie settings:
-#
/**
- * Set to set an explicit domain on the login cookies eg, "justthis.domain. org"
+ * Set to set an explicit domain on the login cookies eg, "justthis.domain.org"
* or ".any.subdomain.net"
*/
$wgCookieDomain = '';
@@ -1889,65 +3699,12 @@ $wgCacheVaryCookies = array();
/** Override to customise the session name */
$wgSessionName = false;
-/** Whether to allow inline image pointing to other websites */
-$wgAllowExternalImages = false;
-
-/** If the above is false, you can specify an exception here. Image URLs
- * that start with this string are then rendered, while all others are not.
- * You can use this to set up a trusted, simple repository of images.
- * You may also specify an array of strings to allow multiple sites
- *
- * Examples:
- * $wgAllowExternalImagesFrom = 'http://127.0.0.1/';
- * $wgAllowExternalImagesFrom = array( 'http://127.0.0.1/', 'http://example.com' );
- */
-$wgAllowExternalImagesFrom = '';
+/** @} */ # end of cookie settings }
-/** If $wgAllowExternalImages is false, you can allow an on-wiki
- * whitelist of regular expression fragments to match the image URL
- * against. If the image matches one of the regular expression fragments,
- * The image will be displayed.
- *
- * Set this to true to enable the on-wiki whitelist (MediaWiki:External image whitelist)
- * Or false to disable it
+/************************************************************************//**
+ * @name LaTeX (mathematical formulas)
+ * @{
*/
-$wgEnableImageWhitelist = true;
-
-/** Allows to move images and other media files */
-$wgAllowImageMoving = true;
-
-/** Disable database-intensive features */
-$wgMiserMode = false;
-/** Disable all query pages if miser mode is on, not just some */
-$wgDisableQueryPages = false;
-/** Number of rows to cache in 'querycache' table when miser mode is on */
-$wgQueryCacheLimit = 1000;
-/** Number of links to a page required before it is deemed "wanted" */
-$wgWantedPagesThreshold = 1;
-/** Enable slow parser functions */
-$wgAllowSlowParserFunctions = false;
-
-/**
- * Maps jobs to their handling classes; extensions
- * can add to this to provide custom jobs
- */
-$wgJobClasses = array(
- 'refreshLinks' => 'RefreshLinksJob',
- 'refreshLinks2' => 'RefreshLinksJob2',
- 'htmlCacheUpdate' => 'HTMLCacheUpdateJob',
- 'html_cache_update' => 'HTMLCacheUpdateJob', // backwards-compatible
- 'sendMail' => 'EmaillingJob',
- 'enotifNotify' => 'EnotifNotifyJob',
- 'fixDoubleRedirect' => 'DoubleRedirectJob',
-);
-
-/**
- * Additional functions to be performed with updateSpecialPages.
- * Expensive Querypages are already updated.
- */
-$wgSpecialPageCacheUpdates = array(
- 'Statistics' => array('SiteStatsUpdate','cacheUpdate')
-);
/**
* To use inline TeX, you need to compile 'texvc' (in the 'math' subdirectory of
@@ -1957,7 +3714,7 @@ $wgSpecialPageCacheUpdates = array(
*/
$wgUseTeX = false;
/** Location of the texvc binary */
-$wgTexvc = './math/texvc';
+$wgTexvc = $IP . '/math/texvc';
/**
* Texvc background color
* use LaTeX color format as used in \special function
@@ -1979,31 +3736,157 @@ $wgTexvcBackgroundColor = 'transparent';
*/
$wgMathCheckFiles = true;
-#
-# Profiling / debugging
-#
-# You have to create a 'profiling' table in your database before using
-# profiling see maintenance/archives/patch-profiling.sql .
-#
-# To enable profiling, edit StartProfiler.php
+/* @} */ # end LaTeX }
+
+/************************************************************************//**
+ * @name Profiling, testing and debugging
+ *
+ * To enable profiling, edit StartProfiler.php
+ *
+ * @{
+ */
+
+/**
+ * Filename for debug logging. See http://www.mediawiki.org/wiki/How_to_debug
+ * The debug log file should be not be publicly accessible if it is used, as it
+ * may contain private data.
+ */
+$wgDebugLogFile = '';
+
+/**
+ * Prefix for debug log lines
+ */
+$wgDebugLogPrefix = '';
+
+/**
+ * If true, instead of redirecting, show a page with a link to the redirect
+ * destination. This allows for the inspection of PHP error messages, and easy
+ * resubmission of form data. For developer use only.
+ */
+$wgDebugRedirects = false;
+
+/**
+ * If true, log debugging data from action=raw.
+ * This is normally false to avoid overlapping debug entries due to gen=css and
+ * gen=js requests.
+ */
+$wgDebugRawPage = false;
+
+/**
+ * Send debug data to an HTML comment in the output.
+ *
+ * This may occasionally be useful when supporting a non-technical end-user. It's
+ * more secure than exposing the debug log file to the web, since the output only
+ * contains private data for the current user. But it's not ideal for development
+ * use since data is lost on fatal errors and redirects.
+ */
+$wgDebugComments = false;
+
+/**
+ * Write SQL queries to the debug log
+ */
+$wgDebugDumpSql = false;
+
+/**
+ * Set to an array of log group keys to filenames.
+ * If set, wfDebugLog() output for that group will go to that file instead
+ * of the regular $wgDebugLogFile. Useful for enabling selective logging
+ * in production.
+ */
+$wgDebugLogGroups = array();
+
+/**
+ * Display debug data at the bottom of the main content area.
+ *
+ * Useful for developers and technical users trying to working on a closed wiki.
+ */
+$wgShowDebug = false;
+
+/**
+ * Prefix debug messages with relative timestamp. Very-poor man's profiler.
+ */
+$wgDebugTimestamps = false;
+
+/**
+ * Print HTTP headers for every request in the debug information.
+ */
+$wgDebugPrintHttpHeaders = true;
+
+/**
+ * Show the contents of $wgHooks in Special:Version
+ */
+$wgSpecialVersionShowHooks = false;
+
+/**
+ * Whether to show "we're sorry, but there has been a database error" pages.
+ * Displaying errors aids in debugging, but may display information useful
+ * to an attacker.
+ */
+$wgShowSQLErrors = false;
+
+/**
+ * If set to true, uncaught exceptions will print a complete stack trace
+ * to output. This should only be used for debugging, as it may reveal
+ * private information in function parameters due to PHP's backtrace
+ * formatting.
+ */
+$wgShowExceptionDetails = false;
+
+/**
+ * If true, show a backtrace for database errors
+ */
+$wgShowDBErrorBacktrace = false;
+
+/**
+ * Expose backend server host names through the API and various HTML comments
+ */
+$wgShowHostnames = false;
+
+/**
+ * If set to true MediaWiki will throw notices for some possible error
+ * conditions and for deprecated functions.
+ */
+$wgDevelopmentWarnings = false;
/** Only record profiling info for pages that took longer than this */
$wgProfileLimit = 0.0;
+
/** Don't put non-profiling info into log file */
$wgProfileOnly = false;
-/** Log sums from profiling into "profiling" table in db. */
+
+/**
+ * Log sums from profiling into "profiling" table in db.
+ *
+ * You have to create a 'profiling' table in your database before using
+ * this feature, see maintenance/archives/patch-profiling.sql
+ *
+ * To enable profiling, edit StartProfiler.php
+ */
$wgProfileToDatabase = false;
+
/** If true, print a raw call tree instead of per-function report */
$wgProfileCallTree = false;
+
/** Should application server host be put into profiling table */
$wgProfilePerHost = false;
-/** Settings for UDP profiler */
+/**
+ * Host for UDP profiler.
+ *
+ * The host should be running a daemon which can be obtained from MediaWiki
+ * Subversion at: http://svn.wikimedia.org/svnroot/mediawiki/trunk/udpprofile
+ */
$wgUDPProfilerHost = '127.0.0.1';
+
+/**
+ * Port for UDP profiler.
+ * @see $wgUDPProfilerHost
+ */
$wgUDPProfilerPort = '3811';
/** Detects non-matching wfProfileIn/wfProfileOut calls */
$wgDebugProfiling = false;
+
/** Output debug message on every wfProfileIn/wfProfileOut */
$wgDebugFunctionEntry = 0;
@@ -2020,9 +3903,52 @@ $wgStatsMethod = 'cache';
*/
$wgDisableCounters = false;
-$wgDisableTextSearch = false;
-$wgDisableSearchContext = false;
+/**
+ * Support blog-style "trackbacks" for articles. See
+ * http://www.sixapart.com/pronet/docs/trackback_spec for details.
+ */
+$wgUseTrackbacks = false;
+
+/**
+ * Parser test suite files to be run by parserTests.php when no specific
+ * filename is passed to it.
+ *
+ * Extensions may add their own tests to this array, or site-local tests
+ * may be added via LocalSettings.php
+ *
+ * Use full paths.
+ */
+$wgParserTestFiles = array(
+ "$IP/maintenance/tests/parser/parserTests.txt",
+ "$IP/maintenance/tests/parser/ExtraParserTests.txt"
+);
+
+/**
+ * If configured, specifies target CodeReview installation to send test
+ * result data from 'parserTests.php --upload'
+ *
+ * Something like this:
+ * $wgParserTestRemote = array(
+ * 'api-url' => 'http://www.mediawiki.org/w/api.php',
+ * 'repo' => 'MediaWiki',
+ * 'suite' => 'ParserTests',
+ * 'path' => '/trunk/phase3', // not used client-side; for reference
+ * 'secret' => 'qmoicj3mc4mcklmqw', // Shared secret used in HMAC validation
+ * );
+ */
+$wgParserTestRemote = false;
+
+/** @} */ # end of profiling, testing and debugging }
+/************************************************************************//**
+ * @name Search
+ * @{
+ */
+
+/**
+ * Set this to true to disable the full text search feature.
+ */
+$wgDisableTextSearch = false;
/**
* Set to true to have nicer highligted text in search results,
@@ -2033,6 +3959,8 @@ $wgAdvancedSearchHighlighting = false;
/**
* Regexp to match word boundaries, defaults for non-CJK languages
* should be empty for CJK since the words are not separate
+ *
+ * @todo FIXME: checks for lower than required PHP version (5.1.x).
*/
$wgSearchHighlightBoundaries = version_compare("5.1", PHP_VERSION, "<")? '[\p{Z}\p{P}\p{C}]'
: '[ ,.;:!?~!@#$%\^&*\(\)+=\-\\|\[\]"\'<>\n\r\/{}]'; // PHP 5.0 workaround
@@ -2092,149 +4020,15 @@ $wgMWSuggestTemplate = false;
* table. If you ever re-enable, be sure to rebuild the search table.
*/
$wgDisableSearchUpdate = false;
-/** Uploads have to be specially set up to be secure */
-$wgEnableUploads = false;
-/**
- * Show EXIF data, on by default if available.
- * Requires PHP's EXIF extension: http://www.php.net/manual/en/ref.exif.php
- *
- * NOTE FOR WINDOWS USERS:
- * To enable EXIF functions, add the folloing lines to the
- * "Windows extensions" section of php.ini:
- *
- * extension=extensions/php_mbstring.dll
- * extension=extensions/php_exif.dll
- */
-$wgShowEXIF = function_exists( 'exif_read_data' );
-
-/**
- * Set to true to enable the upload _link_ while local uploads are disabled.
- * Assumes that the special page link will be bounced to another server where
- * uploads do work.
- */
-$wgRemoteUploads = false;
-
-/**
- * Disable links to talk pages of anonymous users (IPs) in listings on special
- * pages like page history, Special:Recentchanges, etc.
- */
-$wgDisableAnonTalk = false;
-/**
- * Do DELETE/INSERT for link updates instead of incremental
- */
-$wgUseDumbLinkUpdate = false;
-
-/**
- * Anti-lock flags - bitfield
- * ALF_PRELOAD_LINKS
- * Preload links during link update for save
- * ALF_PRELOAD_EXISTENCE
- * Preload cur_id during replaceLinkHolders
- * 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
- * ALF_NO_BLOCK_LOCK
- * As for ALF_LINK_LOCK, this flag is a necessity for high-traffic
- * wikis.
- */
-$wgAntiLockFlags = 0;
-
-/**
- * Path to the GNU diff3 utility. If the file doesn't exist, edit conflicts will
- * fall back to the old behaviour (no merging).
- */
-$wgDiff3 = '/usr/bin/diff3';
/**
- * Path to the GNU diff utility.
- */
-$wgDiff = '/usr/bin/diff';
-
-/**
- * We can also compress text stored in the 'text' table. If this is set on, new
- * revisions will be compressed on page save if zlib support is available. Any
- * compressed revisions will be decompressed on load regardless of this setting
- * *but will not be readable at all* if zlib support is not available.
- */
-$wgCompressRevisions = false;
-
-/**
- * This is the list of preferred extensions for uploading files. Uploading files
- * with extensions not in this list will trigger a warning.
- */
-$wgFileExtensions = array( 'png', 'gif', 'jpg', 'jpeg' );
-
-/** Files with these extensions will never be allowed as uploads. */
-$wgFileBlacklist = array(
- # HTML may contain cookie-stealing JavaScript and web bugs
- 'html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht',
- # PHP scripts may execute arbitrary code on the server
- 'php', 'phtml', 'php3', 'php4', 'php5', 'phps',
- # Other types that may be interpreted by some servers
- 'shtml', 'jhtml', 'pl', 'py', 'cgi',
- # May contain harmful executables for Windows victims
- 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl' );
-
-/** Files with these mime types will never be allowed as uploads
- * if $wgVerifyMimeType is enabled.
- */
-$wgMimeTypeBlacklist= array(
- # HTML may contain cookie-stealing JavaScript and web bugs
- 'text/html', 'text/javascript', 'text/x-javascript', 'application/x-shellscript',
- # PHP scripts may execute arbitrary code on the server
- 'application/x-php', 'text/x-php',
- # Other types that may be interpreted by some servers
- 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh',
- # Client-side hazards on Internet Explorer
- 'text/scriptlet', 'application/x-msdownload',
- # Windows metafile, client-side vulnerability on some systems
- 'application/x-msmetafile',
- # A ZIP file may be a valid Java archive containing an applet which exploits the
- # same-origin policy to steal cookies
- 'application/zip',
-);
-
-/** This is a flag to determine whether or not to check file extensions on upload. */
-$wgCheckFileExtensions = true;
-
-/**
- * If this is turned off, users may override the warning for files not covered
- * by $wgFileExtensions.
- */
-$wgStrictFileExtensions = true;
-
-/** Warn if uploaded files are larger than this (in bytes), or false to disable*/
-$wgUploadSizeWarning = false;
-
-/** For compatibility with old installations set to false */
-$wgPasswordSalt = true;
-
-/** Which namespaces should support subpages?
- * See Language.php for a list of namespaces.
- */
-$wgNamespacesWithSubpages = array(
- NS_TALK => true,
- NS_USER => true,
- NS_USER_TALK => true,
- NS_PROJECT_TALK => true,
- NS_FILE_TALK => true,
- NS_MEDIAWIKI => true,
- NS_MEDIAWIKI_TALK => true,
- NS_TEMPLATE_TALK => true,
- NS_HELP_TALK => true,
- NS_CATEGORY_TALK => true
-);
-
-/**
- * Which namespaces have special treatment where they should be preview-on-open
- * Internaly only Category: pages apply, but using this extensions (e.g. Semantic MediaWiki)
- * can specify namespaces of pages they have special treatment for
+ * List of namespaces which are searched by default. Example:
+ *
+ * <code>
+ * $wgNamespacesToBeSearchedDefault[NS_MAIN] = true;
+ * $wgNamespacesToBeSearchedDefault[NS_PROJECT] = true;
+ * </code>
*/
-$wgPreviewOnOpenNamespaces = array(
- NS_CATEGORY => true
-);
-
$wgNamespacesToBeSearchedDefault = array(
NS_MAIN => true,
);
@@ -2258,158 +4052,104 @@ $wgNamespacesToBeSearchedHelp = array(
$wgSearchEverythingOnlyLoggedIn = false;
/**
- * Site notice shown at the top of each page
- *
- * MediaWiki:Sitenotice page, which will override this. You can also
- * provide a separate message for logged-out users using the
- * MediaWiki:Anonnotice page.
- */
-$wgSiteNotice = '';
-
-#
-# Images settings
-#
-
-/**
- * Plugins for media file type handling.
- * Each entry in the array maps a MIME type to a class name
+ * Disable the internal MySQL-based search, to allow it to be
+ * implemented by an extension instead.
*/
-$wgMediaHandlers = array(
- 'image/jpeg' => 'BitmapHandler',
- 'image/png' => 'BitmapHandler',
- 'image/gif' => 'GIFHandler',
- 'image/tiff' => 'TiffHandler',
- 'image/x-ms-bmp' => 'BmpHandler',
- 'image/x-bmp' => 'BmpHandler',
- 'image/svg+xml' => 'SvgHandler', // official
- 'image/svg' => 'SvgHandler', // compat
- 'image/vnd.djvu' => 'DjVuHandler', // official
- 'image/x.djvu' => 'DjVuHandler', // compat
- 'image/x-djvu' => 'DjVuHandler', // compat
-);
-
+$wgDisableInternalSearch = false;
/**
- * Resizing can be done using PHP's internal image libraries or using
- * ImageMagick or another third-party converter, e.g. GraphicMagick.
- * These support more file formats than PHP, which only supports PNG,
- * GIF, JPG, XBM and WBMP.
+ * Set this to a URL to forward search requests to some external location.
+ * If the URL includes '$1', this will be replaced with the URL-encoded
+ * search term.
*
- * Use Image Magick instead of PHP builtin functions.
+ * For example, to forward to Google you'd have something like:
+ * $wgSearchForwardUrl = 'http://www.google.com/search?q=$1' .
+ * '&domains=http://example.com' .
+ * '&sitesearch=http://example.com' .
+ * '&ie=utf-8&oe=utf-8';
*/
-$wgUseImageMagick = false;
-/** The convert command shipped with ImageMagick */
-$wgImageMagickConvertCommand = '/usr/bin/convert';
-
-/** Sharpening parameter to ImageMagick */
-$wgSharpenParameter = '0x0.4';
-
-/** Reduction in linear dimensions below which sharpening will be enabled */
-$wgSharpenReductionThreshold = 0.85;
+$wgSearchForwardUrl = null;
/**
- * Temporary directory used for ImageMagick. The directory must exist. Leave
- * this set to false to let ImageMagick decide for itself.
+ * Search form behavior
+ * true = use Go & Search buttons
+ * false = use Go button & Advanced search link
*/
-$wgImageMagickTempDir = false;
+$wgUseTwoButtonsSearchForm = true;
/**
- * Use another resizing converter, e.g. GraphicMagick
- * %s will be replaced with the source path, %d with the destination
- * %w and %h will be replaced with the width and height
- *
- * An example is provided for GraphicMagick
- * Leave as false to skip this
+ * Array of namespaces to generate a Google sitemap for when the
+ * maintenance/generateSitemap.php script is run, or false if one is to be ge-
+ * nerated for all namespaces.
*/
-#$wgCustomConvertCommand = "gm convert %s -resize %wx%h %d"
-$wgCustomConvertCommand = false;
+$wgSitemapNamespaces = false;
-# Scalable Vector Graphics (SVG) may be uploaded as images.
-# Since SVG support is not yet standard in browsers, it is
-# necessary to rasterize SVGs to PNG as a fallback format.
-#
-# An external program is required to perform this conversion:
-$wgSVGConverters = array(
- 'ImageMagick' => '$path/convert -background white -thumbnail $widthx$height\! $input PNG:$output',
- 'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output',
- 'inkscape' => '$path/inkscape -z -w $width -f $input -e $output',
- 'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input',
- 'rsvg' => '$path/rsvg -w$width -h$height $input $output',
- 'imgserv' => '$path/imgserv-wrapper -i svg -o png -w$width $input $output',
- );
-/** Pick one of the above */
-$wgSVGConverter = 'ImageMagick';
-/** If not in the executable PATH, specify */
-$wgSVGConverterPath = '';
-/** Don't scale a SVG larger than this */
-$wgSVGMaxSize = 2048;
-/**
- * Don't thumbnail an image if it will use too much working memory
- * Default is 50 MB if decompressed to RGBA form, which corresponds to
- * 12.5 million pixels or 3500x3500
+/** @} */ # end of search settings
+
+/************************************************************************//**
+ * @name Edit user interface
+ * @{
*/
-$wgMaxImageArea = 1.25e7;
+
/**
- * Force thumbnailing of animated GIFs above this size to a single
- * frame instead of an animated thumbnail. ImageMagick seems to
- * get real unhappy and doesn't play well with resource limits. :P
- * Defaulting to 1 megapixel (1000x1000)
+ * Path to the GNU diff3 utility. If the file doesn't exist, edit conflicts will
+ * fall back to the old behaviour (no merging).
*/
-$wgMaxAnimatedGifArea = 1.0e6;
+$wgDiff3 = '/usr/bin/diff3';
+
/**
- * Browsers don't support TIFF inline generally...
- * For inline display, we need to convert to PNG or JPEG.
- * Note scaling should work with ImageMagick, but may not with GD scaling.
- * // PNG is lossless, but inefficient for photos
- * $wgTiffThumbnailType = array( 'png', 'image/png' );
- * // JPEG is good for photos, but has no transparency support. Bad for diagrams.
- * $wgTiffThumbnailType = array( 'jpg', 'image/jpeg' );
+ * Path to the GNU diff utility.
*/
- $wgTiffThumbnailType = false;
+$wgDiff = '/usr/bin/diff';
/**
- * If rendered thumbnail files are older than this timestamp, they
- * will be rerendered on demand as if the file didn't already exist.
- * Update if there is some need to force thumbs and SVG rasterizations
- * to rerender, such as fixes to rendering bugs.
+ * Which namespaces have special treatment where they should be preview-on-open
+ * Internaly only Category: pages apply, but using this extensions (e.g. Semantic MediaWiki)
+ * can specify namespaces of pages they have special treatment for
*/
-$wgThumbnailEpoch = '20030516000000';
+$wgPreviewOnOpenNamespaces = array(
+ NS_CATEGORY => true
+);
/**
- * If set, inline scaled images will still produce <img> tags ready for
- * output instead of showing an error message.
- *
- * This may be useful if errors are transitory, especially if the site
- * is configured to automatically render thumbnails on request.
- *
- * On the other hand, it may obscure error conditions from debugging.
- * Enable the debug log or the 'thumbnail' log group to make sure errors
- * are logged to a file for review.
+ * Activate external editor interface for files and pages
+ * See http://www.mediawiki.org/wiki/Manual:External_editors
*/
-$wgIgnoreImageErrors = false;
+$wgUseExternalEditor = true;
+
+/** Go button goes straight to the edit screen if the article doesn't exist. */
+$wgGoToEdit = false;
/**
- * Allow thumbnail rendering on page view. If this is false, a valid
- * thumbnail URL is still output, but no file will be created at
- * the target location. This may save some time if you have a
- * thumb.php or 404 handler set up which is faster than the regular
- * webserver(s).
+ * Enable the UniversalEditButton for browsers that support it
+ * (currently only Firefox with an extension)
+ * See http://universaleditbutton.org for more background information
*/
-$wgGenerateThumbnailOnParse = true;
+$wgUniversalEditButton = true;
/**
-* Show thumbnails for old images on the image description page
-*/
-$wgShowArchiveThumbnails = true;
+ * If user doesn't specify any edit summary when making a an edit, MediaWiki
+ * will try to automatically create one. This feature can be disabled by set-
+ * ting this variable false.
+ */
+$wgUseAutomaticEditSummaries = true;
-/** Obsolete, always true, kept for compatibility with extensions */
-$wgUseImageResize = true;
+/** @} */ # end edit UI }
+/************************************************************************//**
+ * @name Maintenance
+ * See also $wgSiteNotice
+ * @{
+ */
-/** Set $wgCommandLineMode if it's not set already, to avoid notices */
+/**
+ * @cond file_level_code
+ * Set $wgCommandLineMode if it's not set already, to avoid notices
+ */
if( !isset( $wgCommandLineMode ) ) {
$wgCommandLineMode = false;
}
+/** @endcond */
/** For colorized maintenance script output, is your terminal background dark ? */
$wgCommandLineDarkBg = false;
@@ -2421,12 +4161,42 @@ $wgCommandLineDarkBg = false;
*/
$wgMaintenanceScripts = array();
-#
-# Recent changes settings
-#
+/**
+ * Set this to a string to put the wiki into read-only mode. The text will be
+ * used as an explanation to users.
+ *
+ * This prevents most write operations via the web interface. Cache updates may
+ * still be possible. To prevent database writes completely, use the read_only
+ * option in MySQL.
+ */
+$wgReadOnly = null;
-/** Log IP addresses in the recentchanges table; can be accessed only by extensions (e.g. CheckUser) or a DB admin */
-$wgPutIPinRC = true;
+/**
+ * If this lock file exists (size > 0), the wiki will be forced into read-only mode.
+ * Its contents will be shown to users as part of the read-only warning
+ * message.
+ *
+ * Defaults to "{$wgUploadDirectory}/lock_yBgMBwiR".
+ */
+$wgReadOnlyFile = false;
+
+/**
+ * When you run the web-based upgrade utility, it will tell you what to set
+ * this to in order to authorize the upgrade process. It will subsequently be
+ * used as a password, to authorize further upgrades.
+ *
+ * For security, do not set this to a guessable string. Use the value supplied
+ * by the install/upgrade process. To cause the upgrader to generate a new key,
+ * delete the old key from LocalSettings.php.
+ */
+$wgUpgradeKey = false;
+
+/** @} */ # End of maintenance }
+
+/************************************************************************//**
+ * @name Recent changes, new pages, watchlist and history
+ * @{
+ */
/**
* Recentchanges items are periodically purged; entries older than this many
@@ -2436,14 +4206,17 @@ $wgPutIPinRC = true;
$wgRCMaxAge = 13 * 7 * 24 * 3600;
/**
- * Filter $wgRCLinkDays by $wgRCMaxAge to avoid showing links for numbers higher than what will be stored.
- * Note that this is disabled by default because we sometimes do have RC data which is beyond the limit
- * for some reason, and some users may use the high numbers to display that data which is still there.
+ * Filter $wgRCLinkDays by $wgRCMaxAge to avoid showing links for numbers
+ * higher than what will be stored. Note that this is disabled by default
+ * because we sometimes do have RC data which is beyond the limit for some
+ * reason, and some users may use the high numbers to display that data which
+ * is still there.
*/
$wgRCFilterByAge = false;
/**
- * List of Days and Limits options to list in the Special:Recentchanges and Special:Recentchangeslinked pages.
+ * List of Days and Limits options to list in the Special:Recentchanges and
+ * Special:Recentchangeslinked pages.
*/
$wgRCLinkLimits = array( 50, 100, 250, 500 );
$wgRCLinkDays = array( 1, 3, 7, 14, 30 );
@@ -2486,9 +4259,88 @@ $wgRC2UDPOmitBots = false;
*/
$wgEnableNewpagesUserFilter = true;
-#
-# Copyright and credits settings
-#
+/** Use RC Patrolling to check for vandalism */
+$wgUseRCPatrol = true;
+
+/** Use new page patrolling to check new pages on Special:Newpages */
+$wgUseNPPatrol = true;
+
+/** Provide syndication feeds (RSS, Atom) for, e.g., Recentchanges, Newpages */
+$wgFeed = true;
+
+/** Set maximum number of results to return in syndication feeds (RSS, Atom) for
+ * eg Recentchanges, Newpages. */
+$wgFeedLimit = 50;
+
+/** _Minimum_ timeout for cached Recentchanges feed, in seconds.
+ * A cached version will continue to be served out even if changes
+ * are made, until this many seconds runs out since the last render.
+ *
+ * If set to 0, feed caching is disabled. Use this for debugging only;
+ * feed generation can be pretty slow with diffs.
+ */
+$wgFeedCacheTimeout = 60;
+
+/** When generating Recentchanges RSS/Atom feed, diffs will not be generated for
+ * pages larger than this size. */
+$wgFeedDiffCutoff = 32768;
+
+/** Override the site's default RSS/ATOM feed for recentchanges that appears on
+ * every page. Some sites might have a different feed they'd like to promote
+ * instead of the RC feed (maybe like a "Recent New Articles" or "Breaking news" one).
+ * Ex: $wgSiteFeed['format'] = "http://example.com/somefeed.xml"; Format can be one
+ * of either 'rss' or 'atom'.
+ */
+$wgOverrideSiteFeed = array();
+
+/**
+ * Which feed types should we provide by default? This can include 'rss',
+ * 'atom', neither, or both.
+ */
+$wgAdvertisedFeedTypes = array( 'atom' );
+
+/** Show watching users in recent changes, watchlist and page history views */
+$wgRCShowWatchingUsers = false; # UPO
+/** Show watching users in Page views */
+$wgPageShowWatchingUsers = false;
+/** Show the amount of changed characters in recent changes */
+$wgRCShowChangedSize = true;
+
+/**
+ * If the difference between the character counts of the text
+ * before and after the edit is below that value, the value will be
+ * highlighted on the RC page.
+ */
+$wgRCChangedSizeThreshold = 500;
+
+/**
+ * Show "Updated (since my last visit)" marker in RC view, watchlist and history
+ * view for watched pages with new changes */
+$wgShowUpdatedMarker = true;
+
+/**
+ * Disable links to talk pages of anonymous users (IPs) in listings on special
+ * pages like page history, Special:Recentchanges, etc.
+ */
+$wgDisableAnonTalk = false;
+
+/**
+ * Enable filtering of categories in Recentchanges
+ */
+$wgAllowCategorizedRecentChanges = false;
+
+/**
+ * Allow filtering by change tag in recentchanges, history, etc
+ * Has no effect if no tags are defined in valid_tag.
+ */
+$wgUseTagFilter = true;
+
+/** @} */ # end RC/watchlist }
+
+/************************************************************************//**
+ * @name Copyright and credits settings
+ * @{
+ */
/** RDF metadata toggles */
$wgEnableDublinCoreRdf = false;
@@ -2507,7 +4359,10 @@ $wgRightsIcon = null;
*/
$wgLicenseTerms = false;
-/** Set this to some HTML to override the rights icon with an arbitrary logo */
+/**
+ * Set this to some HTML to override the rights icon with an arbitrary logo
+ * @deprecated Use $wgFooterIcons['copyright']['copyright']
+ */
$wgCopyrightIcon = null;
/** Set this to true if you want detailed copyright information forms on Upload. */
@@ -2530,27 +4385,12 @@ $wgMaxCredits = 0;
* Otherwise, link to a separate credits page. */
$wgShowCreditsIfMax = true;
+/** @} */ # end of copyright and credits settings }
-
-/**
- * Set this to false to avoid forcing the first letter of links to capitals.
- * WARNING: may break links! This makes links COMPLETELY case-sensitive. Links
- * appearing with a capital at the beginning of a sentence will *not* go to the
- * same place as links in the middle of a sentence using a lowercase initial.
- */
-$wgCapitalLinks = true;
-
-/**
- * @since 1.16 - This can now be set per-namespace. Some special namespaces (such
- * as Special, see MWNamespace::$alwaysCapitalizedNamespaces for the full list) must be
- * true by default (and setting them has no effect), due to various things that
- * require them to be so. Also, since Talk namespaces need to directly mirror their
- * associated content namespaces, the values for those are ignored in favor of the
- * subject namespace's setting. Setting for NS_MEDIA is taken automatically from
- * NS_FILE.
- * EX: $wgCapitalLinkOverrides[ NS_FILE ] = false;
+/************************************************************************//**
+ * @name Import / Export
+ * @{
*/
-$wgCapitalLinkOverrides = array();
/**
* List of interwiki prefixes for wikis we'll accept as sources for
@@ -2608,199 +4448,11 @@ $wgExportMaxLinkDepth = 0;
*/
$wgExportFromNamespaces = false;
-/**
- * Edits matching these regular expressions in body text
- * will be recognised as spam and rejected automatically.
- *
- * There's no administrator override on-wiki, so be careful what you set. :)
- * May be an array of regexes or a single string for backwards compatibility.
- *
- * See http://en.wikipedia.org/wiki/Regular_expression
- * Note that each regex needs a beginning/end delimiter, eg: # or /
- */
-$wgSpamRegex = array();
-
-/** Same as the above except for edit summaries */
-$wgSummarySpamRegex = array();
-
-/** Similarly you can get a function to do the job. The function will be given
- * the following args:
- * - a Title object for the article the edit is made on
- * - the text submitted in the textarea (wpTextbox1)
- * - the section number.
- * The return should be boolean indicating whether the edit matched some evilness:
- * - true : block it
- * - false : let it through
- *
- * For a complete example, have a look at the SpamBlacklist extension.
- */
-$wgFilterCallback = false;
-
-/** Go button goes straight to the edit screen if the article doesn't exist. */
-$wgGoToEdit = false;
-
-/** Allow raw, unchecked HTML in <html>...</html> sections.
- * THIS IS VERY DANGEROUS on a publically editable site, so USE wgGroupPermissions
- * TO RESTRICT EDITING to only those that you trust
- */
-$wgRawHtml = false;
-
-/**
- * $wgUseTidy: use tidy to make sure HTML output is sane.
- * Tidy is a free tool that fixes broken HTML.
- * See http://www.w3.org/People/Raggett/tidy/
- * $wgTidyBin should be set to the path of the binary and
- * $wgTidyConf to the path of the configuration file.
- * $wgTidyOpts can include any number of parameters.
- *
- * $wgTidyInternal controls the use of the PECL extension to use an in-
- * process tidy library instead of spawning a separate program.
- * Normally you shouldn't need to override the setting except for
- * debugging. To install, use 'pear install tidy' and add a line
- * 'extension=tidy.so' to php.ini.
- */
-$wgUseTidy = false;
-$wgAlwaysUseTidy = false;
-$wgTidyBin = 'tidy';
-$wgTidyConf = $IP.'/includes/tidy.conf';
-$wgTidyOpts = '';
-$wgTidyInternal = extension_loaded( 'tidy' );
-
-/**
- * Put tidy warnings in HTML comments
- * Only works for internal tidy.
- */
-$wgDebugTidy = false;
-
-/**
- * Validate the overall output using tidy and refuse
- * to display the page if it's not valid.
- */
-$wgValidateAllHtml = false;
-
-/** See list of skins and their symbolic names in languages/Language.php */
-$wgDefaultSkin = 'monobook';
-
-/**
-* Should we allow the user's to select their own skin that will override the default?
-* @deprecated in 1.16, use $wgHiddenPrefs[] = 'skin' to disable it
-*/
-$wgAllowUserSkin = true;
-
-/**
- * Optionally, we can specify a stylesheet to use for media="handheld".
- * This is recognized by some, but not all, handheld/mobile/PDA browsers.
- * If left empty, compliant handheld browsers won't pick up the skin
- * stylesheet, which is specified for 'screen' media.
- *
- * Can be a complete URL, base-relative path, or $wgStylePath-relative path.
- * Try 'chick/main.css' to apply the Chick styles to the MonoBook HTML.
- *
- * Will also be switched in when 'handheld=yes' is added to the URL, like
- * the 'printable=yes' mode for print media.
- */
-$wgHandheldStyle = false;
-
-/**
- * If set, 'screen' and 'handheld' media specifiers for stylesheets are
- * transformed such that they apply to the iPhone/iPod Touch Mobile Safari,
- * which doesn't recognize 'handheld' but does support media queries on its
- * screen size.
- *
- * Consider only using this if you have a *really good* handheld stylesheet,
- * as iPhone users won't have any way to disable it and use the "grown-up"
- * styles instead.
- */
-$wgHandheldForIPhone = false;
-
-/**
- * Settings added to this array will override the default globals for the user
- * preferences used by anonymous visitors and newly created accounts.
- * For instance, to disable section editing links:
- * $wgDefaultUserOptions ['editsection'] = 0;
- *
- */
-$wgDefaultUserOptions = array(
- 'quickbar' => 1,
- 'underline' => 2,
- 'cols' => 80,
- 'rows' => 25,
- 'searchlimit' => 20,
- 'contextlines' => 5,
- 'contextchars' => 50,
- 'disablesuggest' => 0,
- 'skin' => false,
- 'math' => 1,
- 'usenewrc' => 0,
- 'rcdays' => 7,
- 'rclimit' => 50,
- 'wllimit' => 250,
- 'hideminor' => 0,
- 'hidepatrolled' => 0,
- 'newpageshidepatrolled' => 0,
- 'highlightbroken' => 1,
- 'stubthreshold' => 0,
- 'previewontop' => 1,
- 'previewonfirst' => 0,
- 'editsection' => 1,
- 'editsectiononrightclick' => 0,
- 'editondblclick' => 0,
- 'editwidth' => 0,
- 'showtoc' => 1,
- 'showtoolbar' => 1,
- 'minordefault' => 0,
- 'date' => 'default',
- 'imagesize' => 2,
- 'thumbsize' => 2,
- 'rememberpassword' => 0,
- 'nocache' => 0,
- 'diffonly' => 0,
- 'showhiddencats' => 0,
- 'norollbackdiff' => 0,
- 'enotifwatchlistpages' => 0,
- 'enotifusertalkpages' => 1,
- 'enotifminoredits' => 0,
- 'enotifrevealaddr' => 0,
- 'shownumberswatching' => 1,
- 'fancysig' => 0,
- 'externaleditor' => 0,
- 'externaldiff' => 0,
- 'forceeditsummary' => 0,
- 'showjumplinks' => 1,
- 'justify' => 0,
- 'numberheadings' => 0,
- 'uselivepreview' => 0,
- 'watchlistdays' => 3.0,
- 'extendwatchlist' => 0,
- 'watchlisthideminor' => 0,
- 'watchlisthidebots' => 0,
- 'watchlisthideown' => 0,
- 'watchlisthideanons' => 0,
- 'watchlisthideliu' => 0,
- 'watchlisthidepatrolled' => 0,
- 'watchcreations' => 0,
- 'watchdefault' => 0,
- 'watchmoves' => 0,
- 'watchdeletion' => 0,
- 'noconvertlink' => 0,
- 'gender' => 'unknown',
- 'ccmeonemails' => 0,
- 'disablemail' => 0,
- 'editfont' => 'default',
-);
-
-/**
- * Whether or not to allow and use real name fields.
- * @deprecated in 1.16, use $wgHiddenPrefs[] = 'realname' below to disable real
- * names
- */
-$wgAllowRealName = true;
-
-/** An array of preferences to not show for the user */
-$wgHiddenPrefs = array();
+/** @} */ # end of import/export }
-/*****************************************************************************
- * Extensions
+/*************************************************************************//**
+ * @name Extensions
+ * @{
*/
/**
@@ -2892,157 +4544,78 @@ $wgAutoloadClasses = array();
* 'descriptionmsg' => array( 'exampleextension-desc', param1, param2, ... ),
*/
$wgExtensionCredits = array();
-/*
- * end extensions
- ******************************************************************************/
/**
- * Allow user Javascript page?
- * This enables a lot of neat customizations, but may
- * increase security risk to users and server load.
+ * Authentication plugin.
*/
-$wgAllowUserJs = false;
+$wgAuth = null;
/**
- * Allow user Cascading Style Sheets (CSS)?
- * This enables a lot of neat customizations, but may
- * increase security risk to users and server load.
+ * Global list of hooks.
+ * Add a hook by doing:
+ * $wgHooks['event_name'][] = $function;
+ * or:
+ * $wgHooks['event_name'][] = array($function, $data);
+ * or:
+ * $wgHooks['event_name'][] = array($object, 'method');
*/
-$wgAllowUserCss = false;
-
-/** Use the site's Javascript page? */
-$wgUseSiteJs = true;
-
-/** Use the site's Cascading Style Sheets (CSS)? */
-$wgUseSiteCss = true;
+$wgHooks = array();
/**
- * Filter for Special:Randompage. Part of a WHERE clause
- * @deprecated as of 1.16, use the SpecialRandomGetRandomTitle hook
-*/
-
-$wgExtraRandompageSQL = false;
-
-/** Allow the "info" action, very inefficient at the moment */
-$wgAllowPageInfo = false;
-
-/** Maximum indent level of toc. */
-$wgMaxTocLevel = 999;
-
-/** Name of the external diff engine to use */
-$wgExternalDiffEngine = false;
-
-/** Use RC Patrolling to check for vandalism */
-$wgUseRCPatrol = true;
-
-/** Use new page patrolling to check new pages on Special:Newpages */
-$wgUseNPPatrol = true;
-
-/** Provide syndication feeds (RSS, Atom) for, e.g., Recentchanges, Newpages */
-$wgFeed = true;
-
-/** Set maximum number of results to return in syndication feeds (RSS, Atom) for
- * eg Recentchanges, Newpages. */
-$wgFeedLimit = 50;
-
-/** _Minimum_ timeout for cached Recentchanges feed, in seconds.
- * A cached version will continue to be served out even if changes
- * are made, until this many seconds runs out since the last render.
- *
- * If set to 0, feed caching is disabled. Use this for debugging only;
- * feed generation can be pretty slow with diffs.
- */
-$wgFeedCacheTimeout = 60;
-
-/** When generating Recentchanges RSS/Atom feed, diffs will not be generated for
- * pages larger than this size. */
-$wgFeedDiffCutoff = 32768;
-
-/** Override the site's default RSS/ATOM feed for recentchanges that appears on
- * every page. Some sites might have a different feed they'd like to promote
- * instead of the RC feed (maybe like a "Recent New Articles" or "Breaking news" one).
- * Ex: $wgSiteFeed['format'] = "http://example.com/somefeed.xml"; Format can be one
- * of either 'rss' or 'atom'.
+ * Maps jobs to their handling classes; extensions
+ * can add to this to provide custom jobs
*/
-$wgOverrideSiteFeed = array();
+$wgJobClasses = array(
+ 'refreshLinks' => 'RefreshLinksJob',
+ 'refreshLinks2' => 'RefreshLinksJob2',
+ 'htmlCacheUpdate' => 'HTMLCacheUpdateJob',
+ 'html_cache_update' => 'HTMLCacheUpdateJob', // backwards-compatible
+ 'sendMail' => 'EmaillingJob',
+ 'enotifNotify' => 'EnotifNotifyJob',
+ 'fixDoubleRedirect' => 'DoubleRedirectJob',
+ 'uploadFromUrl' => 'UploadFromUrlJob',
+);
/**
- * Which feed types should we provide by default? This can include 'rss',
- * 'atom', neither, or both.
+ * Additional functions to be performed with updateSpecialPages.
+ * Expensive Querypages are already updated.
*/
-$wgAdvertisedFeedTypes = array( 'atom' );
+$wgSpecialPageCacheUpdates = array(
+ 'Statistics' => array('SiteStatsUpdate','cacheUpdate')
+);
/**
- * Additional namespaces. If the namespaces defined in Language.php and
- * Namespace.php are insufficient, you can create new ones here, for example,
- * to import Help files in other languages.
- * PLEASE NOTE: Once you delete a namespace, the pages in that namespace will
- * no longer be accessible. If you rename it, then you can access them through
- * the new namespace name.
- *
- * Custom namespaces should start at 100 to avoid conflicting with standard
- * namespaces, and should always follow the even/odd main/talk pattern.
+ * Hooks that are used for outputting exceptions. Format is:
+ * $wgExceptionHooks[] = $funcname
+ * or:
+ * $wgExceptionHooks[] = array( $class, $funcname )
+ * Hooks should return strings or false
*/
-#$wgExtraNamespaces =
-# array(100 => "Hilfe",
-# 101 => "Hilfe_Diskussion",
-# 102 => "Aide",
-# 103 => "Discussion_Aide"
-# );
-$wgExtraNamespaces = null;
+$wgExceptionHooks = array();
-/**
- * Namespace aliases
- * These are alternate names for the primary localised namespace names, which
- * are defined by $wgExtraNamespaces and the language file. If a page is
- * requested with such a prefix, the request will be redirected to the primary
- * name.
- *
- * Set this to a map from namespace names to IDs.
- * Example:
- * $wgNamespaceAliases = array(
- * 'Wikipedian' => NS_USER,
- * 'Help' => 100,
- * );
- */
-$wgNamespaceAliases = array();
/**
- * Limit images on image description pages to a user-selectable limit. In order
- * to reduce disk usage, limits can only be selected from a list.
- * The user preference is saved as an array offset in the database, by default
- * the offset is set with $wgDefaultUserOptions['imagesize']. Make sure you
- * change it if you alter the array (see bug 8858).
- * This is the list of settings the user can choose from:
+ * Page property link table invalidation lists. When a page property
+ * changes, this may require other link tables to be updated (eg
+ * adding __HIDDENCAT__ means the hiddencat tracking category will
+ * have been added, so the categorylinks table needs to be rebuilt).
+ * This array can be added to by extensions.
*/
-$wgImageLimits = array (
- array(320,240),
- array(640,480),
- array(800,600),
- array(1024,768),
- array(1280,1024),
- array(10000,10000) );
+$wgPagePropLinkInvalidations = array(
+ 'hiddencat' => 'categorylinks',
+);
-/**
- * Adjust thumbnails on image pages according to a user setting. In order to
- * reduce disk usage, the values can only be selected from a list. This is the
- * list of settings the user can choose from:
+/** @} */ # End extensions }
+
+/*************************************************************************//**
+ * @name Categories
+ * @{
*/
-$wgThumbLimits = array(
- 120,
- 150,
- 180,
- 200,
- 250,
- 300
-);
/**
- * Adjust width of upright images when parameter 'upright' is used
- * This allows a nicer look for upright images without the need to fix the width
- * by hardcoded px in wiki sourcecode.
+ * Use experimental, DMOZ-like category browser
*/
-$wgThumbUpright = 0.75;
+$wgUseCategoryBrowser = false;
/**
* On category pages, show thumbnail gallery for images belonging to that
@@ -3056,123 +4629,33 @@ $wgCategoryMagicGallery = true;
$wgCategoryPagingLimit = 200;
/**
- * Should the default category sortkey be the prefixed title?
- * Run maintenance/refreshLinks.php after changing this.
- */
-$wgCategoryPrefixedDefaultSortkey = true;
-
-/**
- * Browser Blacklist for unicode non compliant browsers
- * Contains a list of regexps : "/regexp/" matching problematic browsers
- */
-$wgBrowserBlackList = array(
- /**
- * Netscape 2-4 detection
- * The minor version may contain strings such as "Gold" or "SGoldC-SGI"
- * Lots of non-netscape user agents have "compatible", so it's useful to check for that
- * with a negative assertion. The [UIN] identifier specifies the level of security
- * in a Netscape/Mozilla browser, checking for it rules out a number of fakers.
- * The language string is unreliable, it is missing on NS4 Mac.
- *
- * Reference: http://www.psychedelix.com/agents/index.shtml
- */
- '/^Mozilla\/2\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
- '/^Mozilla\/3\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
- '/^Mozilla\/4\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
-
- /**
- * MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>, Þ to <THORN> and Ð to <ETH>
- *
- * Known useragents:
- * - Mozilla/4.0 (compatible; MSIE 5.0; Mac_PowerPC)
- * - Mozilla/4.0 (compatible; MSIE 5.15; Mac_PowerPC)
- * - Mozilla/4.0 (compatible; MSIE 5.23; Mac_PowerPC)
- * - [...]
- *
- * @link http://en.wikipedia.org/w/index.php?title=User%3A%C6var_Arnfj%F6r%F0_Bjarmason%2Ftestme&diff=12356041&oldid=12355864
- * @link http://en.wikipedia.org/wiki/Template%3AOS9
- */
- '/^Mozilla\/4\.0 \(compatible; MSIE \d+\.\d+; Mac_PowerPC\)/',
-
- /**
- * Google wireless transcoder, seems to eat a lot of chars alive
- * http://it.wikipedia.org/w/index.php?title=Luciano_Ligabue&diff=prev&oldid=8857361
- */
- '/^Mozilla\/4\.0 \(compatible; MSIE 6.0; Windows NT 5.0; Google Wireless Transcoder;\)/'
-);
-
-/**
- * Fake out the timezone that the server thinks it's in. This will be used for
- * date display and not for what's stored in the DB. Leave to null to retain
- * your server's OS-based timezone value.
+ * Specify how category names should be sorted, when listed on a category page.
+ * A sorting scheme is also known as a collation.
*
- * This variable is currently used only for signature formatting and for local
- * time/date parser variables ({{LOCALTIME}} etc.)
+ * Available values are:
*
- * Timezones can be translated by editing MediaWiki messages of type
- * timezone-nameinlowercase like timezone-utc.
- */
-# $wgLocaltimezone = 'GMT';
-# $wgLocaltimezone = 'PST8PDT';
-# $wgLocaltimezone = 'Europe/Sweden';
-# $wgLocaltimezone = 'CET';
-$wgLocaltimezone = null;
-
-/**
- * Set an offset from UTC in minutes to use for the default timezone setting
- * for anonymous users and new user accounts.
- *
- * This setting is used for most date/time displays in the software, and is
- * overrideable in user preferences. It is *not* used for signature timestamps.
+ * - uppercase: Converts the category name to upper case, and sorts by that.
*
- * You can set it to match the configured server timezone like this:
- * $wgLocalTZoffset = date("Z") / 60;
+ * - uca-default: Provides access to the Unicode Collation Algorithm with
+ * the default element table. This is a compromise collation which sorts
+ * all languages in a mediocre way. However, it is better than "uppercase".
*
- * If your server is not configured for the timezone you want, you can set
- * this in conjunction with the signature timezone and override the PHP default
- * timezone like so:
- * $wgLocaltimezone="Europe/Berlin";
- * date_default_timezone_set( $wgLocaltimezone );
- * $wgLocalTZoffset = date("Z") / 60;
- *
- * Leave at NULL to show times in universal time (UTC/GMT).
- */
-$wgLocalTZoffset = null;
-
-
-/**
- * When translating messages with wfMsg(), it is not always clear what should
- * be considered UI messages and what should be content messages.
- *
- * For example, for the English Wikipedia, there should be only one 'mainpage',
- * so when getting the link for 'mainpage', we should treat it as site content
- * and call wfMsgForContent(), but for rendering the text of the link, we call
- * wfMsg(). The code behaves this way by default. However, sites like the
- * Wikimedia Commons do offer different versions of 'mainpage' and the like for
- * different languages. This array provides a way to override the default
- * behavior. For example, to allow language-specific main page and community
- * portal, set
+ * To use the uca-default collation, you must have PHP's intl extension
+ * installed. See http://php.net/manual/en/intl.setup.php . The details of the
+ * resulting collation will depend on the version of ICU installed on the
+ * server.
*
- * $wgForceUIMsgAsContentMsg = array( 'mainpage', 'portal-url' );
+ * After you change this, you must run maintenance/updateCollation.php to fix
+ * the sort keys in the database.
*/
-$wgForceUIMsgAsContentMsg = array();
+$wgCategoryCollation = 'uppercase';
+/** @} */ # End categories }
-/**
- * Authentication plugin.
+/*************************************************************************//**
+ * @name Logging
+ * @{
*/
-$wgAuth = null;
-
-/**
- * Global list of hooks.
- * Add a hook by doing:
- * $wgHooks['event_name'][] = $function;
- * or:
- * $wgHooks['event_name'][] = array($function, $data);
- * or:
- * $wgHooks['event_name'][] = array($object, 'method');
- */
-$wgHooks = array();
/**
* The logging system has two levels: an event type, which describes the
@@ -3283,6 +4766,7 @@ $wgLogActions = array(
'protect/unprotect' => 'unprotectedarticle',
'protect/move_prot' => 'movedarticleprotection',
'rights/rights' => 'rightslogentry',
+ 'rights/disable' => 'disableaccount-logentry',
'delete/delete' => 'deletedarticle',
'delete/restore' => 'undeletedarticle',
'delete/revision' => 'revdelete-logentry',
@@ -3299,13 +4783,15 @@ $wgLogActions = array(
'suppress/file' => 'revdelete-logentry',
'suppress/event' => 'logdelete-logentry',
'suppress/delete' => 'suppressedarticle',
- 'suppress/block' => 'blocklogentry',
+ 'suppress/block' => 'blocklogentry',
'suppress/reblock' => 'reblock-logentry',
+ 'patrol/patrol' => 'patrol-log-line',
);
/**
* The same as above, but here values are names of functions,
- * not messages
+ * not messages.
+ * @see LogPage::actionText
*/
$wgLogActionsHandlers = array();
@@ -3315,6 +4801,29 @@ $wgLogActionsHandlers = array();
$wgNewUserLog = true;
/**
+ * Log the automatic creations of new users accounts?
+ */
+$wgLogAutocreatedAccounts = false;
+
+/** @} */ # end logging }
+
+/*************************************************************************//**
+ * @name Special pages (general and miscellaneous)
+ * @{
+ */
+
+/**
+ * Allow special page inclusions such as {{Special:Allpages}}
+ */
+$wgAllowSpecialInclusion = true;
+
+/**
+ * Set this to an array of special page names to prevent
+ * maintenance/updateSpecialPages.php from updating those pages.
+ */
+$wgDisableQueryPageUpdate = false;
+
+/**
* List of special pages, followed by what subtitle they should go under
* at Special:SpecialPages
*/
@@ -3396,6 +4905,7 @@ $wgSpecialPageGroups = array(
'Search' => 'redirects',
'LinkSearch' => 'redirects',
+ 'ComparePages' => 'pagetools',
'Movepage' => 'pagetools',
'MergeHistory' => 'pagetools',
'Revisiondelete' => 'pagetools',
@@ -3416,56 +4926,35 @@ $wgSpecialPageGroups = array(
'Booksources' => 'other',
);
-/**
- * Disable the internal MySQL-based search, to allow it to be
- * implemented by an extension instead.
- */
-$wgDisableInternalSearch = false;
+/** Whether or not to sort special pages in Special:Specialpages */
-/**
- * Set this to a URL to forward search requests to some external location.
- * If the URL includes '$1', this will be replaced with the URL-encoded
- * search term.
- *
- * For example, to forward to Google you'd have something like:
- * $wgSearchForwardUrl = 'http://www.google.com/search?q=$1' .
- * '&domains=http://example.com' .
- * '&sitesearch=http://example.com' .
- * '&ie=utf-8&oe=utf-8';
- */
-$wgSearchForwardUrl = null;
+$wgSortSpecialPages = true;
/**
- * Set a default target for external links, e.g. _blank to pop up a new window
+ * Filter for Special:Randompage. Part of a WHERE clause
+ * @deprecated as of 1.16, use the SpecialRandomGetRandomTitle hook
*/
-$wgExternalLinkTarget = false;
+$wgExtraRandompageSQL = false;
/**
- * If true, external URL links in wiki text will be given the
- * rel="nofollow" attribute as a hint to search engines that
- * they should not be followed for ranking purposes as they
- * are user-supplied and thus subject to spamming.
+ * On Special:Unusedimages, consider images "used", if they are put
+ * into a category. Default (false) is not to count those as used.
*/
-$wgNoFollowLinks = true;
+$wgCountCategorizedImagesAsUsed = false;
/**
- * Namespaces in which $wgNoFollowLinks doesn't apply.
- * See Language.php for a list of namespaces.
+ * Maximum number of links to a redirect page listed on
+ * Special:Whatlinkshere/RedirectDestination
*/
-$wgNoFollowNsExceptions = array();
+$wgMaxRedirectLinksRetrieved = 500;
-/**
- * If this is set to an array of domains, external links to these domain names
- * (or any subdomains) will not be set to rel="nofollow" regardless of the
- * value of $wgNoFollowLinks. For instance:
- *
- * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org' );
- *
- * This would add rel="nofollow" to links to de.wikipedia.org, but not
- * en.wikipedia.org, wiktionary.org, en.wiktionary.org, us.en.wikipedia.org,
- * etc.
+/** @} */ # end special pages }
+
+/*************************************************************************//**
+ * @name Robot (search engine crawler) policy
+ * See also $wgNoFollowLinks.
+ * @{
*/
-$wgNoFollowDomainExceptions = array();
/**
* Default robot policy. The default policy is to encourage indexing and fol-
@@ -3516,251 +5005,76 @@ $wgArticleRobotPolicies = array();
*/
$wgExemptFromUserRobotsControl = null;
-/**
- * Specifies the minimal length of a user password. If set to 0, empty pass-
- * words are allowed.
- */
-$wgMinimalPasswordLength = 1;
-
-/**
- * Activate external editor interface for files and pages
- * See http://www.mediawiki.org/wiki/Manual:External_editors
- */
-$wgUseExternalEditor = true;
-
-/** Whether or not to sort special pages in Special:Specialpages */
-
-$wgSortSpecialPages = true;
-
-/**
- * Specify the name of a skin that should not be presented in the list of a-
- * vailable skins. Use for blacklisting a skin which you do not want to remove
- * from the .../skins/ directory
- */
-$wgSkipSkin = '';
-$wgSkipSkins = array(); # More of the same
-
-/**
- * Array of disabled article actions, e.g. view, edit, dublincore, delete, etc.
- */
-$wgDisabledActions = array();
-
-/**
- * Disable redirects to special pages and interwiki redirects, which use a 302
- * and have no "redirected from" link.
- */
-$wgDisableHardRedirects = false;
-
-/**
- * Set to false to disable application of access keys and tooltips,
- * eg to avoid keyboard conflicts with system keys or as a low-level
- * optimization.
- */
-$wgEnableTooltipsAndAccesskeys = true;
-
-/**
- * Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies
- * @since 1.16
- */
-$wgEnableDnsBlacklist = false;
-
-/**
- * @deprecated Use $wgEnableDnsBlacklist instead, only kept for backward
- * compatibility
- */
-$wgEnableSorbs = false;
-
-/**
- * List of DNS blacklists to use, if $wgEnableDnsBlacklist is true
- * @since 1.16
- */
-$wgDnsBlacklistUrls = array( 'http.dnsbl.sorbs.net.' );
-
-/**
- * @deprecated Use $wgDnsBlacklistUrls instead, only kept for backward
- * compatibility
- */
-$wgSorbsUrl = array();
-
-/**
- * Proxy whitelist, list of addresses that are assumed to be non-proxy despite
- * what the other methods might say.
- */
-$wgProxyWhitelist = array();
-
-/**
- * Simple rate limiter options to brake edit floods. Maximum number actions
- * allowed in the given number of seconds; after that the violating client re-
- * ceives HTTP 500 error pages until the period elapses.
- *
- * array( 4, 60 ) for a maximum of 4 hits in 60 seconds.
- *
- * This option set is experimental and likely to change. Requires memcached.
- */
-$wgRateLimits = array(
- 'edit' => array(
- 'anon' => null, // for any and all anonymous edits (aggregate)
- 'user' => null, // for each logged-in user
- 'newbie' => null, // for each recent (autoconfirmed) account; overrides 'user'
- 'ip' => null, // for each anon and recent account
- 'subnet' => null, // ... with final octet removed
- ),
- 'move' => array(
- 'user' => null,
- 'newbie' => null,
- 'ip' => null,
- 'subnet' => null,
- ),
- 'mailpassword' => array(
- 'anon' => null,
- ),
- 'emailuser' => array(
- 'user' => null,
- ),
- );
-
-/**
- * Set to a filename to log rate limiter hits.
- */
-$wgRateLimitLog = null;
-
-/**
- * Array of groups which should never trigger the rate limiter
- *
- * @deprecated as of 1.13.0, the preferred method is using
- * $wgGroupPermissions[]['noratelimit']. However, this will still
- * work if desired.
- *
- * $wgRateLimitsExcludedGroups = array( 'sysop', 'bureaucrat' );
- */
-$wgRateLimitsExcludedGroups = array();
-
-/**
- * Array of IPs which should be excluded from rate limits.
- * This may be useful for whitelisting NAT gateways for conferences, etc.
- */
-$wgRateLimitsExcludedIPs = array();
+/** @} */ # End robot policy }
-/**
- * On Special:Unusedimages, consider images "used", if they are put
- * into a category. Default (false) is not to count those as used.
+/************************************************************************//**
+ * @name AJAX and API
+ * Note: The AJAX entry point which this section refers to is gradually being
+ * replaced by the API entry point, api.php. They are essentially equivalent.
+ * Both of them are used for dynamic client-side features, via XHR.
+ * @{
*/
-$wgCountCategorizedImagesAsUsed = false;
-
-/**
- * External stores allow including content
- * from non database sources following URL links
- *
- * Short names of ExternalStore classes may be specified in an array here:
- * $wgExternalStores = array("http","file","custom")...
- *
- * CAUTION: Access to database might lead to code execution
- */
-$wgExternalStores = false;
-
-/**
- * An array of external mysql servers, e.g.
- * $wgExternalServers = array( 'cluster1' => array( 'srv28', 'srv29', 'srv30' ) );
- * Used by LBFactory_Simple, may be ignored if $wgLBFactoryConf is set to another class.
- */
-$wgExternalServers = array();
-
-/**
- * The place to put new revisions, false to put them in the local text table.
- * Part of a URL, e.g. DB://cluster1
- *
- * Can be an array instead of a single string, to enable data distribution. Keys
- * must be consecutive integers, starting at zero. Example:
- *
- * $wgDefaultExternalStore = array( 'DB://cluster1', 'DB://cluster2' );
- *
- */
-$wgDefaultExternalStore = false;
-
-/**
- * Revision text may be cached in $wgMemc to reduce load on external storage
- * servers and object extraction overhead for frequently-loaded revisions.
- *
- * Set to 0 to disable, or number of seconds before cache expiry.
- */
-$wgRevisionCacheExpiry = 0;
/**
- * list of trusted media-types and mime types.
- * Use the MEDIATYPE_xxx constants to represent media types.
- * This list is used by Image::isSafeFile
+ * Enable the MediaWiki API for convenient access to
+ * machine-readable data via api.php
*
- * Types not listed here will have a warning about unsafe content
- * displayed on the images description page. It would also be possible
- * to use this for further restrictions, like disabling direct
- * [[media:...]] links for non-trusted formats.
- */
-$wgTrustedMediaFormats= array(
- MEDIATYPE_BITMAP, //all bitmap formats
- MEDIATYPE_AUDIO, //all audio formats
- MEDIATYPE_VIDEO, //all plain video formats
- "image/svg+xml", //svg (only needed if inline rendering of svg is not supported)
- "application/pdf", //PDF files
- #"application/x-shockwave-flash", //flash/shockwave movie
-);
-
-/**
- * Allow special page inclusions such as {{Special:Allpages}}
- */
-$wgAllowSpecialInclusion = true;
-
-/**
- * Timeout for HTTP requests done internally
+ * See http://www.mediawiki.org/wiki/API
*/
-$wgHTTPTimeout = 25;
+$wgEnableAPI = true;
/**
- * Timeout for Asynchronous (background) HTTP requests
+ * Allow the API to be used to perform write operations
+ * (page edits, rollback, etc.) when an authorised user
+ * accesses it
*/
-$wgAsyncHTTPTimeout = 25;
+$wgEnableWriteAPI = true;
/**
- * Proxy to use for CURL requests.
+ * API module extensions
+ * Associative array mapping module name to class name.
+ * Extension modules may override the core modules.
*/
-$wgHTTPProxy = false;
+$wgAPIModules = array();
+$wgAPIMetaModules = array();
+$wgAPIPropModules = array();
+$wgAPIListModules = array();
/**
- * Enable interwiki transcluding. Only when iw_trans=1.
- */
-$wgEnableScaryTranscluding = false;
-/**
- * Expiry time for interwiki transclusion
+ * Maximum amount of rows to scan in a DB query in the API
+ * The default value is generally fine
*/
-$wgTranscludeCacheExpiry = 3600;
+$wgAPIMaxDBRows = 5000;
/**
- * Support blog-style "trackbacks" for articles. See
- * http://www.sixapart.com/pronet/docs/trackback_spec for details.
+ * The maximum size (in bytes) of an API result.
+ * Don't set this lower than $wgMaxArticleSize*1024
*/
-$wgUseTrackbacks = false;
+$wgAPIMaxResultSize = 8388608;
/**
- * Enable filtering of categories in Recentchanges
+ * The maximum number of uncached diffs that can be retrieved in one API
+ * request. Set this to 0 to disable API diffs altogether
*/
-$wgAllowCategorizedRecentChanges = false ;
+$wgAPIMaxUncachedDiffs = 1;
/**
- * Number of jobs to perform per request. May be less than one in which case
- * jobs are performed probabalistically. If this is zero, jobs will not be done
- * during ordinary apache requests. In this case, maintenance/runJobs.php should
- * be run periodically.
+ * Log file or URL (TCP or UDP) to log API requests to, or false to disable
+ * API request logging
*/
-$wgJobRunRate = 1;
+$wgAPIRequestLog = false;
/**
- * Number of rows to update per job
+ * Cache the API help text for up to an hour. Disable this during API
+ * debugging and development
*/
-$wgUpdateRowsPerJob = 500;
+$wgAPICacheHelp = true;
/**
- * Number of rows to update per query
+ * Set the timeout for the API help text cache. Ignored if $wgAPICacheHelp
+ * is false.
*/
-$wgUpdateRowsPerQuery = 100;
+$wgAPICacheHelpTimeout = 60*60;
/**
* Enable AJAX framework
@@ -3771,7 +5085,7 @@ $wgUseAjax = true;
* List of Ajax-callable functions.
* Extensions acting as Ajax callbacks must register here
*/
-$wgAjaxExportList = array( 'wfAjaxGetThumbnailUrl', 'wfAjaxGetFileUrl' );
+$wgAjaxExportList = array( 'wfAjaxGetFileUrl' );
/**
* Enable watching/unwatching pages using AJAX.
@@ -3791,43 +5105,40 @@ $wgAjaxUploadDestCheck = true;
$wgAjaxLicensePreview = true;
/**
- * Allow DISPLAYTITLE to change title display
+ * 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.
+ * This is currently only used by the API (requests to api.php)
+ * $wgCrossSiteAJAXdomains can be set using a wildcard syntax:
+ *
+ * '*' matches any number of characters
+ * '?' matches any 1 character
+ *
+ * Example:
+ $wgCrossSiteAJAXdomains = array(
+ 'www.mediawiki.org',
+ '*.wikipedia.org',
+ '*.wikimedia.org',
+ '*.wiktionary.org',
+ );
+ *
*/
-$wgAllowDisplayTitle = true;
+$wgCrossSiteAJAXdomains = array();
/**
- * for consistency, restrict DISPLAYTITLE to titles that normalize to the same canonical DB key
+ * Domains that should not be allowed to make AJAX requests,
+ * even if they match one of the domains allowed by $wgCrossSiteAJAXdomains
+ * Uses the same syntax as $wgCrossSiteAJAXdomains
*/
-$wgRestrictDisplayTitle = true;
-/**
- * Array of usernames which may not be registered or logged in from
- * Maintenance scripts can still use these
- */
-$wgReservedUsernames = array(
- 'MediaWiki default', // Default 'Main Page' and MediaWiki: message pages
- 'Conversion script', // Used for the old Wikipedia software upgrade
- 'Maintenance script', // Maintenance scripts which perform editing, image import script
- 'Template namespace initialisation script', // Used in 1.2->1.3 upgrade
- 'msg:double-redirect-fixer', // Automatic double redirect fix
-);
+$wgCrossSiteAJAXdomainExceptions = array();
-/**
- * MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't
- * perform basic stuff like MIME detection and which are vulnerable to further idiots uploading
- * crap files as images. When this directive is on, <title> will be allowed in files with
- * an "image/svg+xml" MIME type. You should leave this disabled if your web server is misconfigured
- * and doesn't send appropriate MIME types for SVG images.
- */
-$wgAllowTitlesInSVG = false;
+/** @} */ # End AJAX and API }
-/**
- * Array of namespaces which can be deemed to contain valid "content", as far
- * as the site statistics are concerned. Useful if additional namespaces also
- * contain "content" which should be considered when generating a count of the
- * number of articles in the wiki.
+/************************************************************************//**
+ * @name Shell and process control
+ * @{
*/
-$wgContentNamespaces = array( NS_MAIN );
/**
* Maximum amount of virtual memory available to shell processes under linux, in KB.
@@ -3851,211 +5162,85 @@ $wgMaxShellTime = 180;
$wgPhpCli = '/usr/bin/php';
/**
- * DJVU settings
- * Path of the djvudump executable
- * Enable this and $wgDjvuRenderer to enable djvu rendering
- */
-# $wgDjvuDump = 'djvudump';
-$wgDjvuDump = null;
-
-/**
- * Path of the ddjvu DJVU renderer
- * Enable this and $wgDjvuDump to enable djvu rendering
- */
-# $wgDjvuRenderer = 'ddjvu';
-$wgDjvuRenderer = null;
-
-/**
- * Path of the djvutxt DJVU text extraction utility
- * Enable this and $wgDjvuDump to enable text layer extraction from djvu files
- */
-# $wgDjvuTxt = 'djvutxt';
-$wgDjvuTxt = null;
-
-/**
- * Path of the djvutoxml executable
- * This works like djvudump except much, much slower as of version 3.5.
- *
- * For now I recommend you use djvudump instead. The djvuxml output is
- * probably more stable, so we'll switch back to it as soon as they fix
- * the efficiency problem.
- * http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
- */
-# $wgDjvuToXML = 'djvutoxml';
-$wgDjvuToXML = null;
-
-
-/**
- * Shell command for the DJVU post processor
- * Default: pnmtopng, since ddjvu generates ppm output
- * Set this to false to output the ppm file directly.
- */
-$wgDjvuPostProcessor = 'pnmtojpeg';
-/**
- * File extension for the DJVU post processor output
- */
-$wgDjvuOutputExtension = 'jpg';
-
-/**
- * Enable the MediaWiki API for convenient access to
- * machine-readable data via api.php
- *
- * See http://www.mediawiki.org/wiki/API
+ * Locale for LC_CTYPE, to work around http://bugs.php.net/bug.php?id=45132
+ * For Unix-like operating systems, set this to to a locale that has a UTF-8
+ * character set. Only the character set is relevant.
*/
-$wgEnableAPI = true;
+$wgShellLocale = 'en_US.utf8';
-/**
- * Allow the API to be used to perform write operations
- * (page edits, rollback, etc.) when an authorised user
- * accesses it
- */
-$wgEnableWriteAPI = true;
+/** @} */ # End shell }
-/**
- * API module extensions
- * Associative array mapping module name to class name.
- * Extension modules may override the core modules.
+/************************************************************************//**
+ * @name HTTP client
+ * @{
*/
-$wgAPIModules = array();
-$wgAPIMetaModules = array();
-$wgAPIPropModules = array();
-$wgAPIListModules = array();
/**
- * Maximum amount of rows to scan in a DB query in the API
- * The default value is generally fine
+ * Timeout for HTTP requests done internally
*/
-$wgAPIMaxDBRows = 5000;
+$wgHTTPTimeout = 25;
/**
- * The maximum size (in bytes) of an API result.
- * Don't set this lower than $wgMaxArticleSize*1024
+ * Timeout for Asynchronous (background) HTTP requests
*/
-$wgAPIMaxResultSize = 8388608;
+$wgAsyncHTTPTimeout = 25;
/**
- * The maximum number of uncached diffs that can be retrieved in one API
- * request. Set this to 0 to disable API diffs altogether
+ * Proxy to use for CURL requests.
*/
-$wgAPIMaxUncachedDiffs = 1;
+$wgHTTPProxy = false;
-/**
- * Log file or URL (TCP or UDP) to log API requests to, or false to disable
- * API request logging
- */
-$wgAPIRequestLog = false;
+/** @} */ # End HTTP client }
-/**
- * Cache the API help text for up to an hour. Disable this during API
- * debugging and development
+/************************************************************************//**
+ * @name Job queue
+ * See also $wgEnotifUseJobQ.
+ * @{
*/
-$wgAPICacheHelp = true;
/**
- * Set the timeout for the API help text cache. Ignored if $wgAPICacheHelp
- * is false.
+ * Number of jobs to perform per request. May be less than one in which case
+ * jobs are performed probabalistically. If this is zero, jobs will not be done
+ * during ordinary apache requests. In this case, maintenance/runJobs.php should
+ * be run periodically.
*/
-$wgAPICacheHelpTimeout = 60*60;
+$wgJobRunRate = 1;
/**
- * Parser test suite files to be run by parserTests.php when no specific
- * filename is passed to it.
- *
- * Extensions may add their own tests to this array, or site-local tests
- * may be added via LocalSettings.php
- *
- * Use full paths.
+ * Number of rows to update per job
*/
-$wgParserTestFiles = array(
- "$IP/maintenance/parserTests.txt",
-);
+$wgUpdateRowsPerJob = 500;
/**
- * If configured, specifies target CodeReview installation to send test
- * result data from 'parserTests.php --upload'
- *
- * Something like this:
- * $wgParserTestRemote = array(
- * 'api-url' => 'http://www.mediawiki.org/w/api.php',
- * 'repo' => 'MediaWiki',
- * 'suite' => 'ParserTests',
- * 'path' => '/trunk/phase3', // not used client-side; for reference
- * 'secret' => 'qmoicj3mc4mcklmqw', // Shared secret used in HMAC validation
- * );
+ * Number of rows to update per query
*/
-$wgParserTestRemote = false;
+$wgUpdateRowsPerQuery = 100;
-/**
- * Break out of framesets. This can be used to prevent clickjacking attacks,
- * or to prevent external sites from framing your site with ads.
- */
-$wgBreakFrames = false;
+/** @} */ # End job queue }
-/**
- * The X-Frame-Options header to send on pages sensitive to clickjacking
- * attacks, such as edit pages. This prevents those pages from being displayed
- * in a frame or iframe. The options are:
- *
- * - 'DENY': Do not allow framing. This is recommended for most wikis.
- *
- * - 'SAMEORIGIN': Allow framing by pages on the same domain. This can be used
- * to allow framing within a trusted domain. This is insecure if there
- * is a page on the same domain which allows framing of arbitrary URLs.
- *
- * - false: Allow all framing. This opens up the wiki to XSS attacks and thus
- * full compromise of local user accounts. Private wikis behind a
- * corporate firewall are especially vulnerable. This is not
- * recommended.
- *
- * For extra safety, set $wgBreakFrames = true, to prevent framing on all pages,
- * not just edit pages.
+/************************************************************************//**
+ * @name Miscellaneous
+ * @{
*/
-$wgEditPageFrameOptions = 'DENY';
-/**
- * Set this to an array of special page names to prevent
- * maintenance/updateSpecialPages.php from updating those pages.
- */
-$wgDisableQueryPageUpdate = false;
+/** Allow the "info" action, very inefficient at the moment */
+$wgAllowPageInfo = false;
-/**
- * Disable output compression (enabled by default if zlib is available)
- */
-$wgDisableOutputCompression = false;
+/** Name of the external diff engine to use */
+$wgExternalDiffEngine = false;
/**
- * If lag is higher than $wgSlaveLagWarning, show a warning in some special
- * pages (like watchlist). If the lag is higher than $wgSlaveLagCritical,
- * show a more obvious warning.
+ * Array of disabled article actions, e.g. view, edit, dublincore, delete, etc.
*/
-$wgSlaveLagWarning = 10;
-$wgSlaveLagCritical = 30;
+$wgDisabledActions = array();
/**
- * Parser configuration. Associative array with the following members:
- *
- * class The class name
- *
- * preprocessorClass The preprocessor class. Two classes are currently available:
- * Preprocessor_Hash, which uses plain PHP arrays for tempoarary
- * storage, and Preprocessor_DOM, which uses the DOM module for
- * temporary storage. Preprocessor_DOM generally uses less memory;
- * the speed of the two is roughly the same.
- *
- * If this parameter is not given, it uses Preprocessor_DOM if the
- * DOM module is available, otherwise it uses Preprocessor_Hash.
- *
- * The entire associative array will be passed through to the constructor as
- * the first parameter. Note that only Setup.php can use this variable --
- * the configuration will change at runtime via $wgParser member functions, so
- * the contents of this variable will be out-of-date. The variable can only be
- * changed during LocalSettings.php, in particular, it can't be changed during
- * an extension setup function.
+ * Disable redirects to special pages and interwiki redirects, which use a 302
+ * and have no "redirected from" link. Note this is only for articles with #Redirect
+ * in them. URL's containing a local interwiki prefix (or a non-canonical special
+ * page name) are still hard redirected regardless of this setting.
*/
-$wgParserConf = array(
- 'class' => 'Parser',
- #'preprocessorClass' => 'Preprocessor_Hash',
-);
+$wgDisableHardRedirects = false;
/**
* LinkHolderArray batch size
@@ -4070,38 +5255,6 @@ $wgLinkHolderBatchSize = 1000;
$wgRegisterInternalExternals = false;
/**
- * Hooks that are used for outputting exceptions. Format is:
- * $wgExceptionHooks[] = $funcname
- * or:
- * $wgExceptionHooks[] = array( $class, $funcname )
- * Hooks should return strings or false
- */
-$wgExceptionHooks = array();
-
-/**
- * Page property link table invalidation lists. When a page property
- * changes, this may require other link tables to be updated (eg
- * adding __HIDDENCAT__ means the hiddencat tracking category will
- * have been added, so the categorylinks table needs to be rebuilt).
- * This array can be added to by extensions.
- */
-$wgPagePropLinkInvalidations = array(
- 'hiddencat' => 'categorylinks',
-);
-
-/**
- * Maximum number of links to a redirect page listed on
- * Special:Whatlinkshere/RedirectDestination
- */
-$wgMaxRedirectLinksRetrieved = 500;
-
-/**
- * Maximum number of calls per parse to expensive parser functions such as
- * PAGESINCATEGORY.
- */
-$wgExpensiveParserFunctionLimit = 100;
-
-/**
* Maximum number of pages to move at once when moving subpages with a page.
*/
$wgMaximumMovedPages = 100;
@@ -4113,133 +5266,12 @@ $wgMaximumMovedPages = 100;
$wgFixDoubleRedirects = false;
/**
- * Max number of redirects to follow when resolving redirects.
- * 1 means only the first redirect is followed (default behavior).
- * 0 or less means no redirects are followed.
- */
-$wgMaxRedirects = 1;
-
-/**
- * Array of invalid page redirect targets.
- * Attempting to create a redirect to any of the pages in this array
- * will make the redirect fail.
- * Userlogout is hard-coded, so it does not need to be listed here.
- * (bug 10569) Disallow Mypage and Mytalk as well.
- *
- * As of now, this only checks special pages. Redirects to pages in
- * other namespaces cannot be invalidated by this variable.
- */
-$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' );
-
-/**
- * Array of namespaces to generate a sitemap for when the
- * maintenance/generateSitemap.php script is run, or false if one is to be ge-
- * nerated for all namespaces.
- */
-$wgSitemapNamespaces = false;
-
-
-/**
- * If user doesn't specify any edit summary when making a an edit, MediaWiki
- * will try to automatically create one. This feature can be disabled by set-
- * ting this variable false.
- */
-$wgUseAutomaticEditSummaries = true;
-
-/**
- * Limit password attempts to X attempts per Y seconds per IP per account.
- * Requires memcached.
- */
-$wgPasswordAttemptThrottle = array( 'count' => 5, 'seconds' => 300 );
-
-/**
- * Display user edit counts in various prominent places.
- */
-$wgEdititis = false;
-
-/**
- * Enable the UniversalEditButton for browsers that support it
- * (currently only Firefox with an extension)
- * See http://universaleditbutton.org for more background information
- */
-$wgUniversalEditButton = true;
-
-/**
- * Should we allow a broader set of characters in id attributes, per HTML5? If
- * not, use only HTML 4-compatible IDs. This option is for testing -- when the
- * functionality is ready, it will be on by default with no option.
- *
- * Currently this appears to work fine in Chrome 4 and 5, Firefox 3.5 and 3.6, IE6
- * and 8, and Opera 10.50, but it fails in Opera 10.10: Unicode IDs don't seem
- * to work as anchors. So not quite ready for general use yet.
- */
-$wgExperimentalHtmlIds = false;
-
-/**
- * Search form behavior
- * true = use Go & Search buttons
- * false = use Go button & Advanced search link
- */
-$wgUseTwoButtonsSearchForm = true;
-
-/**
- * Search form behavior for Vector skin only
- * true = use an icon search button
- * false = use Go & Search buttons
- */
-$wgVectorUseSimpleSearch = false;
-
-/**
- * Watch and unwatch as an icon rather than a link for Vector skin only
- * true = use an icon watch/unwatch button
- * false = use watch/unwatch text link
- */
-$wgVectorUseIconWatch = false;
-
-/**
- * Add extra stylesheets for Vector - This is only being used so that we can play around with different options while
- * keeping our CSS code in the SVN and not having to change the main Vector styles. This will probably go away later on.
- * null = add no extra styles
- * array = list of style paths relative to skins/vector/
- */
-$wgVectorExtraStyles = null;
-
-/**
- * Preprocessor caching threshold
- */
-$wgPreprocessorCacheThreshold = 1000;
-
-/**
- * Allow filtering by change tag in recentchanges, history, etc
- * Has no effect if no tags are defined in valid_tag.
- */
-$wgUseTagFilter = true;
-
-/**
* Allow redirection to another page when a user logs in.
* To enable, set to a string like 'Main Page'
*/
$wgRedirectOnLogin = null;
/**
- * Characters to prevent during new account creations.
- * This is used in a regular expression character class during
- * registration (regex metacharacters like / are escaped).
- */
-$wgInvalidUsernameCharacters = '@';
-
-/**
- * Character used as a delimiter when testing for interwiki userrights
- * (In Special:UserRights, it is possible to modify users on different
- * databases if the delimiter is used, e.g. Someuser@enwiki).
- *
- * It is recommended that you have this delimiter in
- * $wgInvalidUsernameCharacters above, or you will not be able to
- * modify the user rights of those users via Special:UserRights
- */
-$wgUserrightsInterwikiDelimiter = '@';
-
-/**
* Configuration for processing pool control, for use in high-traffic wikis.
* An implementation is provided in the PoolCounter extension.
*
@@ -4248,112 +5280,30 @@ $wgUserrightsInterwikiDelimiter = '@';
* The remaining elements are passed through to the class as constructor
* parameters. Example:
*
- * $wgPoolCounterConf = array( 'Article::view' => array(
+ * $wgPoolCounterConf = array( 'ArticleView' => array(
* 'class' => 'PoolCounter_Client',
+ * 'timeout' => 15, // wait timeout in seconds
+ * 'workers' => 5, // maximum number of active threads in each pool
+ * 'maxqueue' => 50, // maximum number of total threads in each pool
* ... any extension-specific options...
* );
*/
$wgPoolCounterConf = null;
/**
- * Use some particular type of external authentication. The specific
- * authentication module you use will normally require some extra settings to
- * be specified.
- *
- * null indicates no external authentication is to be used. Otherwise,
- * $wgExternalAuthType must be the name of a non-abstract class that extends
- * ExternalUser.
- *
- * Core authentication modules can be found in includes/extauth/.
- */
-$wgExternalAuthType = null;
-
-/**
- * Configuration for the external authentication. This may include arbitrary
- * keys that depend on the authentication mechanism. For instance,
- * authentication against another web app might require that the database login
- * info be provided. Check the file where your auth mechanism is defined for
- * info on what to put here.
- */
-$wgExternalAuthConfig = array();
-
-/**
- * When should we automatically create local accounts when external accounts
- * already exist, if using ExternalAuth? Can have three values: 'never',
- * 'login', 'view'. 'view' requires the external database to support cookies,
- * and implies 'login'.
- *
- * TODO: Implement 'view' (currently behaves like 'login').
- */
-$wgAutocreatePolicy = 'login';
-
-/**
- * Policies for how each preference is allowed to be changed, in the presence
- * of external authentication. The keys are preference keys, e.g., 'password'
- * or 'emailaddress' (see Preferences.php et al.). The value can be one of the
- * following:
- *
- * - local: Allow changes to this pref through the wiki interface but only
- * apply them locally (default).
- * - semiglobal: Allow changes through the wiki interface and try to apply them
- * to the foreign database, but continue on anyway if that fails.
- * - global: Allow changes through the wiki interface, but only let them go
- * through if they successfully update the foreign database.
- * - message: Allow no local changes for linked accounts; replace the change
- * form with a message provided by the auth plugin, telling the user how to
- * change the setting externally (maybe providing a link, etc.). If the auth
- * plugin provides no message for this preference, hide it entirely.
- *
- * Accounts that are not linked to an external account are never affected by
- * this setting. You may want to look at $wgHiddenPrefs instead.
- * $wgHiddenPrefs supersedes this option.
- *
- * TODO: Implement message, global.
- */
-$wgAllowPrefChange = array();
-
-
-/**
- * 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.
- * This is currently only used by the API (requests to api.php)
- * $wgCrossSiteAJAXdomains can be set using a wildcard syntax:
- *
- * '*' matches any number of characters
- * '?' matches any 1 character
- *
- * Example:
- $wgCrossSiteAJAXdomains = array(
- 'www.mediawiki.org',
- '*.wikipedia.org',
- '*.wikimedia.org',
- '*.wiktionary.org',
- );
- *
- */
-$wgCrossSiteAJAXdomains = array();
-
-/**
- * Domains that should not be allowed to make AJAX requests,
- * even if they match one of the domains allowed by $wgCrossSiteAJAXdomains
- * Uses the same syntax as $wgCrossSiteAJAXdomains
- */
-
-$wgCrossSiteAJAXdomainExceptions = array();
-
-/**
- * The minimum amount of memory that MediaWiki "needs"; MediaWiki will try to raise PHP's memory limit if it's below this amount.
- */
-$wgMemoryLimit = "50M";
-
-/**
* To disable file delete/restore temporarily
*/
$wgUploadMaintenance = false;
/**
- * Use old names for change_tags indices.
+ * Allows running of selenium tests via maintenance/tests/RunSeleniumTests.php
*/
-$wgOldChangeTagsIndex = false;
+$wgEnableSelenium = false;
+$wgSeleniumTestConfigs = array();
+$wgSeleniumConfigFile = 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 7be569af..64197d9c 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -9,7 +9,7 @@
*/
define( 'MW_SPECIALPAGE_VERSION', 2 );
-/**#@+
+/**@{
* Database related constants
*/
define( 'DBO_DEBUG', 1 );
@@ -19,27 +19,31 @@ define( 'DBO_TRX', 8 );
define( 'DBO_DEFAULT', 16 );
define( 'DBO_PERSISTENT', 32 );
define( 'DBO_SYSDBA', 64 ); //for oracle maintenance
-/**#@-*/
+define( 'DBO_DDLMODE', 128 ); // when using schema files: mostly for Oracle
+/**@}*/
-# Valid database indexes
-# Operation-based indexes
+/**@{
+ * Valid database indexes
+ * Operation-based indexes
+ */
define( 'DB_SLAVE', -1 ); # Read from the slave (or only server)
define( 'DB_MASTER', -2 ); # Write to master (or only server)
define( 'DB_LAST', -3 ); # Whatever database was used last
+/**@}*/
# Obsolete aliases
define( 'DB_READ', -1 );
define( 'DB_WRITE', -2 );
-/**#@+
+/**@{
* Virtual namespaces; don't appear in the page database
*/
-define('NS_MEDIA', -2);
-define('NS_SPECIAL', -1);
-/**#@-*/
+define( 'NS_MEDIA', -2 );
+define( 'NS_SPECIAL', -1 );
+/**@}*/
-/**#@+
+/**@{
* Real namespaces
*
* Number 100 and beyond are reserved for custom namespaces;
@@ -47,22 +51,23 @@ define('NS_SPECIAL', -1);
* DO NOT Change integer values as they are most probably hardcoded everywhere
* see bug #696 which talked about that.
*/
-define('NS_MAIN', 0);
-define('NS_TALK', 1);
-define('NS_USER', 2);
-define('NS_USER_TALK', 3);
-define('NS_PROJECT', 4);
-define('NS_PROJECT_TALK', 5);
-define('NS_FILE', 6);
-define('NS_FILE_TALK', 7);
-define('NS_MEDIAWIKI', 8);
-define('NS_MEDIAWIKI_TALK', 9);
-define('NS_TEMPLATE', 10);
-define('NS_TEMPLATE_TALK', 11);
-define('NS_HELP', 12);
-define('NS_HELP_TALK', 13);
-define('NS_CATEGORY', 14);
-define('NS_CATEGORY_TALK', 15);
+define( 'NS_MAIN', 0 );
+define( 'NS_TALK', 1 );
+define( 'NS_USER', 2 );
+define( 'NS_USER_TALK', 3 );
+define( 'NS_PROJECT', 4 );
+define( 'NS_PROJECT_TALK', 5 );
+define( 'NS_FILE', 6 );
+define( 'NS_FILE_TALK', 7 );
+define( 'NS_MEDIAWIKI', 8 );
+define( 'NS_MEDIAWIKI_TALK', 9 );
+define( 'NS_TEMPLATE', 10 );
+define( 'NS_TEMPLATE_TALK', 11 );
+define( 'NS_HELP', 12 );
+define( 'NS_HELP_TALK', 13 );
+define( 'NS_CATEGORY', 14 );
+define( 'NS_CATEGORY_TALK', 15 );
+
/**
* NS_IMAGE and NS_IMAGE_TALK are the pre-v1.14 names for NS_FILE and
* NS_FILE_TALK respectively, and are kept for compatibility.
@@ -71,9 +76,9 @@ define('NS_CATEGORY_TALK', 15);
* versions, either stick to the old names or define the new constants
* yourself, if they're not defined already.
*/
-define('NS_IMAGE', NS_FILE);
-define('NS_IMAGE_TALK', NS_FILE_TALK);
-/**#@-*/
+define( 'NS_IMAGE', NS_FILE );
+define( 'NS_IMAGE_TALK', NS_FILE_TALK );
+/**@}*/
/**
* Available feeds objects
@@ -85,7 +90,7 @@ $wgFeedClasses = array(
'atom' => 'AtomFeed',
);
-/**#@+
+/**@{
* Maths constants
*/
define( 'MW_MATH_PNG', 0 );
@@ -94,9 +99,9 @@ define( 'MW_MATH_HTML', 2 );
define( 'MW_MATH_SOURCE', 3 );
define( 'MW_MATH_MODERN', 4 );
define( 'MW_MATH_MATHML', 5 );
-/**#@-*/
+/**@}*/
-/**#@+
+/**@{
* Cache type
*/
define( 'CACHE_ANYTHING', -1 ); // Use anything, as long as it works
@@ -105,11 +110,9 @@ define( 'CACHE_DB', 1 ); // Store cache objects in the DB
define( 'CACHE_MEMCACHED', 2 ); // MemCached, must specify servers in $wgMemCacheServers
define( 'CACHE_ACCEL', 3 ); // eAccelerator
define( 'CACHE_DBA', 4 ); // Use PHP's DBA extension to store in a DBM-style database
-/**#@-*/
-
-
+/**@}*/
-/**#@+
+/**@{
* Media types.
* This defines constants for the value returned by Image::getMediaType()
*/
@@ -123,18 +126,18 @@ define( 'MEDIATYPE_OFFICE', 'OFFICE' ); // Office Documents, Spreadshee
define( 'MEDIATYPE_TEXT', 'TEXT' ); // Plain text (possibly containing program code or scripts)
define( 'MEDIATYPE_EXECUTABLE', 'EXECUTABLE' ); // binary executable
define( 'MEDIATYPE_ARCHIVE', 'ARCHIVE' ); // archive file (zip, tar, etc)
-/**#@-*/
+/**@}*/
-/**#@+
+/**@{
* Antivirus result codes, for use in $wgAntivirusSetup.
*/
define( 'AV_NO_VIRUS', 0 ); #scan ok, no virus found
define( 'AV_VIRUS_FOUND', 1 ); #virus found!
define( 'AV_SCAN_ABORTED', -1 ); #scan aborted, the file is probably imune
define( 'AV_SCAN_FAILED', false ); #scan failed (scanner not found or error in scanner)
-/**#@-*/
+/**@}*/
-/**#@+
+/**@{
* Anti-lock flags
* See DefaultSettings.php for a description
*/
@@ -142,9 +145,9 @@ define( 'ALF_PRELOAD_LINKS', 1 );
define( 'ALF_PRELOAD_EXISTENCE', 2 );
define( 'ALF_NO_LINK_LOCK', 4 );
define( 'ALF_NO_BLOCK_LOCK', 8 );
-/**#@-*/
+/**@}*/
-/**#@+
+/**@{
* Date format selectors; used in user preference storage and by
* Language::date() and co.
*/
@@ -158,9 +161,9 @@ define( 'MW_DATE_MDY', 'mdy' );
define( 'MW_DATE_DMY', 'dmy' );
define( 'MW_DATE_YMD', 'ymd' );
define( 'MW_DATE_ISO', 'ISO 8601' );
-/**#@-*/
+/**@}*/
-/**#@+
+/**@{
* RecentChange type identifiers
* This may be obsolete; log items are now used for moves?
*/
@@ -169,9 +172,9 @@ define( 'RC_NEW', 1);
define( 'RC_MOVE', 2);
define( 'RC_LOG', 3);
define( 'RC_MOVE_OVER_REDIRECT', 4);
-/**#@-*/
+/**@}*/
-/**#@+
+/**@{
* Article edit flags
*/
define( 'EDIT_NEW', 1 );
@@ -181,9 +184,9 @@ define( 'EDIT_SUPPRESS_RC', 8 );
define( 'EDIT_FORCE_BOT', 16 );
define( 'EDIT_DEFER_UPDATES', 32 );
define( 'EDIT_AUTOSUMMARY', 64 );
-/**#@-*/
+/**@}*/
-/**
+/**@{
* Flags for Database::makeList()
* These are also available as Database class constants
*/
@@ -192,36 +195,57 @@ define( 'LIST_AND', 1 );
define( 'LIST_SET', 2 );
define( 'LIST_NAMES', 3);
define( 'LIST_OR', 4);
+define( 'LIST_SET_PREPARED', 8); // List of (?, ?, ?) for DatabaseIbm_db2
+/**@}*/
/**
* Unicode and normalisation related
*/
require_once dirname(__FILE__).'/normal/UtfNormalDefines.php';
-# Hook support constants
+/**@{
+ * Hook support constants
+ */
define( 'MW_SUPPORTS_EDITFILTERMERGED', 1 );
define( 'MW_SUPPORTS_PARSERFIRSTCALLINIT', 1 );
define( 'MW_SUPPORTS_LOCALISATIONCACHE', 1 );
+/**@}*/
+
+/** Support for $wgResourceModules */
+define( 'MW_SUPPORTS_RESOURCE_MODULES', 1 );
-# Allowed values for Parser::$mOutputType
-# Parameter to Parser::startExternalParse().
+/**@{
+ * Allowed values for Parser::$mOutputType
+ * Parameter to Parser::startExternalParse().
+ */
define( 'OT_HTML', 1 );
define( 'OT_WIKI', 2 );
define( 'OT_PREPROCESS', 3 );
define( 'OT_MSG' , 3 ); // b/c alias for OT_PREPROCESS
+define( 'OT_PLAIN', 4 );
+/**@}*/
-# Flags for Parser::setFunctionHook
+/**@{
+ * Flags for Parser::setFunctionHook
+ */
define( 'SFH_NO_HASH', 1 );
define( 'SFH_OBJECT_ARGS', 2 );
+/**@}*/
-# Flags for Parser::setLinkHook
+/**
+ * Flags for Parser::setLinkHook
+ */
define( 'SLH_PATTERN', 1 );
-# Flags for Parser::replaceLinkHolders
+/**
+ * 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)
+/**@{
+ * Autopromote conditions (must be here and not in Autopromote.php, so that
+ * they're loaded for DefaultSettings.php before AutoLoader.php)
+ */
define( 'APCOND_EDITCOUNT', 1 );
define( 'APCOND_AGE', 2 );
define( 'APCOND_EMAILCONFIRMED', 3 );
@@ -230,3 +254,4 @@ define( 'APCOND_ISIP', 5 );
define( 'APCOND_IPINRANGE', 6 );
define( 'APCOND_AGE_FROM_EDIT', 7 );
define( 'APCOND_BLOCKED', 8 );
+/**@}*/
diff --git a/includes/DjVuImage.php b/includes/DjVuImage.php
index 75df0fd5..cccb070a 100644
--- a/includes/DjVuImage.php
+++ b/includes/DjVuImage.php
@@ -1,8 +1,8 @@
<?php
-
/**
+ * DjVu image handler
*
- * Copyright (C) 2006 Brion Vibber <brion@pobox.com>
+ * Copyright © 2006 Brion Vibber <brion@pobox.com>
* http://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
+ * @file
*/
/**
@@ -225,6 +226,8 @@ class DjVuImage {
*/
function retrieveMetaData() {
global $wgDjvuToXML, $wgDjvuDump, $wgDjvuTxt;
+ wfProfileIn( __METHOD__ );
+
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
@@ -247,28 +250,38 @@ class DjVuImage {
wfProfileIn( 'djvutxt' );
$cmd = wfEscapeShellArg( $wgDjvuTxt ) . ' --detail=page ' . wfEscapeShellArg( $this->mFilename ) ;
wfDebug( __METHOD__.": $cmd\n" );
+ $retval = '';
$txt = wfShellExec( $cmd, $retval );
wfProfileOut( 'djvutxt' );
if( $retval == 0) {
- # Get rid of invalid UTF-8, strip control characters
- if( is_callable( 'iconv' ) ) {
- wfSuppressWarnings();
- $txt = iconv( "UTF-8","UTF-8//IGNORE", $txt );
- wfRestoreWarnings();
- } else {
- $txt = UtfNormal::cleanUp( $txt );
- }
+ # Strip some control characters
$txt = preg_replace( "/[\013\035\037]/", "", $txt );
- $txt = htmlspecialchars($txt);
- $txt = preg_replace( "/\((page\s[\d-]*\s[\d-]*\s[\d-]*\s[\d-]*\s*\&quot;([^<]*?)\&quot;\s*|)\)/s", "<PAGE value=\"$2\" />", $txt );
+ $reg = <<<EOR
+ /\(page\s[\d-]*\s[\d-]*\s[\d-]*\s[\d-]*\s*"
+ ((?> # Text to match is composed of atoms of either:
+ \\\\. # - any escaped character
+ | # - any character different from " and \
+ [^"\\\\]+
+ )*?)
+ "\s*\)
+ | # Or page can be empty ; in this case, djvutxt dumps ()
+ \(\s*()\)/sx
+EOR;
+ $txt = preg_replace_callback( $reg, array( $this, 'pageTextCallback' ), $txt );
$txt = "<DjVuTxt>\n<HEAD></HEAD>\n<BODY>\n" . $txt . "</BODY>\n</DjVuTxt>\n";
$xml = preg_replace( "/<DjVuXML>/", "<mw-djvu><DjVuXML>", $xml );
$xml = $xml . $txt. '</mw-djvu>' ;
}
}
+ wfProfileOut( __METHOD__ );
return $xml;
}
+ function pageTextCallback( $matches ) {
+ # Get rid of invalid UTF-8, strip control characters
+ return '<PAGE value="' . htmlspecialchars( UtfNormal::cleanUp( $matches[1] ) ) . '" />';
+ }
+
/**
* Hack to temporarily work around djvutoxml bug
*/
diff --git a/includes/EditPage.php b/includes/EditPage.php
index b4cbf0de..3e85ad10 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -96,7 +96,7 @@ class EditPage {
* @todo document
* @param $article
*/
- function EditPage( $article ) {
+ function __construct( $article ) {
$this->mArticle =& $article;
$this->mTitle = $article->getTitle();
$this->action = 'submit';
@@ -129,21 +129,19 @@ class EditPage {
wfProfileIn( __METHOD__ );
# Get variables from query string :P
$section = $wgRequest->getVal( 'section' );
-
- $preload = $wgRequest->getVal( 'preload',
+
+ $preload = $wgRequest->getVal( 'preload',
// Custom preload text for new sections
$section === 'new' ? 'MediaWiki:addsection-preload' : '' );
$undoafter = $wgRequest->getVal( 'undoafter' );
$undo = $wgRequest->getVal( 'undo' );
- $text = '';
// For message page not locally set, use the i18n message.
// For other non-existent articles, use preload text if any.
if ( !$this->mTitle->exists() ) {
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
# If this is a system message, get the default text.
list( $message, $lang ) = $wgMessageCache->figureMessage( $wgContLang->lcfirst( $this->mTitle->getText() ) );
- $wgMessageCache->loadAllMessages( $lang );
$text = wfMsgGetKey( $message, false, $lang, false );
if( wfEmptyMsg( $message, $text ) )
$text = $this->getPreloadedText( $preload );
@@ -220,30 +218,39 @@ class EditPage {
}
/**
- * Get the contents of a page from its title and remove includeonly tags
+ * Get the contents to be preloaded into the box, either set by
+ * an earlier setPreloadText() or by loading the given page.
*
- * @param $preload String: the title of the page.
- * @return string The contents of the page.
+ * @param $preload String: representing the title to preload from.
+ * @return String
*/
protected function getPreloadedText( $preload ) {
+ global $wgUser, $wgParser;
if ( !empty( $this->mPreloadText ) ) {
return $this->mPreloadText;
- } elseif ( $preload === '' ) {
- return '';
- } else {
- $preloadTitle = Title::newFromText( $preload );
- if ( isset( $preloadTitle ) && $preloadTitle->userCanRead() ) {
- $rev = Revision::newFromTitle( $preloadTitle );
- if ( is_object( $rev ) ) {
- $text = $rev->getText();
- // TODO FIXME: AAAAAAAAAAA, this shouldn't be implementing
- // its own mini-parser! -ævar
- $text = preg_replace( '~</?includeonly>~', '', $text );
- return $text;
- } else
- return '';
+ } elseif ( $preload !== '' ) {
+ $title = Title::newFromText( $preload );
+ # Check for existence to avoid getting MediaWiki:Noarticletext
+ if ( isset( $title ) && $title->exists() && $title->userCanRead() ) {
+ $article = new Article( $title );
+
+ if ( $article->isRedirect() ) {
+ $title = Title::newFromRedirectRecurse( $article->getContent() );
+ # Redirects to missing titles are displayed, to hidden pages are followed
+ # Copying observed behaviour from ?action=view
+ if ( $title->exists() ) {
+ if ($title->userCanRead() ) {
+ $article = new Article( $title );
+ } else {
+ return "";
+ }
+ }
+ }
+ $parserOptions = ParserOptions::newFromUser( $wgUser );
+ return $wgParser->getPreloadText( $article->getContent(), $title, $parserOptions );
}
}
+ return '';
}
/*
@@ -267,6 +274,24 @@ class EditPage {
return $this->deletedSinceEdit;
}
+ /**
+ * Checks whether the user entered a skin name in uppercase,
+ * e.g. "User:Example/Monobook.css" instead of "monobook.css"
+ */
+ protected function isWrongCaseCssJsPage() {
+ if( $this->mTitle->isCssJsSubpage() ) {
+ $name = $this->mTitle->getSkinFromCssJsSubpage();
+ $skins = array_merge(
+ array_keys( Skin::getSkinNames() ),
+ array( 'common' )
+ );
+ return !in_array( $name, $skins )
+ && in_array( strtolower( $name ), $skins );
+ } else {
+ return false;
+ }
+ }
+
function submit() {
$this->edit();
}
@@ -310,11 +335,10 @@ class EditPage {
$this->preview = true;
}
- $wgOut->addScriptFile( 'edit.js' );
-
+ $wgOut->addModules( array( 'mediawiki.legacy.edit', 'mediawiki.action.edit' ) );
+
if ( $wgUser->getOption( 'uselivepreview', false ) ) {
- $wgOut->includeJQuery();
- $wgOut->addScriptFile( 'preview.js' );
+ $wgOut->addModules( 'mediawiki.legacy.preview' );
}
// Bug #19334: textarea jumps when editing articles in IE8
$wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
@@ -322,7 +346,9 @@ class EditPage {
$permErrors = $this->getEditPermissionErrors();
if ( $permErrors ) {
wfDebug( __METHOD__ . ": User can't edit\n" );
- $this->readOnlyPage( $this->getContent( false ), true, $permErrors, 'edit' );
+ $content = $this->getContent( null );
+ $content = $content === '' ? null : $content;
+ $this->readOnlyPage( $content, true, $permErrors, 'edit' );
wfProfileOut( __METHOD__ );
return;
} else {
@@ -354,7 +380,7 @@ class EditPage {
$this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
$this->isCssSubpage = $this->mTitle->isCssSubpage();
$this->isJsSubpage = $this->mTitle->isJsSubpage();
- $this->isValidCssJsSubpage = $this->mTitle->isValidCssJsSubpage();
+ $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
# Show applicable editing introductions
if ( $this->formtype == 'initial' || $this->firsttime )
@@ -484,7 +510,7 @@ class EditPage {
/**
* Does this EditPage class support section editing?
* This is used by EditPage subclasses to indicate their ui cannot handle section edits
- *
+ *
* @return bool
*/
protected function isSectionEditSupported() {
@@ -496,8 +522,8 @@ class EditPage {
* This is used by EditPage subclasses when simply customizing the action
* variable in the constructor is not enough. This can be used when the
* EditPage lives inside of a Special page rather than a custom page action.
- *
- * @param Title $title The title for which is being edited (where we go to for &action= links)
+ *
+ * @param $title Title object for which is being edited (where we go to for &action= links)
* @return string
*/
protected function getActionURL( Title $title ) {
@@ -635,10 +661,10 @@ class EditPage {
// Custom edit intro for new sections
$this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
- wfProfileOut( __METHOD__ );
-
// Allow extensions to modify form data
wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
+
+ wfProfileOut( __METHOD__ );
}
/**
@@ -646,8 +672,8 @@ class EditPage {
* posted form to be placed in $this->textbox1, if using customized input
* this method should be overrided and return the page text that will be used
* for saving, preview parsing and so on...
- *
- * @praram WebRequest $request
+ *
+ * @param $request WebRequest
*/
protected function importContentFormData( &$request ) {
return; // Don't do anything, EditPage already extracted wpTextbox1
@@ -681,7 +707,7 @@ class EditPage {
if ( $namespace == NS_MEDIAWIKI ) {
# Show a warning if editing an interface message
- $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1</div>", 'editinginterface' );
+ $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
}
# Show a warning message when someone creates/edits a user (talk) page but the user does not exist
@@ -692,7 +718,7 @@ class EditPage {
$user = User::newFromName( $username, false /* allow IP users*/ );
$ip = User::isIP( $username );
if ( !$user->isLoggedIn() && !$ip ) { # User does not exist
- $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1</div>",
+ $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
array( 'userpage-userdoesnotexist', $username ) );
} else if ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
LogEventsList::showLogExtract(
@@ -714,9 +740,9 @@ class EditPage {
# Try to add a custom edit intro, or use the standard one if this is not possible.
if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
if ( $wgUser->isLoggedIn() ) {
- $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletext\">\n$1</div>", 'newarticletext' );
+ $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletext\">\n$1\n</div>", 'newarticletext' );
} else {
- $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletextanon\">\n$1</div>", 'newarticletextanon' );
+ $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletextanon\">\n$1\n</div>", 'newarticletextanon' );
}
}
# Give a notice if the user is editing a deleted/moved page...
@@ -756,7 +782,7 @@ class EditPage {
* @return one of the constants describing the result
*/
function internalAttemptSave( &$result, $bot = false ) {
- global $wgFilterCallback, $wgUser, $wgOut, $wgParser;
+ global $wgFilterCallback, $wgUser, $wgParser;
global $wgMaxArticleSize;
wfProfileIn( __METHOD__ );
@@ -764,6 +790,8 @@ class EditPage {
if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
+ wfProfileOut( __METHOD__ . '-checks' );
+ wfProfileOut( __METHOD__ );
return self::AS_HOOK_ERROR;
}
@@ -771,11 +799,12 @@ class EditPage {
if ( $this->mTitle->getNamespace() == NS_FILE &&
Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
!$wgUser->isAllowed( 'upload' ) ) {
- if ( $wgUser->isAnon() ) {
- return self::AS_IMAGE_REDIRECT_ANON;
- } else {
- return self::AS_IMAGE_REDIRECT_LOGGED;
- }
+ $isAnon = $wgUser->isAnon();
+
+ wfProfileOut( __METHOD__ . '-checks' );
+ wfProfileOut( __METHOD__ );
+
+ return $isAnon ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
}
# Check for spam
@@ -859,7 +888,7 @@ class EditPage {
wfProfileOut( __METHOD__ . '-checks' );
# If article is new, insert it.
- $aid = $this->mTitle->getArticleID( GAID_FOR_UPDATE );
+ $aid = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
if ( 0 == $aid ) {
// Late check for create permission, just in case *PARANOIA*
if ( !$this->mTitle->userCan( 'create' ) ) {
@@ -1008,7 +1037,7 @@ class EditPage {
return self::AS_TEXTBOX_EMPTY;
}
if ( $this->summary != '' ) {
- $sectionanchor = $wgParser->guessSectionNameFromWikiText( $this->summary );
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
# This is a new section, so create a link to the new section
# in the revision summary.
$cleanSummary = $wgParser->stripSectionName( $this->summary );
@@ -1022,7 +1051,7 @@ class EditPage {
# we can't deal with anchors, includes, html etc in the header for now,
# headline would need to be parsed to improve this
if ( $hasmatch and strlen( $matches[2] ) > 0 ) {
- $sectionanchor = $wgParser->guessSectionNameFromWikiText( $matches[2] );
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
}
}
wfProfileOut( __METHOD__ . '-sectionanchor' );
@@ -1071,7 +1100,7 @@ class EditPage {
),
__METHOD__,
array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ) );
- while( $row = $res->fetchObject() ) {
+ foreach ( $res as $row ) {
if( $row->rev_user != $id ) {
return false;
}
@@ -1166,7 +1195,7 @@ class EditPage {
* parameter; will be called during form output
* near the top, for captchas and the like.
*/
- function showEditForm( $formCallback=null ) {
+ function showEditForm( $formCallback = null ) {
global $wgOut, $wgUser, $wgTitle;
# If $wgTitle is null, that means we're in API mode.
@@ -1196,10 +1225,12 @@ class EditPage {
# Enabled article-related sidebar, toplinks, etc.
$wgOut->setArticleRelated( true );
- if ( $this->showHeader() === false )
+ if ( $this->showHeader() === false ) {
+ wfProfileOut( __METHOD__ );
return;
+ }
- $action = htmlspecialchars($this->getActionURL($wgTitle));
+ $action = htmlspecialchars( $this->getActionURL( $wgTitle ) );
if ( $wgUser->getOption( 'showtoolbar' ) and !$this->isCssJsSubpage ) {
# prepare toolbar for edit buttons
@@ -1225,7 +1256,7 @@ class EditPage {
if ( $this->wasDeletedSinceLastEdit() && 'save' != $this->formtype ) {
$wgOut->wrapWikiMsg(
- "<div class='error mw-deleted-while-editing'>\n$1</div>",
+ "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
'deletedwhileediting' );
} elseif ( $this->wasDeletedSinceLastEdit() ) {
// Hide the toolbar and edit area, user can click preview to get it back
@@ -1268,11 +1299,11 @@ HTML
# automatic one and pass that in the hidden field wpAutoSummary.
if ( $this->missingSummary ||
( $this->section == 'new' && $this->nosummary ) )
- $wgOut->addHTML( Xml::hidden( 'wpIgnoreBlankSummary', true ) );
+ $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
$autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
- $wgOut->addHTML( Xml::hidden( 'wpAutoSummary', $autosumm ) );
+ $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
- $wgOut->addHTML( Xml::hidden( 'oldid', $this->mArticle->getOldID() ) );
+ $wgOut->addHTML( Html::hidden( 'oldid', $this->mArticle->getOldID() ) );
if ( $this->section == 'new' ) {
$this->showSummaryInput( true, $this->summary );
@@ -1280,7 +1311,7 @@ HTML
}
$wgOut->addHTML( $this->editFormTextBeforeContent );
-
+
if ( $this->isConflict ) {
// In an edit conflict bypass the overrideable content form method
// and fallback to the raw wpTextbox1 since editconflicts can't be
@@ -1317,7 +1348,7 @@ HTML
if ( $this->isConflict )
$this->showConflict();
-
+
$wgOut->addHTML( $this->editFormTextBottom );
$wgOut->addHTML( "</form>\n" );
if ( !$wgUser->getOption( 'previewontop' ) ) {
@@ -1326,11 +1357,11 @@ HTML
wfProfileOut( __METHOD__ );
}
-
+
protected function showHeader() {
global $wgOut, $wgUser, $wgTitle, $wgMaxArticleSize, $wgLang;
if ( $this->isConflict ) {
- $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1</div>", 'explainconflict' );
+ $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
$this->edittime = $this->mArticle->getTimestamp();
} else {
if ( $this->section != '' && !$this->isSectionEditSupported() ) {
@@ -1355,15 +1386,15 @@ HTML
}
if ( $this->missingComment ) {
- $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1</div>", 'missingcommenttext' );
+ $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
}
if ( $this->missingSummary && $this->section != 'new' ) {
- $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1</div>", 'missingsummary' );
+ $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
}
if ( $this->missingSummary && $this->section == 'new' ) {
- $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1</div>", 'missingcommentheader' );
+ $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
}
if ( $this->hookError !== '' ) {
@@ -1378,9 +1409,9 @@ HTML
// Let sysop know that this will make private content public if saved
if ( !$this->mArticle->mRevision->userCan( Revision::DELETED_TEXT ) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' );
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
} else if ( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' );
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
}
if ( !$this->mArticle->mRevision->isCurrent() ) {
@@ -1392,19 +1423,23 @@ HTML
if ( wfReadOnly() ) {
$wgOut->wrapWikiMsg( "<div id=\"mw-read-only-warning\">\n$1\n</div>", array( 'readonlywarning', wfReadOnlyReason() ) );
- } elseif ( $wgUser->isAnon() && $this->formtype != 'preview' ) {
- $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' );
+ } elseif ( $wgUser->isAnon() ) {
+ if ( $this->formtype != 'preview' ) {
+ $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' );
+ } else {
+ $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 'anonpreviewwarning' );
+ }
} else {
if ( $this->isCssJsSubpage ) {
# Check the skin exists
- if ( !$this->isValidCssJsSubpage ) {
- $wgOut->addWikiMsg( 'userinvalidcssjstitle', $wgTitle->getSkinFromCssJsSubpage() );
+ if ( $this->isWrongCaseCssJsPage ) {
+ $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $wgTitle->getSkinFromCssJsSubpage() ) );
}
if ( $this->formtype !== 'preview' ) {
if ( $this->isCssSubpage )
- $wgOut->addWikiMsg( 'usercssyoucanpreview' );
+ $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
if ( $this->isJsSubpage )
- $wgOut->addWikiMsg( 'userjsyoucanpreview' );
+ $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
}
}
}
@@ -1447,13 +1482,16 @@ HTML
}
if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
- $wgOut->addHTML( "<div class='error' id='mw-edit-longpageerror'>\n" );
- $wgOut->addWikiMsg( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) );
- $wgOut->addHTML( "</div>\n" );
- } elseif ( $this->kblength > 29 ) {
- $wgOut->addHTML( "<div id='mw-edit-longpagewarning'>\n" );
- $wgOut->addWikiMsg( 'longpagewarning', $wgLang->formatNum( $this->kblength ) );
- $wgOut->addHTML( "</div>\n" );
+ $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
+ array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
+ } else {
+ $msg = 'longpage-hint';
+ $text = wfMsg( $msg );
+ if( !wfEmptyMsg( $msg, $text ) && $text !== '-' ) {
+ $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
+ array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
+ );
+ }
}
}
@@ -1463,23 +1501,25 @@ HTML
* Note that you do not need to worry about the label's for=, it will be
* inferred by the id given to the input. You can remove them both by
* passing array( 'id' => false ) to $userInputAttrs.
- *
+ *
* @param $summary The value of the summary input
* @param $labelText The html to place inside the label
- * @param $userInputAttrs An array of attrs to use on the input
- * @param $userSpanAttrs An array of attrs to use on the span inside the label
- *
+ * @param $inputAttrs An array of attrs to use on the input
+ * @param $spanLabelAttrs An array of attrs to use on the span inside the label
+ *
* @return array An array in the format array( $label, $input )
*/
function getSummaryInput($summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null) {
+ global $wgUser;
+ //Note: the maxlength is overriden in JS to 250 and to make it use UTF-8 bytes, not characters.
$inputAttrs = ( is_array($inputAttrs) ? $inputAttrs : array() ) + array(
'id' => 'wpSummary',
'maxlength' => '200',
'tabindex' => '1',
'size' => 60,
'spellcheck' => 'true',
- );
-
+ ) + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'summary' );
+
$spanLabelAttrs = ( is_array($spanLabelAttrs) ? $spanLabelAttrs : array() ) + array(
'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
'id' => "wpSummaryLabel"
@@ -1497,11 +1537,11 @@ HTML
}
/**
- * @param bool $isSubjectPreview true if this is the section subject/title
- * up top, or false if this is the comment
- * summary down below the textarea
- * @param string $summary The text of the summary to display
- * @return string
+ * @param $isSubjectPreview Boolean: true if this is the section subject/title
+ * up top, or false if this is the comment summary
+ * down below the textarea
+ * @param $summary String: The text of the summary to display
+ * @return String
*/
protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
global $wgOut, $wgContLang;
@@ -1521,41 +1561,43 @@ HTML
}
/**
- * @param bool $isSubjectPreview true if this is the section subject/title
- * up top, or false if this is the comment
- * summary down below the textarea
- * @param string $summary The text of the summary to display
- * @return string
+ * @param $isSubjectPreview Boolean: true if this is the section subject/title
+ * up top, or false if this is the comment summary
+ * down below the textarea
+ * @param $summary String: the text of the summary to display
+ * @return String
*/
protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
if ( !$summary || ( !$this->preview && !$this->diff ) )
return "";
-
+
global $wgParser, $wgUser;
$sk = $wgUser->getSkin();
-
+
if ( $isSubjectPreview )
$summary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $summary ) );
- $summary = wfMsgExt( 'subject-preview', 'parseinline' ) . $sk->commentBlock( $summary, $this->mTitle, !!$isSubjectPreview );
+ $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
+
+ $summary = wfMsgExt( $message, 'parseinline' ) . $sk->commentBlock( $summary, $this->mTitle, $isSubjectPreview );
return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
}
protected function showFormBeforeText() {
global $wgOut;
$section = htmlspecialchars( $this->section );
- $wgOut->addHTML( <<<INPUTS
+ $wgOut->addHTML( <<<HTML
<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" />
-INPUTS
+HTML
);
if ( !$this->checkUnicodeCompliantBrowser() )
- $wgOut->addHTML(Xml::hidden( 'safemode', '1' ));
+ $wgOut->addHTML(Html::hidden( 'safemode', '1' ));
}
-
+
protected function showFormAfterText() {
global $wgOut, $wgUser;
/**
@@ -1570,7 +1612,7 @@ INPUTS
* include the constant suffix to prevent editing from
* broken text-mangling proxies.
*/
- $wgOut->addHTML( "\n" . Xml::hidden( "wpEditToken", $wgUser->editToken() ) . "\n" );
+ $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->editToken() ) . "\n" );
}
/**
@@ -1580,8 +1622,6 @@ INPUTS
* be it a form, or simply wpTextbox1 with a modified content that will be
* reverse modified when extracted from the post data.
* Note that this is basically the inverse for importContentFormData
- *
- * @praram WebRequest $request
*/
protected function showContentForm() {
$this->showTextbox1();
@@ -1591,9 +1631,9 @@ INPUTS
* Method to output wpTextbox1
* The $textoverride method can be used by subclasses overriding showContentForm
* to pass back to this method.
- *
- * @param array $customAttribs An array of html attributes to use in the textarea
- * @param string $textoverride Optional text to override $this->textarea1 with
+ *
+ * @param $customAttribs An array of html attributes to use in the textarea
+ * @param $textoverride String: optional text to override $this->textarea1 with
*/
protected function showTextbox1($customAttribs = null, $textoverride = null) {
$classes = array(); // Textarea CSS
@@ -1617,7 +1657,7 @@ INPUTS
$classes[] = $attribs['class'];
$attribs['class'] = implode( ' ', $classes );
}
-
+
$this->showTextbox( isset($textoverride) ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
}
@@ -1640,14 +1680,11 @@ INPUTS
$attribs = $customAttribs + array(
'accesskey' => ',',
'id' => $name,
- 'cols' => $wgUser->getIntOption( 'cols' ),
+ 'cols' => $wgUser->getIntOption( 'cols' ),
'rows' => $wgUser->getIntOption( 'rows' ),
- 'style' => '' // avoid php notices when appending for editwidth preference (appending allows customAttribs['style'] to still work
+ 'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
);
- if ( $wgUser->getOption( 'editwidth' ) )
- $attribs['style'] .= 'width: 100%';
-
$wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
}
@@ -1679,7 +1716,7 @@ INPUTS
* Append preview output to $wgOut.
* Includes category rendering if this is a category page.
*
- * @param string $text The HTML to be output for the preview.
+ * @param $text String: the HTML to be output for the preview.
*/
protected function showPreview( $text ) {
global $wgOut;
@@ -1695,14 +1732,16 @@ INPUTS
}
}
+ /**
+ * Give a chance for site and per-namespace customizations of
+ * terms of service summary link that might exist separately
+ * from the copyright notice.
+ *
+ * This will display between the save button and the edit tools,
+ * so should remain short!
+ */
protected function showTosSummary() {
$msg = 'editpage-tos-summary';
- // Give a chance for site and per-namespace customizations of
- // terms of service summary link that might exist separately
- // from the copyright notice.
- //
- // This will display between the save button and the edit tools,
- // so should remain short!
wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
$text = wfMsg( $msg );
if( !wfEmptyMsg( $msg, $text ) && $text !== '-' ) {
@@ -1719,7 +1758,7 @@ INPUTS
$wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) );
$wgOut->addHTML( '</div>' );
}
-
+
protected function getCopywarn() {
global $wgRightsText;
if ( $wgRightsText ) {
@@ -1732,10 +1771,10 @@ INPUTS
}
// Allow for site and per-namespace customization of contribution/copyright notice.
wfRunHooks( 'EditPageCopyrightWarning', array( $this->mTitle, &$copywarnMsg ) );
-
+
return "<div id=\"editpage-copywarn\">\n" . call_user_func_array("wfMsgNoTrans", $copywarnMsg) . "\n</div>";
}
-
+
protected function showStandardInputs( &$tabindex = 2 ) {
global $wgOut, $wgUser;
$wgOut->addHTML( "<div class='editOptions'>\n" );
@@ -1818,7 +1857,7 @@ INPUTS
* @return string
*/
function getPreviewText() {
- global $wgOut, $wgUser, $wgTitle, $wgParser, $wgLang, $wgContLang, $wgMessageCache;
+ global $wgOut, $wgUser, $wgParser, $wgMessageCache;
wfProfileIn( __METHOD__ );
@@ -1841,54 +1880,71 @@ INPUTS
if ( $wgRawHtml && !$this->mTokenOk ) {
// Could be an offsite preview attempt. This is very unsafe if
// HTML is enabled, as it could be an attack.
- return $wgOut->parse( "<div class='previewnote'>" .
+ $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
wfMsg( 'session_fail_preview_html' ) . "</div>" );
+ wfProfileOut( __METHOD__ );
+ return $parsedNote;
}
# don't parse user css/js, show message about preview
- # XXX: stupid php bug won't let us use $wgTitle->isCssJsSubpage() here
+ # XXX: stupid php bug won't let us use $wgTitle->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago?
+
+ if ( $this->isCssJsSubpage || $this->mTitle->isCssOrJsPage() ) {
+ $level = 'user';
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ $level = 'site';
+ }
- if ( $this->isCssJsSubpage ) {
+ # Used messages to make sure grep find them:
+ # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
if (preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
- $previewtext = wfMsg( 'usercsspreview' );
- } else if (preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
- $previewtext = wfMsg( 'userjspreview' );
+ $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
+ $class = "mw-code mw-css";
+ } elseif (preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
+ $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMsg( "{$level}jspreview" ) . "\n</div>";
+ $class = "mw-code mw-js";
+ } else {
+ throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
}
+
$parserOptions->setTidy( true );
$parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
$previewHTML = $parserOutput->mText;
- } elseif ( $rt = Title::newFromRedirectArray( $this->textbox1 ) ) {
- $previewHTML = $this->mArticle->viewRedirect( $rt, false );
+ $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
} else {
- $toparse = $this->textbox1;
+ $rt = Title::newFromRedirectArray( $this->textbox1 );
+ if ( $rt ) {
+ $previewHTML = $this->mArticle->viewRedirect( $rt, false );
+ } else {
+ $toparse = $this->textbox1;
- # If we're adding a comment, we need to show the
- # summary as the headline
- if ( $this->section == "new" && $this->summary != "" ) {
- $toparse = "== {$this->summary} ==\n\n" . $toparse;
- }
+ # If we're adding a comment, we need to show the
+ # summary as the headline
+ if ( $this->section == "new" && $this->summary != "" ) {
+ $toparse = "== {$this->summary} ==\n\n" . $toparse;
+ }
- wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
-
- // Parse mediawiki messages with correct target language
- if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- list( /* $unused */, $lang ) = $wgMessageCache->figureMessage( $this->mTitle->getText() );
- $obj = wfGetLangObj( $lang );
- $parserOptions->setTargetLanguage( $obj );
- }
+ wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
+ // Parse mediawiki messages with correct target language
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ list( /* $unused */, $lang ) = $wgMessageCache->figureMessage( $this->mTitle->getText() );
+ $obj = wfGetLangObj( $lang );
+ $parserOptions->setTargetLanguage( $obj );
+ }
- $parserOptions->setTidy( true );
- $parserOptions->enableLimitReport();
- $parserOutput = $wgParser->parse( $this->mArticle->preSaveTransform( $toparse ),
+ $parserOptions->setTidy( true );
+ $parserOptions->enableLimitReport();
+ $parserOutput = $wgParser->parse( $this->mArticle->preSaveTransform( $toparse ),
$this->mTitle, $parserOptions );
- $previewHTML = $parserOutput->getText();
- $this->mParserOutput = $parserOutput;
- $wgOut->addParserOutputNoText( $parserOutput );
+ $previewHTML = $parserOutput->getText();
+ $this->mParserOutput = $parserOutput;
+ $wgOut->addParserOutputNoText( $parserOutput );
- if ( count( $parserOutput->getWarnings() ) ) {
- $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ if ( count( $parserOutput->getWarnings() ) ) {
+ $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ }
}
}
@@ -1991,8 +2047,9 @@ INPUTS
* Produce the stock "your edit contains spam" page
*
* @param $match Text which triggered one or more filters
+ * @deprecated Use method spamPageWithContent() instead
*/
- function spamPage( $match = false ) {
+ static function spamPage( $match = false ) {
global $wgOut, $wgTitle;
$wgOut->setPageTitle( wfMsg( 'spamprotectiontitle' ) );
@@ -2001,14 +2058,47 @@ INPUTS
$wgOut->addHTML( '<div id="spamprotected">' );
$wgOut->addWikiMsg( 'spamprotectiontext' );
- if ( $match )
+ if ( $match ) {
$wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
+ }
$wgOut->addHTML( '</div>' );
$wgOut->returnToMain( false, $wgTitle );
}
/**
+ * Show "your edit contains spam" page with your diff and text
+ *
+ * @param $match Text which triggered one or more filters
+ */
+ public function spamPageWithContent( $match = false ) {
+ global $wgOut, $wgTitle;
+ $this->textbox2 = $this->textbox1;
+
+ $wgOut->setPageTitle( wfMsg( 'spamprotectiontitle' ) );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
+ $wgOut->setArticleRelated( false );
+
+ $wgOut->addHTML( '<div id="spamprotected">' );
+ $wgOut->addWikiMsg( 'spamprotectiontext' );
+ if ( $match ) {
+ $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
+ }
+ $wgOut->addHTML( '</div>' );
+
+ $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
+ $de = new DifferenceEngine( $this->mTitle );
+ $de->setText( $this->getContent(), $this->textbox2 );
+ $de->showDiff( wfMsg( "storedversion" ), wfMsg( "yourtext" ) );
+
+ $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
+ $this->showTextbox2();
+
+ $wgOut->addReturnTo( $wgTitle, array( 'action' => 'edit' ) );
+ }
+
+
+ /**
* @private
* @todo document
*/
@@ -2076,8 +2166,8 @@ INPUTS
/**
* Format an anchor fragment as it would appear for a given section name
- * @param string $text
- * @return string
+ * @param $text String
+ * @return String
* @private
*/
function sectionAnchor( $text ) {
@@ -2093,7 +2183,10 @@ INPUTS
* @return string
*/
static function getEditToolbar() {
- global $wgStylePath, $wgContLang, $wgLang;
+ global $wgStylePath, $wgContLang, $wgLang, $wgOut;
+ global $wgUseTeX, $wgEnableUploads, $wgForeignFileRepos;
+
+ $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
/**
@@ -2153,7 +2246,7 @@ INPUTS
'tip' => wfMsg( 'headline_tip' ),
'key' => 'H'
),
- array(
+ $imagesAvailable ? array(
'image' => $wgLang->getImageFile( 'button-image' ),
'id' => 'mw-editbutton-image',
'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
@@ -2161,8 +2254,8 @@ INPUTS
'sample' => wfMsg( 'image_sample' ),
'tip' => wfMsg( 'image_tip' ),
'key' => 'D'
- ),
- array(
+ ) : false,
+ $imagesAvailable ? array(
'image' => $wgLang->getImageFile( 'button-media' ),
'id' => 'mw-editbutton-media',
'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
@@ -2170,8 +2263,8 @@ INPUTS
'sample' => wfMsg( 'media_sample' ),
'tip' => wfMsg( 'media_tip' ),
'key' => 'M'
- ),
- array(
+ ) : false,
+ $wgUseTeX ? array(
'image' => $wgLang->getImageFile( 'button-math' ),
'id' => 'mw-editbutton-math',
'open' => "<math>",
@@ -2179,7 +2272,7 @@ INPUTS
'sample' => wfMsg( 'math_sample' ),
'tip' => wfMsg( 'math_tip' ),
'key' => 'C'
- ),
+ ) : false,
array(
'image' => $wgLang->getImageFile( 'button-nowiki' ),
'id' => 'mw-editbutton-nowiki',
@@ -2212,6 +2305,10 @@ INPUTS
$script = '';
foreach ( $toolarray as $tool ) {
+ if ( !$tool ) {
+ continue;
+ }
+
$params = array(
$image = $wgStylePath . '/common/images/' . $tool['image'],
// Note that we use the tip both for the ALT tag and the TITLE tag of the image.
@@ -2229,8 +2326,11 @@ INPUTS
array_map( array( 'Xml', 'encodeJsVar' ), $params ) );
$script .= "addButton($paramList);\n";
}
- $toolbar .= Html::inlineScript( "\n$script\n" );
-
+
+ $wgOut->addScript( Html::inlineScript(
+ "if ( window.mediaWiki ) { $script }"
+ ) );
+
$toolbar .= "\n</div>";
wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
@@ -2264,7 +2364,9 @@ INPUTS
);
$checkboxes['minor'] =
Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
- "&nbsp;<label for='wpMinoredit'" . $skin->tooltip( 'minoredit', 'withaccess' ) . ">{$minorLabel}</label>";
+ "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
+ Xml::expandAttributes( array( 'title' => $skin->titleAttrib( 'minoredit', 'withaccess' ) ) ) .
+ ">{$minorLabel}</label>";
}
$watchLabel = wfMsgExt( 'watchthis', array( 'parseinline' ) );
@@ -2277,7 +2379,9 @@ INPUTS
);
$checkboxes['watch'] =
Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
- "&nbsp;<label for='wpWatchthis'" . $skin->tooltip( 'watch', 'withaccess' ) . ">{$watchLabel}</label>";
+ "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
+ Xml::expandAttributes( array( 'title' => $skin->titleAttrib( 'watch', 'withaccess' ) ) ) .
+ ">{$watchLabel}</label>";
}
wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
return $checkboxes;
@@ -2291,7 +2395,7 @@ INPUTS
*
* @return array
*/
- public function getEditButtons(&$tabindex) {
+ public function getEditButtons( &$tabindex ) {
$buttons = array();
$temp = array(
@@ -2415,9 +2519,9 @@ INPUTS
* Filter an input field through a Unicode de-armoring process if it
* came from an old browser with known broken Unicode editing issues.
*
- * @param WebRequest $request
- * @param string $field
- * @return string
+ * @param $request WebRequest
+ * @param $field String
+ * @return String
* @private
*/
function safeUnicodeInput( $request, $field ) {
@@ -2438,8 +2542,8 @@ INPUTS
* Filter an output field through a Unicode armoring process if it is
* going to an old browser with known broken Unicode editing issues.
*
- * @param string $text
- * @return string
+ * @param $text String
+ * @return String
* @private
*/
function safeUnicodeOutput( $text ) {
@@ -2459,8 +2563,8 @@ INPUTS
* Preexisting such character references will have a 0 added to them
* to ensure that round-trips do not alter the original data.
*
- * @param string $invalue
- * @return string
+ * @param $invalue String
+ * @return String
* @private
*/
function makesafe( $invalue ) {
@@ -2501,8 +2605,8 @@ INPUTS
* back to UTF-8. Used to protect data from corruption by broken web browsers
* as listed in $wgBrowserBlackList.
*
- * @param string $invalue
- * @return string
+ * @param $invalue String
+ * @return String
* @private
*/
function unmakesafe( $invalue ) {
@@ -2573,7 +2677,7 @@ INPUTS
return false;
case self::AS_SPAM_ERROR:
- $this->spamPage( $resultDetails['spam'] );
+ $this->spamPageWithContent( $resultDetails['spam'] );
return false;
case self::AS_BLOCKED_PAGE_FOR_USER:
@@ -2612,7 +2716,7 @@ INPUTS
}
function getBaseRevision() {
- if ( $this->mBaseRevision == false ) {
+ if ( !$this->mBaseRevision ) {
$db = wfGetDB( DB_MASTER );
$baseRevision = Revision::loadFromTimestamp(
$db, $this->mTitle, $this->edittime );
@@ -2620,5 +2724,5 @@ INPUTS
} else {
return $this->mBaseRevision;
}
- }
+ }
}
diff --git a/includes/Exception.php b/includes/Exception.php
index f6bc6f87..ff5d4b19 100644
--- a/includes/Exception.php
+++ b/includes/Exception.php
@@ -1,10 +1,17 @@
<?php
/**
+ * Exception class and handler
+ *
+ * @file
+ */
+
+/**
* @defgroup Exception Exception
*/
/**
* MediaWiki exception
+ *
* @ingroup Exception
*/
class MWException extends Exception {
@@ -25,37 +32,45 @@ class MWException extends Exception {
*/
function useMessageCache() {
global $wgLang;
+
foreach ( $this->getTrace() as $frame ) {
if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
return false;
}
}
+
return is_object( $wgLang );
}
/**
* Run hook to allow extensions to modify the text of the exception
*
- * @param String $name class name of the exception
- * @param Array $args arguments to pass to the callback functions
- * @return mixed string to output or null if any hook has been called
+ * @param $name String: class name of the exception
+ * @param $args Array: arguments to pass to the callback functions
+ * @return Mixed: string to output or null if any hook has been called
*/
function runHooks( $name, $args = array() ) {
global $wgExceptionHooks;
- if( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) )
+
+ if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
return; // Just silently ignore
- if( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) )
+ }
+
+ if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) ) {
return;
+ }
+
$hooks = $wgExceptionHooks[ $name ];
$callargs = array_merge( array( $this ), $args );
- foreach( $hooks as $hook ) {
- if( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) { //'function' or array( 'class', hook' )
+ foreach ( $hooks as $hook ) {
+ if ( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) { // 'function' or array( 'class', hook' )
$result = call_user_func_array( $hook, $callargs );
} else {
$result = null;
}
- if( is_string( $result ) )
+
+ if ( is_string( $result ) )
return $result;
}
}
@@ -63,14 +78,15 @@ class MWException extends Exception {
/**
* Get a message from i18n
*
- * @param String $key message name
- * @param String $fallback default message if the message cache can't be
- * called by the exception
+ * @param $key String: message name
+ * @param $fallback String: default message if the message cache can't be
+ * called by the exception
* The function also has other parameters that are arguments for the message
* @return String message with arguments replaced
*/
function msg( $key, $fallback /*[, params...] */ ) {
$args = array_slice( func_get_args(), 2 );
+
if ( $this->useMessageCache() ) {
return wfMsgReal( $key, $args );
} else {
@@ -87,7 +103,8 @@ class MWException extends Exception {
*/
function getHTML() {
global $wgShowExceptionDetails;
- if( $wgShowExceptionDetails ) {
+
+ if ( $wgShowExceptionDetails ) {
return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) .
'</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
"</p>\n";
@@ -104,7 +121,8 @@ class MWException extends Exception {
*/
function getText() {
global $wgShowExceptionDetails;
- if( $wgShowExceptionDetails ) {
+
+ if ( $wgShowExceptionDetails ) {
return $this->getMessage() .
"\nBacktrace:\n" . $this->getTraceAsString() . "\n";
} else {
@@ -119,6 +137,7 @@ class MWException extends Exception {
return wfMsg( 'internalerror' );
} else {
global $wgSitename;
+
return "$wgSitename error";
}
}
@@ -127,13 +146,15 @@ class MWException extends Exception {
* Return the requested URL and point to file and line number from which the
* exception occured
*
- * @return string
+ * @return String
*/
function getLogMessage() {
global $wgRequest;
+
$file = $this->getFile();
$line = $this->getLine();
$message = $this->getMessage();
+
if ( isset( $wgRequest ) ) {
$url = $wgRequest->getRequestURL();
if ( !$url ) {
@@ -149,6 +170,7 @@ class MWException extends Exception {
/** Output the exception report using HTML */
function reportHTML() {
global $wgOut;
+
if ( $this->useOutputPage() ) {
$wgOut->setPageTitle( $this->getPageTitle() );
$wgOut->setRobotPolicy( "noindex,nofollow" );
@@ -156,16 +178,21 @@ class MWException extends Exception {
$wgOut->enableClientCache( false );
$wgOut->redirect( '' );
$wgOut->clearHTML();
- if( $hookResult = $this->runHooks( get_class( $this ) ) ) {
+
+ $hookResult = $this->runHooks( get_class( $this ) );
+ if ( $hookResult ) {
$wgOut->addHTML( $hookResult );
} else {
$wgOut->addHTML( $this->getHTML() );
}
+
$wgOut->output();
} else {
- if( $hookResult = $this->runHooks( get_class( $this ) . "Raw" ) ) {
+ $hookResult = $this->runHooks( get_class( $this ) . "Raw" );
+ if ( $hookResult ) {
die( $hookResult );
}
+
if ( defined( 'MEDIAWIKI_INSTALL' ) || $this->htmlBodyOnly() ) {
echo $this->getHTML();
} else {
@@ -182,9 +209,11 @@ class MWException extends Exception {
*/
function report() {
$log = $this->getLogMessage();
+
if ( $log ) {
wfDebugLog( 'exception', $log );
}
+
if ( self::isCommandLine() ) {
wfPrintError( $this->getText() );
} else {
@@ -197,22 +226,25 @@ class MWException extends Exception {
* $wgOut to output the exception.
*/
function htmlHeader() {
- global $wgLogo, $wgSitename, $wgOutputEncoding;
+ global $wgLogo, $wgOutputEncoding;
if ( !headers_sent() ) {
header( 'HTTP/1.0 500 Internal Server Error' );
- header( 'Content-type: text/html; charset='.$wgOutputEncoding );
+ header( 'Content-type: text/html; charset=' . $wgOutputEncoding );
/* Don't cache error pages! They cause no end of trouble... */
header( 'Cache-control: none' );
header( 'Pragma: nocache' );
}
- $title = $this->getPageTitle();
+
+ $logo = htmlspecialchars( $wgLogo, ENT_QUOTES );
+ $title = htmlspecialchars( $this->getPageTitle() );
+
return "<html>
<head>
<title>$title</title>
</head>
<body>
- <h1><img src='$wgLogo' style='float:left;margin-right:1em' alt=''/>$title</h1>
+ <h1><img src='$logo' style='float:left;margin-right:1em' alt=''/>$title</h1>
";
}
@@ -222,7 +254,7 @@ class MWException extends Exception {
function htmlFooter() {
return "</body></html>";
}
-
+
/**
* headers handled by subclass?
*/
@@ -267,6 +299,7 @@ class ErrorPageError extends MWException {
function report() {
global $wgOut;
+
$wgOut->showErrorPage( $this->title, $this->msg );
$wgOut->output();
}
@@ -283,7 +316,10 @@ function wfInstallExceptionHandler() {
* Report an exception to the user
*/
function wfReportException( Exception $e ) {
+ global $wgShowExceptionDetails;
+
$cmdLine = MWException::isCommandLine();
+
if ( $e instanceof MWException ) {
try {
$e->report();
@@ -292,28 +328,36 @@ function wfReportException( Exception $e ) {
// Show a simpler error message for the original exception,
// don't try to invoke report()
$message = "MediaWiki internal error.\n\n";
- if ( $GLOBALS['wgShowExceptionDetails'] )
- $message .= "Original exception: " . $e->__toString();
- $message .= "\n\nException caught inside exception handler";
- if ( $GLOBALS['wgShowExceptionDetails'] )
- $message .= ": " . $e2->__toString();
+
+ if ( $wgShowExceptionDetails ) {
+ $message .= 'Original exception: ' . $e->__toString() . "\n\n" .
+ 'Exception caught inside exception handler: ' . $e2->__toString();
+ } else {
+ $message .= "Exception caught inside exception handler.\n\n" .
+ "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
+ "to show detailed debugging information.";
+ }
+
$message .= "\n";
+
if ( $cmdLine ) {
wfPrintError( $message );
} else {
- echo nl2br( htmlspecialchars( $message ) ). "\n";
+ echo nl2br( htmlspecialchars( $message ) ) . "\n";
}
}
} else {
$message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
$e->__toString() . "\n";
- if ( $GLOBALS['wgShowExceptionDetails'] ) {
- $message .= "\n" . $e->getTraceAsString() ."\n";
+
+ if ( $wgShowExceptionDetails ) {
+ $message .= "\n" . $e->getTraceAsString() . "\n";
}
+
if ( $cmdLine ) {
wfPrintError( $message );
} else {
- echo nl2br( htmlspecialchars( $message ) ). "\n";
+ echo nl2br( htmlspecialchars( $message ) ) . "\n";
}
}
}
@@ -323,7 +367,7 @@ function wfReportException( Exception $e ) {
* Use this in command line mode only (see isCommandLine)
*/
function wfPrintError( $message ) {
- #NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
+ # NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
# Try to produce meaningful output anyway. Using echo may corrupt output to STDOUT though.
if ( defined( 'STDERR' ) ) {
fwrite( STDERR, $message );
@@ -345,9 +389,10 @@ function wfPrintError( $message ) {
*/
function wfExceptionHandler( $e ) {
global $wgFullyInitialised;
+
wfReportException( $e );
- // Final cleanup, similar to wfErrorExit()
+ // Final cleanup
if ( $wgFullyInitialised ) {
try {
wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
diff --git a/includes/Exif.php b/includes/Exif.php
index 594e633a..630821c3 100644
--- a/includes/Exif.php
+++ b/includes/Exif.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Exif metadata reader
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
@@ -618,7 +620,7 @@ class FormatExif {
* @param $exif Array: the Exif data to format ( as returned by
* Exif::getFilteredData() )
*/
- function FormatExif( $exif ) {
+ function __construct( $exif ) {
$this->mExif = $exif;
}
@@ -1146,15 +1148,3 @@ class FormatExif {
return $a;
}
}
-
-/**
- * MW 1.6 compatibility
- */
-define( 'MW_EXIF_BYTE', Exif::BYTE );
-define( 'MW_EXIF_ASCII', Exif::ASCII );
-define( 'MW_EXIF_SHORT', Exif::SHORT );
-define( 'MW_EXIF_LONG', Exif::LONG );
-define( 'MW_EXIF_RATIONAL', Exif::RATIONAL );
-define( 'MW_EXIF_UNDEFINED', Exif::UNDEFINED );
-define( 'MW_EXIF_SLONG', Exif::SLONG );
-define( 'MW_EXIF_SRATIONAL', Exif::SRATIONAL );
diff --git a/includes/Export.php b/includes/Export.php
index e8e74289..e7cd4d3f 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -1,21 +1,27 @@
<?php
-# Copyright (C) 2003, 2005, 2006 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * Base classes for dumps and export
+ *
+ * Copyright © 2003, 2005, 2006 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
/**
* @defgroup Dump Dump
@@ -124,7 +130,7 @@ class WikiExporter {
public function pageByName( $name ) {
$title = Title::newFromText( $name );
if( is_null( $title ) ) {
- return new WikiError( "Can't export invalid title" );
+ throw new MWException( "Can't export invalid title" );
} else {
return $this->pageByTitle( $title );
}
@@ -152,17 +158,16 @@ class WikiExporter {
# Not called by default (depends on $this->list_authors)
# Can be set by Special:Export when not exporting whole history
protected function do_list_authors( $page , $revision , $cond ) {
- $fname = "do_list_authors" ;
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
$this->author_list = "<contributors>";
//rev_deleted
$nothidden = '('.$this->db->bitAnd('rev_deleted', Revision::DELETED_USER) . ') = 0';
$sql = "SELECT DISTINCT rev_user_text,rev_user FROM {$page},{$revision}
WHERE page_id=rev_page AND $nothidden AND " . $cond ;
- $result = $this->db->query( $sql, $fname );
+ $result = $this->db->query( $sql, __METHOD__ );
$resultset = $this->db->resultObject( $result );
- while( $row = $resultset->fetchObject() ) {
+ foreach ( $resultset as $row ) {
$this->author_list .= "<contributor>" .
"<username>" .
htmlentities( $row->rev_user_text ) .
@@ -172,8 +177,8 @@ class WikiExporter {
"</id>" .
"</contributor>";
}
- wfProfileOut( $fname );
$this->author_list .= "</contributors>";
+ wfProfileOut( __METHOD__ );
}
protected function dumpFrom( $cond = '' ) {
@@ -246,12 +251,12 @@ class WikiExporter {
# One, and only one hook should set this, and return false
if( wfRunHooks( 'WikiExporter::dumpStableQuery', array(&$tables,&$opts,&$join) ) ) {
wfProfileOut( __METHOD__ );
- return new WikiError( __METHOD__." given invalid history dump type." );
+ throw new MWException( __METHOD__." given invalid history dump type." );
}
} else {
# Uknown history specification parameter?
wfProfileOut( __METHOD__ );
- return new WikiError( __METHOD__." given invalid history dump type." );
+ throw new MWException( __METHOD__." given invalid history dump type." );
}
# Query optimization hacks
if( $cond == '' ) {
@@ -301,7 +306,7 @@ class WikiExporter {
*/
protected function outputPageStream( $resultset ) {
$last = null;
- while( $row = $resultset->fetchObject() ) {
+ foreach ( $resultset as $row ) {
if( is_null( $last ) ||
$last->page_namespace != $row->page_namespace ||
$last->page_title != $row->page_title ) {
@@ -332,7 +337,7 @@ class WikiExporter {
}
protected function outputLogStream( $resultset ) {
- while( $row = $resultset->fetchObject() ) {
+ foreach ( $resultset as $row ) {
$output = $this->writer->writeLogItem( $row );
$this->sink->writeLogItem( $row, $output );
}
@@ -349,7 +354,7 @@ class XmlDumpWriter {
* @return string
*/
function schemaVersion() {
- return "0.4";
+ return "0.5";
}
/**
@@ -363,7 +368,7 @@ class XmlDumpWriter {
* @return string
*/
function openStream() {
- global $wgContLanguageCode;
+ global $wgLanguageCode;
$ver = $this->schemaVersion();
return Xml::element( 'mediawiki', array(
'xmlns' => "http://www.mediawiki.org/xml/export-$ver/",
@@ -371,7 +376,7 @@ class XmlDumpWriter {
'xsi:schemaLocation' => "http://www.mediawiki.org/xml/export-$ver/ " .
"http://www.mediawiki.org/xml/export-$ver.xsd",
'version' => $ver,
- 'xml:lang' => $wgContLanguageCode ),
+ 'xml:lang' => $wgLanguageCode ),
null ) .
"\n" .
$this->siteInfo();
@@ -477,8 +482,7 @@ class XmlDumpWriter {
* @access private
*/
function writeRevision( $row ) {
- $fname = 'WikiExporter::dumpRev';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
$out = " <revision>\n";
$out .= " " . Xml::element( 'id', null, strval( $row->rev_id ) ) . "\n";
@@ -507,12 +511,12 @@ class XmlDumpWriter {
// Raw text from the database may have invalid chars
$text = strval( Revision::getRevisionText( $row ) );
$out .= " " . Xml::elementClean( 'text',
- array( 'xml:space' => 'preserve' ),
+ array( 'xml:space' => 'preserve', 'bytes' => $row->rev_len ),
strval( $text ) ) . "\n";
} else {
// Stub output
$out .= " " . Xml::element( 'text',
- array( 'id' => $row->rev_text_id ),
+ array( 'id' => $row->rev_text_id, 'bytes' => $row->rev_len ),
"" ) . "\n";
}
@@ -520,7 +524,7 @@ class XmlDumpWriter {
$out .= " </revision>\n";
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $out;
}
@@ -533,8 +537,7 @@ class XmlDumpWriter {
* @access private
*/
function writeLogItem( $row ) {
- $fname = 'WikiExporter::writeLogItem';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
$out = " <logitem>\n";
$out .= " " . Xml::element( 'id', null, strval( $row->log_id ) ) . "\n";
@@ -568,7 +571,7 @@ class XmlDumpWriter {
$out .= " </logitem>\n";
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $out;
}
@@ -666,7 +669,7 @@ class DumpOutput {
class DumpFileOutput extends DumpOutput {
var $handle;
- function DumpFileOutput( $file ) {
+ function __construct( $file ) {
$this->handle = fopen( $file, "wt" );
}
@@ -682,7 +685,7 @@ class DumpFileOutput extends DumpOutput {
* @ingroup Dump
*/
class DumpPipeOutput extends DumpFileOutput {
- function DumpPipeOutput( $command, $file = null ) {
+ function __construct( $command, $file = null ) {
if( !is_null( $file ) ) {
$command .= " > " . wfEscapeShellArg( $file );
}
@@ -695,8 +698,8 @@ class DumpPipeOutput extends DumpFileOutput {
* @ingroup Dump
*/
class DumpGZipOutput extends DumpPipeOutput {
- function DumpGZipOutput( $file ) {
- parent::DumpPipeOutput( "gzip", $file );
+ function __construct( $file ) {
+ parent::__construct( "gzip", $file );
}
}
@@ -705,8 +708,8 @@ class DumpGZipOutput extends DumpPipeOutput {
* @ingroup Dump
*/
class DumpBZip2Output extends DumpPipeOutput {
- function DumpBZip2Output( $file ) {
- parent::DumpPipeOutput( "bzip2", $file );
+ function __construct( $file ) {
+ parent::__construct( "bzip2", $file );
}
}
@@ -715,12 +718,12 @@ class DumpBZip2Output extends DumpPipeOutput {
* @ingroup Dump
*/
class Dump7ZipOutput extends DumpPipeOutput {
- function Dump7ZipOutput( $file ) {
+ function __construct( $file ) {
$command = "7za a -bd -si " . wfEscapeShellArg( $file );
// Suppress annoying useless crap from p7zip
// Unfortunately this could suppress real error messages too
$command .= ' >' . wfGetNull() . ' 2>&1';
- parent::DumpPipeOutput( $command );
+ parent::__construct( $command );
}
}
@@ -733,7 +736,7 @@ class Dump7ZipOutput extends DumpPipeOutput {
* @ingroup Dump
*/
class DumpFilter {
- function DumpFilter( &$sink ) {
+ function __construct( &$sink ) {
$this->sink =& $sink;
}
@@ -796,8 +799,8 @@ class DumpNamespaceFilter extends DumpFilter {
var $invert = false;
var $namespaces = array();
- function DumpNamespaceFilter( &$sink, $param ) {
- parent::DumpFilter( $sink );
+ function __construct( &$sink, $param ) {
+ parent::__construct( $sink );
$constants = array(
"NS_MAIN" => NS_MAIN,
@@ -882,7 +885,7 @@ class DumpLatestFilter extends DumpFilter {
* @ingroup Dump
*/
class DumpMultiWriter {
- function DumpMultiWriter( $sinks ) {
+ function __construct( $sinks ) {
$this->sinks = $sinks;
$this->count = count( $sinks );
}
@@ -919,8 +922,7 @@ class DumpMultiWriter {
}
function xmlsafe( $string ) {
- $fname = 'xmlsafe';
- wfProfileIn( $fname );
+ wfProfileIn( __FUNCTION__ );
/**
* The page may contain old data which has not been properly normalized.
@@ -930,6 +932,6 @@ function xmlsafe( $string ) {
$string = UtfNormal::cleanUp( $string );
$string = htmlspecialchars( $string );
- wfProfileOut( $fname );
+ wfProfileOut( __FUNCTION__ );
return $string;
}
diff --git a/includes/ExternalEdit.php b/includes/ExternalEdit.php
index 1c58f442..7109c1ac 100644
--- a/includes/ExternalEdit.php
+++ b/includes/ExternalEdit.php
@@ -1,7 +1,10 @@
<?php
/**
+ * External editors support
+ *
* License: Public domain
*
+ * @file
* @author Erik Moeller <moeller@scireview.de>
*/
@@ -15,7 +18,6 @@
* and save the modified data back to the server.
*
*/
-
class ExternalEdit {
function __construct( $article, $mode ) {
@@ -51,6 +53,11 @@ class ExternalEdit {
}
$special=$wgLang->getNsText(NS_SPECIAL);
$control = <<<CONTROL
+; You're seeing this file because you're using Mediawiki's External Editor
+; feature. This is probably because you selected use external editor
+; in your preferences. To edit normally, either disable that preference
+; or go to the URL $url .
+; See http://www.mediawiki.org/wiki/Manual:External_editors for details.
[Process]
Type=$type
Engine=MediaWiki
diff --git a/includes/ExternalStore.php b/includes/ExternalStore.php
index 6a779079..ddb40c32 100644
--- a/includes/ExternalStore.php
+++ b/includes/ExternalStore.php
@@ -32,10 +32,17 @@ class ExternalStore {
if( !$wgExternalStores )
return false;
- @list( $proto, $path ) = explode( '://', $url, 2 );
- /* Bad URL */
- if( $path == '' )
+ $parts = explode( '://', $url, 2 );
+
+ if ( count( $parts ) != 2 ) {
+ return false;
+ }
+
+ list( $proto, $path ) = $parts;
+
+ if ( $path == '' ) { // Bad URL
return false;
+ }
$store = self::getStoreObject( $proto, $params );
if ( $store === false )
diff --git a/includes/ExternalStoreDB.php b/includes/ExternalStoreDB.php
index 769c64da..877277a2 100644
--- a/includes/ExternalStoreDB.php
+++ b/includes/ExternalStoreDB.php
@@ -1,26 +1,6 @@
<?php
/**
- * External database storage will use one (or more) separate connection pools
- * from what the main wiki uses. If we load many revisions, such as when doing
- * bulk backups or maintenance, we want to keep them around over the lifetime
- * of the script.
- *
- * Associative array of LoadBalancer objects, indexed by cluster name.
- */
-global $wgExternalLoadBalancers;
-$wgExternalLoadBalancers = array();
-
-/**
- * One-step cache variable to hold base blobs; operations that
- * pull multiple revisions may often pull multiple times from
- * the same blob. By keeping the last-used one open, we avoid
- * redundant unserialization and decompression overhead.
- */
-global $wgExternalBlobCache;
-$wgExternalBlobCache = array();
-
-/**
* DB accessable external objects
* @ingroup ExternalStorage
*/
@@ -113,11 +93,18 @@ class ExternalStoreDB {
* @private
*/
function &fetchBlob( $cluster, $id, $itemID ) {
- global $wgExternalBlobCache;
+ /**
+ * One-step cache variable to hold base blobs; operations that
+ * pull multiple revisions may often pull multiple times from
+ * the same blob. By keeping the last-used one open, we avoid
+ * redundant unserialization and decompression overhead.
+ */
+ static $externalBlobCache = array();
+
$cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
- if( isset( $wgExternalBlobCache[$cacheID] ) ) {
+ if( isset( $externalBlobCache[$cacheID] ) ) {
wfDebug( "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
- return $wgExternalBlobCache[$cacheID];
+ return $externalBlobCache[$cacheID];
}
wfDebug( "ExternalStoreDB::fetchBlob cache miss on $cacheID\n" );
@@ -138,7 +125,7 @@ class ExternalStoreDB {
$ret = unserialize( $ret );
}
- $wgExternalBlobCache = array( $cacheID => &$ret );
+ $externalBlobCache = array( $cacheID => &$ret );
return $ret;
}
diff --git a/includes/ExternalUser.php b/includes/ExternalUser.php
index 65dae617..d1eff916 100644
--- a/includes/ExternalUser.php
+++ b/includes/ExternalUser.php
@@ -1,21 +1,24 @@
<?php
-
-# Copyright (C) 2009 Aryeh Gregor
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * Authentication with a foreign database
+ *
+ * Copyright © 2009 Aryeh Gregor
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
/**
* @defgroup ExternalUser ExternalUser
diff --git a/includes/Feed.php b/includes/Feed.php
index 782b6428..bc20a9dc 100644
--- a/includes/Feed.php
+++ b/includes/Feed.php
@@ -1,34 +1,36 @@
<?php
-
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
- * @defgroup Feed Feed
- *
* Basic support for outputting syndication feeds in RSS, other formats.
+ *
* Contain a feed class as well as classes to build rss / atom ... feeds
* Available feeds are defined in Defines.php
*
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
*/
/**
+ * @defgroup Feed Feed
+ */
+
+/**
* A base class for basic support for outputting syndication feeds in RSS and other formats.
*
* @ingroup Feed
@@ -99,7 +101,7 @@ class FeedItem {
*/
public function setUniqueId($uniqueId, $RSSisPermalink = False) {
$this->UniqueId = $uniqueId;
- $this->RSSIsPermalink = $isPermalink;
+ $this->RSSIsPermalink = $RSSisPermalink;
}
/**
@@ -135,8 +137,8 @@ class FeedItem {
* @return String
*/
public function getLanguage() {
- global $wgContLanguageCode;
- return $wgContLanguageCode;
+ global $wgLanguageCode;
+ return $wgLanguageCode;
}
/**
@@ -303,7 +305,7 @@ class RSSFeed extends ChannelFeed {
<item>
<title><?php print $item->getTitle() ?></title>
<link><?php print $item->getUrl() ?></link>
- <guid<?php if( $item->RSSIsPermalink ) print ' isPermaLink="true"' ?>><?php print $item->getUniqueId() ?></guid>
+ <guid<?php if( !$item->RSSIsPermalink ) print ' isPermaLink="false"' ?>><?php print $item->getUniqueId() ?></guid>
<description><?php print $item->getDescription() ?></description>
<?php if( $item->getDate() ) { ?><pubDate><?php print $this->formatTime( $item->getDate() ) ?></pubDate><?php } ?>
<?php if( $item->getAuthor() ) { ?><dc:creator><?php print $item->getAuthor() ?></dc:creator><?php }?>
diff --git a/includes/FeedUtils.php b/includes/FeedUtils.php
index 7e841f32..9daffc12 100644
--- a/includes/FeedUtils.php
+++ b/includes/FeedUtils.php
@@ -86,7 +86,7 @@ class FeedUtils {
* @return String
*/
public static function formatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext='' ) {
- global $wgFeedDiffCutoff, $wgContLang, $wgUser;
+ global $wgFeedDiffCutoff, $wgLang, $wgUser;
wfProfileIn( __METHOD__ );
$skin = $wgUser->getSkin();
@@ -108,9 +108,9 @@ class FeedUtils {
wfProfileIn( __METHOD__."-dodiff" );
#$diffText = $de->getDiff( wfMsg( 'revisionasof',
- # $wgContLang->timeanddate( $timestamp ),
- # $wgContLang->date( $timestamp ),
- # $wgContLang->time( $timestamp ) ),
+ # $wgLang->timeanddate( $timestamp ),
+ # $wgLang->date( $timestamp ),
+ # $wgLang->time( $timestamp ) ),
# wfMsg( 'currentrev' ) );
// Don't bother generating the diff if we won't be able to show it
@@ -119,12 +119,12 @@ class FeedUtils {
$diffText = $de->getDiff(
wfMsg( 'previousrevision' ), // hack
wfMsg( 'revisionasof',
- $wgContLang->timeanddate( $timestamp ),
- $wgContLang->date( $timestamp ),
- $wgContLang->time( $timestamp ) ) );
+ $wgLang->timeanddate( $timestamp ),
+ $wgLang->date( $timestamp ),
+ $wgLang->time( $timestamp ) ) );
}
- if ( ( strlen( $diffText ) > $wgFeedDiffCutoff ) || ( $wgFeedDiffCutoff <= 0 ) ) {
+ if ( $wgFeedDiffCutoff <= 0 || ( strlen( $diffText ) > $wgFeedDiffCutoff ) ) {
// Omit large diffs
$diffLink = $title->escapeFullUrl(
'diff=' . $newid .
diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php
index dad19524..030330bb 100644
--- a/includes/FileDeleteForm.php
+++ b/includes/FileDeleteForm.php
@@ -102,34 +102,47 @@ class FileDeleteForm {
public static function doDelete( &$title, &$file, &$oldimage, $reason, $suppress ) {
global $wgUser;
$article = null;
+ $status = Status::newFatal( 'error' );
+
if( $oldimage ) {
$status = $file->deleteOld( $oldimage, $reason, $suppress );
if( $status->ok ) {
// Need to do a log item
$log = new LogPage( 'delete' );
$logComment = wfMsgForContent( 'deletedrevision', $oldimage );
- if( trim( $reason ) != '' )
+ if( trim( $reason ) != '' ) {
$logComment .= wfMsgForContent( 'colon-separator' ) . $reason;
- $log->addEntry( 'delete', $title, $logComment );
+ }
+ $log->addEntry( 'delete', $title, $logComment );
}
} else {
- $status = $file->delete( $reason, $suppress );
- if( $status->ok ) {
- $id = $title->getArticleID( GAID_FOR_UPDATE );
- // Need to delete the associated article
- $article = new Article( $title );
- $error = '';
- if( wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason, &$error)) ) {
- if( $article->doDeleteArticle( $reason, $suppress, $id ) ) {
+ $id = $title->getArticleID( Title::GAID_FOR_UPDATE );
+ $article = new Article( $title );
+ $error = '';
+ $dbw = wfGetDB( DB_MASTER );
+ try {
+ if( wfRunHooks( 'ArticleDelete', array( &$article, &$wgUser, &$reason, &$error ) ) ) {
+ // delete the associated article first
+ if( $article->doDeleteArticle( $reason, $suppress, $id, false ) ) {
global $wgRequest;
if( $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn() ) {
$article->doWatch();
} elseif( $title->userIsWatching() ) {
$article->doUnwatch();
}
- wfRunHooks('ArticleDeleteComplete', array(&$article, &$wgUser, $reason, $id));
+ $status = $file->delete( $reason, $suppress );
+ if( $status->ok ) {
+ $dbw->commit();
+ wfRunHooks( 'ArticleDeleteComplete', array( &$article, &$wgUser, $reason, $id ) );
+ } else {
+ $dbw->rollback();
+ }
}
}
+ } catch ( MWException $e ) {
+ // rollback before returning to prevent UI from displaying incorrect "View or restore N deleted edits?"
+ $dbw->rollback();
+ throw $e;
}
}
if( $status->isGood() )
@@ -161,7 +174,7 @@ class FileDeleteForm {
'id' => 'mw-img-deleteconfirm' ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'filedelete-legend' ) ) .
- Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->oldimage ) ) .
+ Html::hidden( 'wpEditToken', $wgUser->editToken( $this->oldimage ) ) .
$this->prepareMessage( 'filedelete-intro' ) .
Xml::openElement( 'table', array( 'id' => 'mw-img-deleteconfirm-table' ) ) .
"<tr>
@@ -241,7 +254,6 @@ class FileDeleteForm {
private function prepareMessage( $message ) {
global $wgLang;
if( $this->oldimage ) {
- $url = $this->file->getArchiveUrl( $this->oldimage );
return wfMsgExt(
"{$message}-old", # To ensure grep will find them: 'filedelete-intro-old', 'filedelete-nofile-old', 'filedelete-success-old'
'parse',
diff --git a/includes/FileRevertForm.php b/includes/FileRevertForm.php
index eb16693a..47084aad 100644
--- a/includes/FileRevertForm.php
+++ b/includes/FileRevertForm.php
@@ -88,11 +88,11 @@ class FileRevertForm {
* Show the confirmation form
*/
protected function showForm() {
- global $wgOut, $wgUser, $wgRequest, $wgLang, $wgContLang;
+ global $wgOut, $wgUser, $wgLang, $wgContLang;
$timestamp = $this->getTimestamp();
$form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getAction() ) );
- $form .= Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->archiveName ) );
+ $form .= Html::hidden( 'wpEditToken', $wgUser->editToken( $this->archiveName ) );
$form .= '<fieldset><legend>' . wfMsgHtml( 'filerevert-legend' ) . '</legend>';
$form .= wfMsgExt( 'filerevert-intro', 'parse', $this->title->getText(),
$wgLang->date( $timestamp, true ), $wgLang->time( $timestamp, true ),
diff --git a/includes/ForkController.php b/includes/ForkController.php
index 7b889228..e5b44c2b 100644
--- a/includes/ForkController.php
+++ b/includes/ForkController.php
@@ -126,8 +126,6 @@ class ForkController {
* Fork a number of worker processes.
*/
protected function forkWorkers( $numProcs ) {
- global $wgMemc, $wgCaches, $wgMainCacheType;
-
$this->prepareEnvironment();
// Create the child processes
diff --git a/includes/FormOptions.php b/includes/FormOptions.php
index 262c8c7f..2442a330 100644
--- a/includes/FormOptions.php
+++ b/includes/FormOptions.php
@@ -2,16 +2,17 @@
/**
* Helper class to keep track of options when mixing links and form elements.
*
+ * Copyright © 2008, Niklas Laxström
+ *
* @author Niklas Laxström
- * @copyright Copyright © 2008, Niklas Laxström
*/
class FormOptions implements ArrayAccess {
- const AUTO = -1; //! Automatically detects simple data types
+ const AUTO = -1; // ! Automatically detects simple data types
const STRING = 0;
const INT = 1;
const BOOL = 2;
- const INTNULL = 3; //! Useful for namespace selector
+ const INTNULL = 3; // ! Useful for namespace selector
protected $options = array();
@@ -34,15 +35,15 @@ class FormOptions implements ArrayAccess {
public function delete( $name ) {
$this->validateName( $name, true );
- unset($this->options[$name]);
+ unset( $this->options[$name] );
}
public static function guessType( $data ) {
- if ( is_bool($data) ) {
+ if ( is_bool( $data ) ) {
return self::BOOL;
- } elseif( is_int($data) ) {
+ } elseif ( is_int( $data ) ) {
return self::INT;
- } elseif( is_string($data) ) {
+ } elseif ( is_string( $data ) ) {
return self::STRING;
} else {
throw new MWException( 'Unsupported datatype' );
@@ -52,7 +53,7 @@ class FormOptions implements ArrayAccess {
# Handling values
public function validateName( $name, $strict = false ) {
- if ( !isset($this->options[$name]) ) {
+ if ( !isset( $this->options[$name] ) ) {
if ( $strict ) {
throw new MWException( "Invalid option $name" );
} else {
@@ -64,6 +65,7 @@ class FormOptions implements ArrayAccess {
public function setValue( $name, $value, $force = false ) {
$this->validateName( $name, true );
+
if ( !$force && $value === $this->options[$name]['default'] ) {
// null default values as unchanged
$this->options[$name]['value'] = null;
@@ -74,6 +76,7 @@ class FormOptions implements ArrayAccess {
public function getValue( $name ) {
$this->validateName( $name, true );
+
return $this->getValueReal( $this->options[$name] );
}
@@ -93,16 +96,19 @@ class FormOptions implements ArrayAccess {
public function consumeValue( $name ) {
$this->validateName( $name, true );
$this->options[$name]['consumed'] = true;
+
return $this->getValueReal( $this->options[$name] );
}
public function consumeValues( /*Array*/ $names ) {
$out = array();
+
foreach ( $names as $name ) {
$this->validateName( $name, true );
$this->options[$name]['consumed'] = true;
$out[] = $this->getValueReal( $this->options[$name] );
}
+
return $out;
}
@@ -111,8 +117,9 @@ class FormOptions implements ArrayAccess {
public function validateIntBounds( $name, $min, $max ) {
$this->validateName( $name, true );
- if ( $this->options[$name]['type'] !== self::INT )
+ if ( $this->options[$name]['type'] !== self::INT ) {
throw new MWException( "Option $name is not of type int" );
+ }
$value = $this->getValueReal( $this->options[$name] );
$value = max( $min, min( $max, $value ) );
@@ -124,6 +131,7 @@ class FormOptions implements ArrayAccess {
public function getUnconsumedValues( $all = false ) {
$values = array();
+
foreach ( $this->options as $name => $data ) {
if ( !$data['consumed'] ) {
if ( $all || $data['value'] !== null ) {
@@ -131,24 +139,29 @@ class FormOptions implements ArrayAccess {
}
}
}
+
return $values;
}
public function getChangedValues() {
$values = array();
+
foreach ( $this->options as $name => $data ) {
if ( $data['value'] !== null ) {
$values[$name] = $data['value'];
}
}
+
return $values;
}
public function getAllValues() {
$values = array();
+
foreach ( $this->options as $name => $data ) {
$values[$name] = $this->getValueReal( $data );
}
+
return $values;
}
@@ -156,7 +169,7 @@ class FormOptions implements ArrayAccess {
public function fetchValuesFromRequest( WebRequest $r, $values = false ) {
if ( !$values ) {
- $values = array_keys($this->options);
+ $values = array_keys( $this->options );
}
foreach ( $values as $name ) {
@@ -184,7 +197,7 @@ class FormOptions implements ArrayAccess {
/* ArrayAccess methods */
public function offsetExists( $name ) {
- return isset($this->options[$name]);
+ return isset( $this->options[$name] );
}
public function offsetGet( $name ) {
@@ -192,11 +205,10 @@ class FormOptions implements ArrayAccess {
}
public function offsetSet( $name, $value ) {
- return $this->setValue( $name, $value );
+ $this->setValue( $name, $value );
}
public function offsetUnset( $name ) {
- return $this->delete( $name );
+ $this->delete( $name );
}
-
}
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index d6e0f5b4..b21779c1 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -1,15 +1,14 @@
<?php
+/**
+ * Global functions used everywhere
+ * @file
+ */
if ( !defined( 'MEDIAWIKI' ) ) {
die( "This file is part of MediaWiki, it is not a valid entry point" );
}
-/**
- * Global functions used everywhere
- */
-
-require_once dirname(__FILE__) . '/normal/UtfNormalUtil.php';
-require_once dirname(__FILE__) . '/XmlFunctions.php';
+require_once dirname( __FILE__ ) . '/normal/UtfNormalUtil.php';
// Hide compatibility functions from Doxygen
/// @cond
@@ -17,18 +16,27 @@ require_once dirname(__FILE__) . '/XmlFunctions.php';
/**
* Compatibility functions
*
- * We more or less support PHP 5.0.x and up.
+ * We support PHP 5.1.x and up.
* Re-implementations of newer functions or functions in non-standard
* PHP extensions may be included here.
*/
-if( !function_exists('iconv') ) {
+if( !function_exists( 'iconv' ) ) {
# iconv support is not in the default configuration and so may not be present.
# Assume will only ever use utf-8 and iso-8859-1.
# This will *not* work in all circumstances.
function iconv( $from, $to, $string ) {
- if(strcasecmp( $from, $to ) == 0) return $string;
- if(strcasecmp( $from, 'utf-8' ) == 0) return utf8_decode( $string );
- if(strcasecmp( $to, 'utf-8' ) == 0) return utf8_encode( $string );
+ if ( substr( $to, -8 ) == '//IGNORE' ) {
+ $to = substr( $to, 0, strlen( $to ) - 8 );
+ }
+ if( strcasecmp( $from, $to ) == 0 ) {
+ return $string;
+ }
+ if( strcasecmp( $from, 'utf-8' ) == 0 ) {
+ return utf8_decode( $string );
+ }
+ if( strcasecmp( $to, 'utf-8' ) == 0 ) {
+ return utf8_encode( $string );
+ }
return $string;
}
}
@@ -49,41 +57,43 @@ if ( !function_exists( 'mb_substr' ) ) {
$split = mb_substr_split_unicode( $str, intval( $start ) );
$str = substr( $str, $split );
}
-
+
if( $count !== 'end' ) {
$split = mb_substr_split_unicode( $str, intval( $count ) );
$str = substr( $str, 0, $split );
}
-
+
return $str;
}
-
+
function mb_substr_split_unicode( $str, $splitPos ) {
if( $splitPos == 0 ) {
return 0;
}
-
+
$byteLen = strlen( $str );
-
+
if( $splitPos > 0 ) {
if( $splitPos > 256 ) {
// Optimize large string offsets by skipping ahead N bytes.
// This will cut out most of our slow time on Latin-based text,
// and 1/2 to 1/3 on East European and Asian scripts.
$bytePos = $splitPos;
- while ($bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0")
+ while ( $bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
++$bytePos;
+ }
$charPos = mb_strlen( substr( $str, 0, $bytePos ) );
} else {
$charPos = 0;
$bytePos = 0;
}
-
+
while( $charPos++ < $splitPos ) {
++$bytePos;
// Move past any tail bytes
- while ($bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0")
+ while ( $bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
++$bytePos;
+ }
}
} else {
$splitPosX = $splitPos + 1;
@@ -92,11 +102,12 @@ if ( !function_exists( 'mb_substr' ) ) {
while( $bytePos > 0 && $charPos-- >= $splitPosX ) {
--$bytePos;
// Move past any tail bytes
- while ($bytePos > 0 && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0")
+ while ( $bytePos > 0 && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
--$bytePos;
+ }
}
}
-
+
return $bytePos;
}
}
@@ -108,7 +119,7 @@ if ( !function_exists( 'mb_strlen' ) ) {
* @param string $enc optional encoding; ignored
* @return int
*/
- function mb_strlen( $str, $enc="" ) {
+ function mb_strlen( $str, $enc = '' ) {
$counts = count_chars( $str );
$total = 0;
@@ -135,11 +146,11 @@ if( !function_exists( 'mb_strpos' ) ) {
* @param $encoding String: optional encoding; ignored
* @return int
*/
- function mb_strpos( $haystack, $needle, $offset = 0, $encoding="" ) {
+ function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
$needle = preg_quote( $needle, '/' );
$ar = array();
- preg_match( '/'.$needle.'/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
+ preg_match( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
if( isset( $ar[0][1] ) ) {
return $ar[0][1];
@@ -158,65 +169,17 @@ if( !function_exists( 'mb_strrpos' ) ) {
* @param $encoding String: optional encoding; ignored
* @return int
*/
- function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = "" ) {
+ function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
$needle = preg_quote( $needle, '/' );
$ar = array();
- preg_match_all( '/'.$needle.'/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
+ preg_match_all( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
- if( isset( $ar[0] ) && count( $ar[0] ) > 0 &&
- isset( $ar[0][count($ar[0])-1][1] ) ) {
- return $ar[0][count($ar[0])-1][1];
+ if( isset( $ar[0] ) && count( $ar[0] ) > 0 &&
+ isset( $ar[0][count( $ar[0] ) - 1][1] ) ) {
+ return $ar[0][count( $ar[0] ) - 1][1];
} else {
return false;
- }
- }
-}
-
-if ( !function_exists( 'array_diff_key' ) ) {
- /**
- * Exists in PHP 5.1.0+
- * Not quite compatible, two-argument version only
- * Null values will cause problems due to this use of isset()
- */
- function array_diff_key( $left, $right ) {
- $result = $left;
- foreach ( $left as $key => $unused ) {
- if ( isset( $right[$key] ) ) {
- unset( $result[$key] );
- }
- }
- return $result;
- }
-}
-
-if ( !function_exists( 'array_intersect_key' ) ) {
- /**
- * Exists in 5.1.0+
- * Define our own array_intersect_key function
- */
- function array_intersect_key( $isec, $keys ) {
- $argc = func_num_args();
-
- if ( $argc > 2 ) {
- for ( $i = 1; $isec && $i < $argc; $i++ ) {
- $arr = func_get_arg( $i );
-
- foreach ( array_keys( $isec ) as $key ) {
- if ( !isset( $arr[$key] ) )
- unset( $isec[$key] );
- }
- }
-
- return $isec;
- } else {
- $res = array();
- foreach ( array_keys( $isec ) as $key ) {
- if ( isset( $keys[$key] ) )
- $res[$key] = $isec[$key];
- }
-
- return $res;
}
}
}
@@ -234,6 +197,16 @@ if ( !function_exists( 'istainted' ) ) {
define( 'TC_PCRE', 1 );
define( 'TC_SELF', 1 );
}
+
+// array_fill_keys() was only added in 5.2, but people use it anyway
+// add a back-compat layer for 5.1. See bug 27781
+if( !function_exists( 'array_fill_keys' ) ) {
+ function array_fill_keys( $keys, $value ) {
+ return array_combine( $keys, array_fill( 0, count( $keys ), $value ) );
+ }
+}
+
+
/// @endcond
@@ -251,7 +224,7 @@ function wfArrayDiff2_cmp( $a, $b ) {
} else {
reset( $a );
reset( $b );
- while( ( list( $keyA, $valueA ) = each( $a ) ) && ( list( $keyB, $valueB ) = each( $b ) ) ) {
+ while( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) {
$cmp = strcmp( $valueA, $valueB );
if ( $cmp !== 0 ) {
return $cmp;
@@ -264,9 +237,10 @@ function wfArrayDiff2_cmp( $a, $b ) {
/**
* Seed Mersenne Twister
* No-op for compatibility; only necessary in PHP < 4.2.0
+ * @deprecated. Remove in 1.18
*/
function wfSeedRandom() {
- /* No-op */
+ wfDeprecated(__FUNCTION__);
}
/**
@@ -280,7 +254,7 @@ function wfRandom() {
# The maximum random value is "only" 2^31-1, so get two random
# values to reduce the chance of dupes
$max = mt_getrandmax() + 1;
- $rand = number_format( (mt_rand() * $max + mt_rand())
+ $rand = number_format( ( mt_rand() * $max + mt_rand() )
/ $max / $max, 12, '.', '' );
return $rand;
}
@@ -299,16 +273,27 @@ function wfRandom() {
*
* ;:@$!*(),/
*
+ * However, IIS7 redirects fail when the url contains a colon (Bug 22709),
+ * so no fancy : for IIS7.
+ *
* %2F in the page titles seems to fatally break for some reason.
*
* @param $s String:
* @return string
*/
function wfUrlencode( $s ) {
+ static $needle;
+ if ( is_null( $needle ) ) {
+ $needle = array( '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F' );
+ if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) || ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false ) ) {
+ $needle[] = '%3A';
+ }
+ }
+
$s = urlencode( $s );
$s = str_ireplace(
- array( '%3B','%3A','%40','%24','%21','%2A','%28','%29','%2C','%2F' ),
- array( ';', ':', '@', '$', '!', '*', '(', ')', ',', '/' ),
+ $needle,
+ array( ';', '@', '$', '!', '*', '(', ')', ',', '/', ':' ),
$s
);
@@ -371,7 +356,9 @@ function wfDebug( $text, $logonly = false ) {
function wfDebugTimer() {
global $wgDebugTimestamps;
- if ( !$wgDebugTimestamps ) return '';
+ if ( !$wgDebugTimestamps ) {
+ return '';
+ }
static $start = null;
if ( $start === null ) {
@@ -409,7 +396,7 @@ function wfDebugMem( $exact = false ) {
*/
function wfDebugLog( $logGroup, $text, $public = true ) {
global $wgDebugLogGroups, $wgShowHostnames;
- $text = trim($text)."\n";
+ $text = trim( $text ) . "\n";
if( isset( $wgDebugLogGroups[$logGroup] ) ) {
$time = wfTimestamp( TS_DB );
$wiki = wfWikiID();
@@ -419,7 +406,7 @@ function wfDebugLog( $logGroup, $text, $public = true ) {
$host = '';
}
wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
- } else if ( $public === true ) {
+ } elseif ( $public === true ) {
wfDebug( $text, true );
}
}
@@ -432,28 +419,27 @@ function wfLogDBError( $text ) {
global $wgDBerrorLog, $wgDBname;
if ( $wgDBerrorLog ) {
$host = trim(`hostname`);
- $text = date('D M j G:i:s T Y') . "\t$host\t$wgDBname\t$text";
+ $text = date( 'D M j G:i:s T Y' ) . "\t$host\t$wgDBname\t$text";
wfErrorLog( $text, $wgDBerrorLog );
}
}
/**
* Log to a file without getting "file size exceeded" signals.
- *
- * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
+ *
+ * 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.
*/
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
- $protocol = $m[1];
$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 ) ) {
- $protocol = $m[1];
$host = $m[2];
if ( !IP::isIPv4( $host ) ) {
$host = gethostbyname( $host );
@@ -462,7 +448,7 @@ function wfErrorLog( $text, $file ) {
$prefix = isset( $m[4] ) ? $m[4] : false;
$domain = AF_INET;
} else {
- throw new MWException( __METHOD__.": Invalid UDP specification" );
+ throw new MWException( __METHOD__ . ': Invalid UDP specification' );
}
// Clean it up for the multiplexer
if ( strval( $prefix ) !== '' ) {
@@ -496,29 +482,39 @@ function wfLogProfilingData() {
global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
global $wgProfiler, $wgProfileLimit, $wgUser;
# Profiling must actually be enabled...
- if( !isset( $wgProfiler ) ) return;
+ if( is_null( $wgProfiler ) ) {
+ return;
+ }
# Get total page request time
$now = wfTime();
$elapsed = $now - $wgRequestTime;
# Only show pages that longer than $wgProfileLimit time (default is 0)
- if( $elapsed <= $wgProfileLimit ) return;
+ if( $elapsed <= $wgProfileLimit ) {
+ return;
+ }
$prof = wfGetProfilingOutput( $wgRequestTime, $elapsed );
$forward = '';
- if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
+ if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
$forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
- if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) )
+ }
+ if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
$forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
- if( !empty( $_SERVER['HTTP_FROM'] ) )
+ }
+ if( !empty( $_SERVER['HTTP_FROM'] ) ) {
$forward .= ' from ' . $_SERVER['HTTP_FROM'];
- if( $forward )
+ }
+ if( $forward ) {
$forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
+ }
// Don't unstub $wgUser at this late stage just for statistics purposes
- if( StubObject::isRealObject($wgUser) && $wgUser->isAnon() )
+ // FIXME: We can detect some anons even if it is not loaded. See User::getId()
+ if( $wgUser->mDataLoaded && $wgUser->isAnon() ) {
$forward .= ' anon';
+ }
$log = sprintf( "%s\t%04.3f\t%s\n",
- gmdate( 'YmdHis' ), $elapsed,
- urldecode( $wgRequest->getRequestURL() . $forward ) );
- if ( $wgDebugLogFile != '' && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) {
+ gmdate( 'YmdHis' ), $elapsed,
+ urldecode( $wgRequest->getRequestURL() . $forward ) );
+ if ( $wgDebugLogFile != '' && ( $wgRequest->getVal( 'action' ) != 'raw' || $wgDebugRawPage ) ) {
wfErrorLog( $log . $prof, $wgDebugLogFile );
}
}
@@ -566,28 +562,33 @@ function wfReadOnlyReason() {
* functionality), or if it is true then use the wikis
* @return Language object
*/
-function wfGetLangObj( $langcode = false ){
+function wfGetLangObj( $langcode = false ) {
# Identify which language to get or create a language object for.
- if( $langcode instanceof Language )
- # Great, we already have the object!
+ # Using is_object here due to Stub objects.
+ if( is_object( $langcode ) ) {
+ # Great, we already have the object (hopefully)!
return $langcode;
-
- global $wgContLang;
- if( $langcode === $wgContLang->getCode() || $langcode === true )
+ }
+
+ global $wgContLang, $wgLanguageCode;
+ if( $langcode === true || $langcode === $wgLanguageCode ) {
# $langcode is the language code of the wikis content language object.
# or it is a boolean and value is true
return $wgContLang;
-
+ }
+
global $wgLang;
- if( $langcode === $wgLang->getCode() || $langcode === false )
+ if( $langcode === false || $langcode === $wgLang->getCode() ) {
# $langcode is the language code of user language object.
# or it was a boolean and value is false
return $wgLang;
+ }
$validCodes = array_keys( Language::getLanguageNames() );
- if( in_array( $langcode, $validCodes ) )
+ if( in_array( $langcode, $validCodes ) ) {
# $langcode corresponds to a valid language.
return Language::factory( $langcode );
+ }
# $langcode is a string, but not a valid language code; use content language.
wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
@@ -595,6 +596,36 @@ function wfGetLangObj( $langcode = false ){
}
/**
+ * Use this instead of $wgContLang, when working with user interface.
+ * User interface is currently hard coded according to wiki content language
+ * in many ways, especially regarding to text direction. There is lots stuff
+ * to fix, hence this function to keep the old behaviour unless the global
+ * $wgBetterDirectionality is enabled (or removed when everything works).
+ */
+function wfUILang() {
+ global $wgBetterDirectionality;
+ return wfGetLangObj( !$wgBetterDirectionality );
+}
+
+/**
+ * This is the new function for getting translated interface messages.
+ * See the Message class for documentation how to use them.
+ * The intention is that this function replaces all old wfMsg* functions.
+ * @param $key \string Message key.
+ * Varargs: normal message parameters.
+ * @return \type{Message}
+ * @since 1.17
+ */
+function wfMessage( $key /*...*/) {
+ $params = func_get_args();
+ array_shift( $params );
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ return new Message( $key, $params );
+}
+
+/**
* Get a message from anywhere, for the current user language.
*
* Use wfMsgForContent() instead if the message should NOT
@@ -604,7 +635,7 @@ function wfGetLangObj( $langcode = false ){
* defined in languages/Language.php
*
* This function also takes extra optional parameters (not
- * shown in the function definition), which can by used to
+ * shown in the function definition), which can be used to
* insert variable text into the predefined message.
*/
function wfMsg( $key ) {
@@ -626,7 +657,7 @@ function wfMsgNoTrans( $key ) {
* Get a message from anywhere, for the current global language
* set with $wgLanguageCode.
*
- * Use this if the message should NOT change dependent on the
+ * Use this if the message should NOT change dependent on the
* language set in the user's preferences. This is the case for
* most text written into logs, as well as link targets (such as
* the name of the copyright policy page). Link titles, on the
@@ -638,8 +669,8 @@ function wfMsgNoTrans( $key ) {
*
* Be wary of this distinction: If you use wfMsg() where you should
* use wfMsgForContent(), a user of the software may have to
- * customize over 70 messages in order to, e.g., fix a link in every
- * possible language.
+ * customize potentially hundreds of messages in
+ * order to, e.g., fix a link in every possible language.
*
* @param $key String: lookup key for the message, usually
* defined in languages/Language.php
@@ -651,7 +682,9 @@ function wfMsgForContent( $key ) {
$forcontent = true;
if( is_array( $wgForceUIMsgAsContentMsg ) &&
in_array( $key, $wgForceUIMsgAsContentMsg ) )
+ {
$forcontent = false;
+ }
return wfMsgReal( $key, $args, true, $forcontent );
}
@@ -665,7 +698,9 @@ function wfMsgForContentNoTrans( $key ) {
$forcontent = true;
if( is_array( $wgForceUIMsgAsContentMsg ) &&
in_array( $key, $wgForceUIMsgAsContentMsg ) )
+ {
$forcontent = false;
+ }
return wfMsgReal( $key, $args, true, $forcontent, false );
}
@@ -688,7 +723,9 @@ function wfMsgNoDBForContent( $key ) {
$forcontent = true;
if( is_array( $wgForceUIMsgAsContentMsg ) &&
in_array( $key, $wgForceUIMsgAsContentMsg ) )
+ {
$forcontent = false;
+ }
return wfMsgReal( $key, $args, false, $forcontent );
}
@@ -698,8 +735,8 @@ function wfMsgNoDBForContent( $key ) {
* @param $key String: key to get.
* @param $args
* @param $useDB Boolean
- * @param $transform Boolean: Whether or not to transform the message.
* @param $forContent Mixed: Language code, or false for user lang, true for content lang.
+ * @param $transform Boolean: Whether or not to transform the message.
* @return String: the requested message.
*/
function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) {
@@ -716,10 +753,11 @@ function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform
*/
function wfMsgWeirdKey( $key ) {
$source = wfMsgGetKey( $key, false, true, false );
- if ( wfEmptyMsg( $key, $source ) )
- return "";
- else
+ if ( wfEmptyMsg( $key, $source ) ) {
+ return '';
+ } else {
return $source;
+ }
}
/**
@@ -730,33 +768,22 @@ function wfMsgWeirdKey( $key ) {
* behaves as a content language switch if it is a boolean.
* @param $transform Boolean: whether to parse magic words, etc.
* @return string
- * @private
*/
function wfMsgGetKey( $key, $useDB, $langCode = false, $transform = true ) {
- global $wgContLang, $wgMessageCache;
+ global $wgMessageCache;
- wfRunHooks('NormalizeMessageKey', array(&$key, &$useDB, &$langCode, &$transform));
-
- # If $wgMessageCache isn't initialised yet, try to return something sensible.
- if( is_object( $wgMessageCache ) ) {
- $message = $wgMessageCache->get( $key, $useDB, $langCode );
- if ( $transform ) {
- $message = $wgMessageCache->transform( $message );
- }
- } else {
- $lang = wfGetLangObj( $langCode );
+ wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
- # MessageCache::get() does this already, Language::getMessage() doesn't
- # ISSUE: Should we try to handle "message/lang" here too?
- $key = str_replace( ' ' , '_' , $wgContLang->lcfirst( $key ) );
-
- if( is_object( $lang ) ) {
- $message = $lang->getMessage( $key );
- } else {
- $message = false;
- }
+ if ( !is_object( $wgMessageCache ) ) {
+ throw new MWException( 'Trying to get message before message cache is initialised' );
}
+ $message = $wgMessageCache->get( $key, $useDB, $langCode );
+ if( $message === false ) {
+ $message = '&lt;' . htmlspecialchars( $key ) . '&gt;';
+ } elseif ( $transform ) {
+ $message = $wgMessageCache->transform( $message );
+ }
return $message;
}
@@ -780,7 +807,7 @@ function wfMsgReplaceArgs( $message, $args ) {
}
$replacementKeys = array();
foreach( $args as $n => $param ) {
- $replacementKeys['$' . ($n + 1)] = $param;
+ $replacementKeys['$' . ( $n + 1 )] = $param;
}
$message = strtr( $message, $replacementKeys );
}
@@ -827,17 +854,17 @@ function wfMsgWikiHtml( $key ) {
* Returns message in the requested format
* @param $key String: key of the message
* @param $options Array: processing rules. Can take the following options:
- * <i>parse</i>: parses wikitext to html
- * <i>parseinline</i>: parses wikitext to html and removes the surrounding
+ * <i>parse</i>: parses wikitext to HTML
+ * <i>parseinline</i>: parses wikitext to HTML and removes the surrounding
* p's added by parser or tidy
* <i>escape</i>: filters message through htmlspecialchars
- * <i>escapenoentities</i>: same, but allows entity references like &nbsp; through
+ * <i>escapenoentities</i>: same, but allows entity references like &#160; through
* <i>replaceafter</i>: parameters are substituted after parsing or escaping
* <i>parsemag</i>: transform the message using magic phrases
* <i>content</i>: fetch message for content language instead of interface
* Also can accept a single associative argument, of the form 'language' => 'xx':
* <i>language</i>: Language object or language code to fetch message for
- * (overriden by <i>content</i>), its behaviour with parser, parseinline
+ * (overriden by <i>content</i>), its behaviour with parse, parseinline
* and parsemag is undefined.
* Behavior for conflicting options (e.g., parse+parseinline) is undefined.
*/
@@ -861,10 +888,10 @@ function wfMsgExt( $key, $options ) {
}
}
- if( in_array('content', $options, true ) ) {
+ if( in_array( 'content', $options, true ) ) {
$forContent = true;
$langCode = true;
- } elseif( array_key_exists('language', $options) ) {
+ } elseif( array_key_exists( 'language', $options ) ) {
$forContent = false;
$langCode = wfGetLangObj( $options['language'] );
} else {
@@ -874,19 +901,19 @@ function wfMsgExt( $key, $options ) {
$string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
- if( !in_array('replaceafter', $options, true ) ) {
+ if( !in_array( 'replaceafter', $options, true ) ) {
$string = wfMsgReplaceArgs( $string, $args );
}
- if( in_array('parse', $options, true ) ) {
+ if( in_array( 'parse', $options, true ) ) {
$string = $wgOut->parse( $string, true, !$forContent );
- } elseif ( in_array('parseinline', $options, true ) ) {
+ } elseif ( in_array( 'parseinline', $options, true ) ) {
$string = $wgOut->parse( $string, true, !$forContent );
$m = array();
if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
$string = $m[1];
}
- } elseif ( in_array('parsemag', $options, true ) ) {
+ } elseif ( in_array( 'parsemag', $options, true ) ) {
global $wgMessageCache;
if ( isset( $wgMessageCache ) ) {
$string = $wgMessageCache->transform( $string,
@@ -895,13 +922,13 @@ function wfMsgExt( $key, $options ) {
}
}
- if ( in_array('escape', $options, true ) ) {
+ if ( in_array( 'escape', $options, true ) ) {
$string = htmlspecialchars ( $string );
- } elseif ( in_array( 'escapenoentities', $options, true ) ) {
+ } elseif ( in_array( 'escapenoentities', $options, true ) ) {
$string = Sanitizer::escapeHtmlAllowEntities( $string );
}
- if( in_array('replaceafter', $options, true ) ) {
+ if( in_array( 'replaceafter', $options, true ) ) {
$string = wfMsgReplaceArgs( $string, $args );
}
@@ -913,24 +940,26 @@ function wfMsgExt( $key, $options ) {
* Just like exit() but makes a note of it.
* Commits open transactions except if the error parameter is set
*
- * @deprecated Please return control to the caller or throw an exception
+ * @deprecated Please return control to the caller or throw an exception. Will
+ * be removed in 1.19.
*/
-function wfAbruptExit( $error = false ){
+function wfAbruptExit( $error = false ) {
static $called = false;
- if ( $called ){
+ if ( $called ) {
exit( -1 );
}
$called = true;
+ wfDeprecated( __FUNCTION__ );
$bt = wfDebugBacktrace();
if( $bt ) {
- for($i = 0; $i < count($bt) ; $i++){
- $file = isset($bt[$i]['file']) ? $bt[$i]['file'] : "unknown";
- $line = isset($bt[$i]['line']) ? $bt[$i]['line'] : "unknown";
- wfDebug("WARNING: Abrupt exit in $file at line $line\n");
+ for( $i = 0; $i < count( $bt ); $i++ ) {
+ $file = isset( $bt[$i]['file'] ) ? $bt[$i]['file'] : 'unknown';
+ $line = isset( $bt[$i]['line'] ) ? $bt[$i]['line'] : 'unknown';
+ wfDebug( "WARNING: Abrupt exit in $file at line $line\n");
}
} else {
- wfDebug("WARNING: Abrupt exit\n");
+ wfDebug( "WARNING: Abrupt exit\n" );
}
wfLogProfilingData();
@@ -942,9 +971,11 @@ function wfAbruptExit( $error = false ){
}
/**
- * @deprecated Please return control the caller or throw an exception
+ * @deprecated Please return control the caller or throw an exception. Will
+ * be removed in 1.19.
*/
function wfErrorExit() {
+ wfDeprecated( __FUNCTION__ );
wfAbruptExit( true );
}
@@ -953,7 +984,7 @@ function wfErrorExit() {
* Plain die() fails to return nonzero to the shell if you pass a string.
* @param $msg String
*/
-function wfDie( $msg='' ) {
+function wfDie( $msg = '' ) {
echo $msg;
die( 1 );
}
@@ -1008,8 +1039,8 @@ function wfReportTime() {
$elapsed = $now - $wgRequestTime;
return $wgShowHostnames
- ? sprintf( "<!-- Served by %s in %01.3f secs. -->", wfHostname(), $elapsed )
- : sprintf( "<!-- Served in %01.3f secs. -->", $elapsed );
+ ? sprintf( '<!-- Served by %s in %01.3f secs. -->', wfHostname(), $elapsed )
+ : sprintf( '<!-- Served in %01.3f secs. -->', $elapsed );
}
/**
@@ -1062,7 +1093,7 @@ function wfBacktrace() {
foreach( $backtrace as $call ) {
if( isset( $call['file'] ) ) {
$f = explode( DIRECTORY_SEPARATOR, $call['file'] );
- $file = $f[count($f)-1];
+ $file = $f[count( $f ) - 1];
} else {
$file = '-';
}
@@ -1076,7 +1107,9 @@ function wfBacktrace() {
} else {
$msg .= '<li>' . $file . ' line ' . $line . ' calls ';
}
- if( !empty( $call['class'] ) ) $msg .= $call['class'] . '::';
+ if( !empty( $call['class'] ) ) {
+ $msg .= $call['class'] . '::';
+ }
$msg .= $call['function'] . '()';
if ( $wgCommandLineMode ) {
@@ -1103,8 +1136,12 @@ function wfBacktrace() {
*/
function wfShowingResults( $offset, $limit ) {
global $wgLang;
- return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ),
- $wgLang->formatNum( $offset+1 ) );
+ return wfMsgExt(
+ 'showingresults',
+ array( 'parseinline' ),
+ $wgLang->formatNum( $limit ),
+ $wgLang->formatNum( $offset + 1 )
+ );
}
/**
@@ -1112,8 +1149,13 @@ function wfShowingResults( $offset, $limit ) {
*/
function wfShowingResultsNum( $offset, $limit, $num ) {
global $wgLang;
- return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ),
- $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
+ return wfMsgExt(
+ 'showingresultsnum',
+ array( 'parseinline' ),
+ $wgLang->formatNum( $limit ),
+ $wgLang->formatNum( $offset + 1 ),
+ $wgLang->formatNum( $num )
+ );
}
/**
@@ -1129,11 +1171,11 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
$fmtLimit = $wgLang->formatNum( $limit );
// FIXME: Why on earth this needs one message for the text and another one for tooltip??
# Get prev/next link display text
- $prev = wfMsgExt( 'prevn', array('parsemag','escape'), $fmtLimit );
- $next = wfMsgExt( 'nextn', array('parsemag','escape'), $fmtLimit );
+ $prev = wfMsgExt( 'prevn', array( 'parsemag', 'escape' ), $fmtLimit );
+ $next = wfMsgExt( 'nextn', array( 'parsemag', 'escape' ), $fmtLimit );
# Get prev/next link title text
- $pTitle = wfMsgExt( 'prevn-title', array('parsemag','escape'), $fmtLimit );
- $nTitle = wfMsgExt( 'nextn-title', array('parsemag','escape'), $fmtLimit );
+ $pTitle = wfMsgExt( 'prevn-title', array( 'parsemag', 'escape' ), $fmtLimit );
+ $nTitle = wfMsgExt( 'nextn-title', array( 'parsemag', 'escape' ), $fmtLimit );
# Fetch the title object
if( is_object( $link ) ) {
$title =& $link;
@@ -1146,28 +1188,28 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
# Make 'previous' link
if( 0 != $offset ) {
$po = $offset - $limit;
- $po = max($po,0);
+ $po = max( $po, 0 );
$q = "limit={$limit}&offset={$po}";
if( $query != '' ) {
- $q .= '&'.$query;
+ $q .= '&' . $query;
}
- $plink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$pTitle}\" class=\"mw-prevlink\">{$prev}</a>";
- } else {
+ $plink = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$pTitle}\" class=\"mw-prevlink\">{$prev}</a>";
+ } else {
$plink = $prev;
}
# Make 'next' link
$no = $offset + $limit;
$q = "limit={$limit}&offset={$no}";
if( $query != '' ) {
- $q .= '&'.$query;
+ $q .= '&' . $query;
}
if( $atend ) {
$nlink = $next;
} else {
- $nlink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$nTitle}\" class=\"mw-nextlink\">{$next}</a>";
+ $nlink = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$nTitle}\" class=\"mw-nextlink\">{$next}</a>";
}
# Make links to set number of items per page
- $nums = $wgLang->pipeList( array(
+ $nums = $wgLang->pipeList( array(
wfNumLink( $offset, 20, $title, $query ),
wfNumLink( $offset, 50, $title, $query ),
wfNumLink( $offset, 100, $title, $query ),
@@ -1186,15 +1228,15 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
*/
function wfNumLink( $offset, $limit, $title, $query = '' ) {
global $wgLang;
- if( $query == '' ) {
+ if( $query == '' ) {
$q = '';
- } else {
+ } else {
$q = $query.'&';
}
$q .= "limit={$limit}&offset={$offset}";
$fmtLimit = $wgLang->formatNum( $limit );
- $lTitle = wfMsgExt('shown-title',array('parsemag','escape'),$limit);
- $s = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$lTitle}\" class=\"mw-numlink\">{$fmtLimit}</a>";
+ $lTitle = wfMsgExt( 'shown-title', array( 'parsemag', 'escape' ), $limit );
+ $s = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$lTitle}\" class=\"mw-numlink\">{$fmtLimit}</a>";
return $s;
}
@@ -1205,19 +1247,28 @@ function wfNumLink( $offset, $limit, $title, $query = '' ) {
* @return bool Whereas client accept gzip compression
*/
function wfClientAcceptsGzip() {
- if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
- # FIXME: we may want to blacklist some broken browsers
- $m = array();
- if( preg_match(
- '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
- $_SERVER['HTTP_ACCEPT_ENCODING'],
- $m ) ) {
- if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) return false;
- wfDebug( " accepts gzip\n" );
- return true;
+ static $result = null;
+ if ( $result === null ) {
+ $result = false;
+ if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
+ # FIXME: we may want to blacklist some broken browsers
+ $m = array();
+ if( preg_match(
+ '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
+ $_SERVER['HTTP_ACCEPT_ENCODING'],
+ $m )
+ )
+ {
+ if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
+ $result = false;
+ return $result;
+ }
+ wfDebug( " accepts gzip\n" );
+ $result = true;
+ }
}
}
- return false;
+ return $result;
}
/**
@@ -1246,9 +1297,12 @@ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
*/
function wfEscapeWikiText( $text ) {
$text = str_replace(
- array( '[', '|', ']', '\'', 'ISBN ', 'RFC ', '://', "\n=", '{{' ), # }}
- array( '&#91;', '&#124;', '&#93;', '&#39;', 'ISBN&#32;', 'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;' ),
- htmlspecialchars($text) );
+ array( '[', '|', ']', '\'', 'ISBN ',
+ 'RFC ', '://', "\n=", '{{', '}}' ),
+ array( '&#91;', '&#124;', '&#93;', '&#39;', 'ISBN&#32;',
+ 'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;', '&#125;&#125;' ),
+ htmlspecialchars( $text )
+ );
return $text;
}
@@ -1266,7 +1320,9 @@ function wfQuotedPrintable( $string, $charset = '' ) {
$illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
$replace = $illegal . '\t ?_';
- if( !preg_match( "/[$illegal]/", $string ) ) return $string;
+ if( !preg_match( "/[$illegal]/", $string ) ) {
+ return $string;
+ }
$out = "=?$charset?Q?";
$out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
$out .= '?=';
@@ -1279,7 +1335,7 @@ function wfQuotedPrintable( $string, $charset = '' ) {
* @return float
*/
function wfTime() {
- return microtime(true);
+ return microtime( true );
}
/**
@@ -1298,7 +1354,7 @@ function wfSetVar( &$dest, $source ) {
* As for wfSetVar except setting a bit
*/
function wfSetBit( &$dest, $bit, $state = true ) {
- $temp = (bool)($dest & $bit );
+ $temp = (bool)( $dest & $bit );
if ( !is_null( $state ) ) {
if ( $state ) {
$dest |= $bit;
@@ -1314,8 +1370,7 @@ function wfSetBit( &$dest, $bit, $state = true ) {
* "days=7&limit=100". Options in the first array override options in the second.
* Options set to "" will not be output.
*/
-function wfArrayToCGI( $array1, $array2 = null )
-{
+function wfArrayToCGI( $array1, $array2 = null ) {
if ( !is_null( $array2 ) ) {
$array1 = $array1 + $array2;
}
@@ -1357,7 +1412,7 @@ function wfArrayToCGI( $array1, $array2 = null )
* @return array Array version of input
*/
function wfCgiToArray( $query ) {
- if( isset( $query[0] ) and $query[0] == '?' ) {
+ if( isset( $query[0] ) && $query[0] == '?' ) {
$query = substr( $query, 1 );
}
$bits = explode( '&', $query );
@@ -1399,16 +1454,19 @@ function wfAppendQuery( $url, $query ) {
/**
* Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
- * is correct. Also doesn't handle any type of relative URL except one
- * starting with a single "/": this won't work with current-path-relative URLs
- * like "subdir/foo.html", protocol-relative URLs like
- * "//en.wikipedia.org/wiki/", etc. TODO: improve this!
+ * and $wgProto are correct.
+ *
+ * @todo this won't work with current-path-relative URLs
+ * like "subdir/foo.html", etc.
*
* @param $url String: either fully-qualified or a local path + query
* @return string Fully-qualified URL
*/
function wfExpandUrl( $url ) {
- if( substr( $url, 0, 1 ) == '/' ) {
+ if( substr( $url, 0, 2 ) == '//' ) {
+ global $wgProto;
+ return $wgProto . ':' . $url;
+ } elseif( substr( $url, 0, 1 ) == '/' ) {
global $wgServer;
return $wgServer . $url;
} else {
@@ -1417,19 +1475,11 @@ function wfExpandUrl( $url ) {
}
/**
- * This is obsolete, use SquidUpdate::purge()
- * @deprecated
- */
-function wfPurgeSquidServers ($urlArr) {
- SquidUpdate::purge( $urlArr );
-}
-
-/**
* Windows-compatible version of escapeshellarg()
* Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
* function puts single quotes in regardless of OS.
*
- * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
+ * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
* earlier distro releases of PHP)
*/
function wfEscapeShellArg( ) {
@@ -1451,14 +1501,19 @@ function wfEscapeShellArg( ) {
// Double the backslashes before any double quotes. Escape the double quotes.
$tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
$arg = '';
- $delim = false;
+ $iteration = 0;
foreach ( $tokens as $token ) {
- if ( $delim ) {
+ if ( $iteration % 2 == 1 ) {
+ // Delimiter, a double quote preceded by zero or more slashes
$arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
- } else {
+ } elseif ( $iteration % 4 == 2 ) {
+ // ^ in $token will be outside quotes, need to be escaped
+ $arg .= str_replace( '^', '^^', $token );
+ } else { // $iteration % 4 == 0
+ // ^ in $token will appear inside double quotes, so leave as is
$arg .= $token;
}
- $delim = !$delim;
+ $iteration++;
}
// Double the backslashes before the end of the string, because
// we will soon add a quote
@@ -1480,12 +1535,16 @@ function wfEscapeShellArg( ) {
* wfMerge attempts to merge differences between three texts.
* Returns true for a clean merge and false for failure or a conflict.
*/
-function wfMerge( $old, $mine, $yours, &$result ){
+function wfMerge( $old, $mine, $yours, &$result ) {
global $wgDiff3;
# This check may also protect against code injection in
# case of broken installations.
- if( !$wgDiff3 || !file_exists( $wgDiff3 ) ) {
+ wfSuppressWarnings();
+ $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
+ wfRestoreWarnings();
+
+ if( !$haveDiff3 ) {
wfDebug( "diff3 not found\n" );
return false;
}
@@ -1496,18 +1555,21 @@ function wfMerge( $old, $mine, $yours, &$result ){
$mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
$yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
- fwrite( $oldtextFile, $old ); fclose( $oldtextFile );
- fwrite( $mytextFile, $mine ); fclose( $mytextFile );
- fwrite( $yourtextFile, $yours ); fclose( $yourtextFile );
+ fwrite( $oldtextFile, $old );
+ fclose( $oldtextFile );
+ fwrite( $mytextFile, $mine );
+ fclose( $mytextFile );
+ fwrite( $yourtextFile, $yours );
+ fclose( $yourtextFile );
# Check for a conflict
$cmd = $wgDiff3 . ' -a --overlap-only ' .
- wfEscapeShellArg( $mytextName ) . ' ' .
- wfEscapeShellArg( $oldtextName ) . ' ' .
- wfEscapeShellArg( $yourtextName );
+ wfEscapeShellArg( $mytextName ) . ' ' .
+ wfEscapeShellArg( $oldtextName ) . ' ' .
+ wfEscapeShellArg( $yourtextName );
$handle = popen( $cmd, 'r' );
- if( fgets( $handle, 1024 ) ){
+ if( fgets( $handle, 1024 ) ) {
$conflict = true;
} else {
$conflict = false;
@@ -1516,7 +1578,7 @@ function wfMerge( $old, $mine, $yours, &$result ){
# Merge differences
$cmd = $wgDiff3 . ' -a -e --merge ' .
- wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
+ wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
$handle = popen( $cmd, 'r' );
$result = '';
do {
@@ -1527,13 +1589,15 @@ function wfMerge( $old, $mine, $yours, &$result ){
$result .= $data;
} while ( true );
pclose( $handle );
- unlink( $mytextName ); unlink( $oldtextName ); unlink( $yourtextName );
+ unlink( $mytextName );
+ unlink( $oldtextName );
+ unlink( $yourtextName );
- if ( $result === '' && $old !== '' && $conflict == false ) {
+ if ( $result === '' && $old !== '' && !$conflict ) {
wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
$conflict = true;
}
- return ! $conflict;
+ return !$conflict;
}
/**
@@ -1545,15 +1609,18 @@ function wfMerge( $old, $mine, $yours, &$result ){
* @return String: unified diff of $before and $after
*/
function wfDiff( $before, $after, $params = '-u' ) {
- if ($before == $after) {
+ if ( $before == $after ) {
return '';
}
-
+
global $wgDiff;
+ wfSuppressWarnings();
+ $haveDiff = $wgDiff && file_exists( $wgDiff );
+ wfRestoreWarnings();
# This check may also protect against code injection in
# case of broken installations.
- if( !file_exists( $wgDiff ) ){
+ if( !$haveDiff ) {
wfDebug( "diff executable not found\n" );
$diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
$format = new UnifiedDiffFormatter();
@@ -1565,16 +1632,18 @@ function wfDiff( $before, $after, $params = '-u' ) {
$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
$newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
- fwrite( $oldtextFile, $before ); fclose( $oldtextFile );
- fwrite( $newtextFile, $after ); fclose( $newtextFile );
-
+ fwrite( $oldtextFile, $before );
+ fclose( $oldtextFile );
+ fwrite( $newtextFile, $after );
+ fclose( $newtextFile );
+
// Get the diff of the two files
- $cmd = "$wgDiff " . $params . ' ' .wfEscapeShellArg( $oldtextName, $newtextName );
-
+ $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
+
$h = popen( $cmd, 'r' );
-
+
$diff = '';
-
+
do {
$data = fread( $h, 8192 );
if ( strlen( $data ) == 0 ) {
@@ -1582,23 +1651,23 @@ function wfDiff( $before, $after, $params = '-u' ) {
}
$diff .= $data;
} while ( true );
-
+
// Clean up
pclose( $h );
unlink( $oldtextName );
unlink( $newtextName );
-
+
// Kill the --- and +++ lines. They're not useful.
$diff_lines = explode( "\n", $diff );
- if (strpos( $diff_lines[0], '---' ) === 0) {
- unset($diff_lines[0]);
+ if ( strpos( $diff_lines[0], '---' ) === 0 ) {
+ unset( $diff_lines[0] );
}
- if (strpos( $diff_lines[1], '+++' ) === 0) {
- unset($diff_lines[1]);
+ if ( strpos( $diff_lines[1], '+++' ) === 0 ) {
+ unset( $diff_lines[1] );
}
-
+
$diff = implode( "\n", $diff_lines );
-
+
return $diff;
}
@@ -1610,7 +1679,7 @@ function wfDiff( $before, $after, $params = '-u' ) {
*/
function wfVarDump( $var ) {
global $wgOut;
- $s = str_replace("\n","<br />\n", var_export( $var, true ) . "\n");
+ $s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
if ( headers_sent() || !@is_object( $wgOut ) ) {
print $s;
} else {
@@ -1630,11 +1699,11 @@ function wfHttpError( $code, $label, $desc ) {
header( 'Content-type: text/html; charset=utf-8' );
print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
- "<html><head><title>" .
+ '<html><head><title>' .
htmlspecialchars( $label ) .
- "</title></head><body><h1>" .
+ '</title></head><body><h1>' .
htmlspecialchars( $label ) .
- "</h1><p>" .
+ '</h1><p>' .
nl2br( htmlspecialchars( $desc ) ) .
"</p></body></html>\n";
}
@@ -1656,7 +1725,7 @@ function wfHttpError( $code, $label, $desc ) {
*
* @param $resetGzipEncoding Bool
*/
-function wfResetOutputBuffers( $resetGzipEncoding=true ) {
+function wfResetOutputBuffers( $resetGzipEncoding = true ) {
if( $resetGzipEncoding ) {
// Suppress Content-Encoding and Content-Length
// headers from 1.10+s wfOutputHandler
@@ -1681,7 +1750,13 @@ function wfResetOutputBuffers( $resetGzipEncoding=true ) {
if( $status['name'] == 'ob_gzhandler' ) {
// Reset the 'Content-Encoding' field set by this handler
// so we can start fresh.
- header( 'Content-Encoding:' );
+ if ( function_exists( 'header_remove' ) ) {
+ // Available since PHP 5.3.0
+ header_remove( 'Content-Encoding' );
+ } else {
+ // We need to provide a valid content-coding. See bug 28069
+ header( 'Content-Encoding: identity' );
+ }
break;
}
}
@@ -1725,7 +1800,7 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) {
if( !isset( $qpart ) ) {
$prefs[$value] = 1.0;
} elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
- $prefs[$value] = floatval($match[1]);
+ $prefs[$value] = floatval( $match[1] );
}
}
@@ -1745,7 +1820,7 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) {
* @private
*/
function mimeTypeMatch( $type, $avail ) {
- if( array_key_exists($type, $avail) ) {
+ if( array_key_exists( $type, $avail ) ) {
return $type;
} else {
$parts = explode( '/', $type );
@@ -1844,7 +1919,7 @@ function wfSuppressWarnings( $end = false ) {
}
} else {
if ( !$suppressCount ) {
- $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE ) );
+ $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE ) );
}
++$suppressCount;
}
@@ -1862,29 +1937,29 @@ function wfRestoreWarnings() {
/**
* Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
*/
-define('TS_UNIX', 0);
+define( 'TS_UNIX', 0 );
/**
* MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
*/
-define('TS_MW', 1);
+define( 'TS_MW', 1 );
/**
* MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
*/
-define('TS_DB', 2);
+define( 'TS_DB', 2 );
/**
* RFC 2822 format, for E-mail and HTTP headers
*/
-define('TS_RFC2822', 3);
+define( 'TS_RFC2822', 3 );
/**
* ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
*
* This is used by Special:Export
*/
-define('TS_ISO_8601', 4);
+define( 'TS_ISO_8601', 4 );
/**
* An Exif timestamp (YYYY:MM:DD HH:MM:SS)
@@ -1893,91 +1968,152 @@ define('TS_ISO_8601', 4);
* DateTime tag and page 36 for the DateTimeOriginal and
* DateTimeDigitized tags.
*/
-define('TS_EXIF', 5);
+define( 'TS_EXIF', 5 );
/**
* Oracle format time.
*/
-define('TS_ORACLE', 6);
+define( 'TS_ORACLE', 6 );
/**
* Postgres format time.
*/
-define('TS_POSTGRES', 7);
+define( 'TS_POSTGRES', 7 );
/**
* DB2 format time
*/
-define('TS_DB2', 8);
+define( 'TS_DB2', 8 );
+
+/**
+ * ISO 8601 basic format with no timezone: 19860209T200000Z
+ *
+ * This is used by ResourceLoader
+ */
+define( 'TS_ISO_8601_BASIC', 9 );
/**
* @param $outputtype Mixed: A timestamp in one of the supported formats, the
* function will autodetect which format is supplied and act
* accordingly.
* @param $ts Mixed: the timestamp to convert or 0 for the current timestamp
- * @return String: in the format specified in $outputtype
+ * @return Mixed: String / false The same date in the format specified in $outputtype or false
*/
function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
$uts = 0;
$da = array();
- if ($ts==0) {
- $uts=time();
- } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
+ $strtime = '';
+
+ if ( !$ts ) { // We want to catch 0, '', null... but not date strings starting with a letter.
+ $uts = time();
+ $strtime = "@$uts";
+ } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
# TS_DB
- } elseif (preg_match('/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
+ } elseif ( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
# TS_EXIF
- } elseif (preg_match('/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D',$ts,$da)) {
+ } elseif ( preg_match( '/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D', $ts, $da ) ) {
# TS_MW
- } elseif (preg_match('/^\d{1,13}$/D',$ts)) {
+ } elseif ( preg_match( '/^-?\d{1,13}$/D', $ts ) ) {
# TS_UNIX
$uts = $ts;
- } elseif (preg_match('/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts)) {
+ $strtime = "@$ts"; // Undocumented?
+ } elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
# TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
- $uts = strtotime(preg_replace('/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
- str_replace("+00:00", "UTC", $ts)));
- } elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z$/', $ts, $da)) {
+ $strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
+ str_replace( '+00:00', 'UTC', $ts ) );
+ } elseif ( preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
# TS_ISO_8601
- } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/',$ts,$da)) {
+ } elseif ( preg_match( '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
+ #TS_ISO_8601_BASIC
+ } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/', $ts, $da ) ) {
# TS_POSTGRES
- } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/',$ts,$da)) {
+ } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/', $ts, $da ) ) {
# TS_POSTGRES
+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.\d\d\d$/',$ts,$da)) {
+ # TS_DB2
+ } elseif ( preg_match( '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' . # Day of week
+ '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' . # dd Mon yyyy
+ '[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S', $ts ) ) { # hh:mm:ss
+ # TS_RFC2822, accepting a trailing comment. See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
+ # The regex is a superset of rfc2822 for readability
+ $strtime = strtok( $ts, ';' );
+ } elseif ( preg_match( '/^[A-Z][a-z]{5,8}, \d\d-[A-Z][a-z]{2}-\d{2} \d\d:\d\d:\d\d/', $ts ) ) {
+ # TS_RFC850
+ $strtime = $ts;
+ } elseif ( preg_match( '/^[A-Z][a-z]{2} [A-Z][a-z]{2} +\d{1,2} \d\d:\d\d:\d\d \d{4}/', $ts ) ) {
+ # asctime
+ $strtime = $ts;
} else {
- # Bogus value; fall back to the epoch...
+ # Bogus value...
wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
- $uts = 0;
+
+ return false;
}
- if (count( $da ) ) {
- // Warning! gmmktime() acts oddly if the month or day is set to 0
- // We may want to handle that explicitly at some point
- $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
- (int)$da[2],(int)$da[3],(int)$da[1]);
+
+
+ static $formats = array(
+ TS_UNIX => 'U',
+ TS_MW => 'YmdHis',
+ TS_DB => 'Y-m-d H:i:s',
+ TS_ISO_8601 => 'Y-m-d\TH:i:s\Z',
+ TS_ISO_8601_BASIC => 'Ymd\THis\Z',
+ TS_EXIF => 'Y:m:d H:i:s', // This shouldn't ever be used, but is included for completeness
+ TS_RFC2822 => 'D, d M Y H:i:s',
+ TS_ORACLE => 'd-m-Y H:i:s.000000', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
+ TS_POSTGRES => 'Y-m-d H:i:s',
+ TS_DB2 => 'Y-m-d H:i:s',
+ );
+
+ if ( !isset( $formats[$outputtype] ) ) {
+ throw new MWException( 'wfTimestamp() called with illegal output type.' );
}
- switch($outputtype) {
- case TS_UNIX:
+ if ( function_exists( "date_create" ) ) {
+ if ( count( $da ) ) {
+ $ds = sprintf("%04d-%02d-%02dT%02d:%02d:%02d.00+00:00",
+ (int)$da[1], (int)$da[2], (int)$da[3],
+ (int)$da[4], (int)$da[5], (int)$da[6]);
+
+ $d = date_create( $ds, new DateTimeZone( 'GMT' ) );
+ } elseif ( $strtime ) {
+ $d = date_create( $strtime, new DateTimeZone( 'GMT' ) );
+ } else {
+ return false;
+ }
+
+ if ( !$d ) {
+ wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
+ return false;
+ }
+
+ $output = $d->format( $formats[$outputtype] );
+ } else {
+ if ( count( $da ) ) {
+ // Warning! gmmktime() acts oddly if the month or day is set to 0
+ // We may want to handle that explicitly at some point
+ $uts = gmmktime( (int)$da[4], (int)$da[5], (int)$da[6],
+ (int)$da[2], (int)$da[3], (int)$da[1] );
+ } elseif ( $strtime ) {
+ $uts = strtotime( $strtime );
+ }
+
+ if ( $uts === false ) {
+ wfDebug("wfTimestamp() can't parse the timestamp (non 32-bit time? Update php): $outputtype; $ts\n");
+ return false;
+ }
+
+ if ( TS_UNIX == $outputtype ) {
return $uts;
- case TS_MW:
- return gmdate( 'YmdHis', $uts );
- case TS_DB:
- return gmdate( 'Y-m-d H:i:s', $uts );
- case TS_ISO_8601:
- return gmdate( 'Y-m-d\TH:i:s\Z', $uts );
- // This shouldn't ever be used, but is included for completeness
- case TS_EXIF:
- return gmdate( 'Y:m:d H:i:s', $uts );
- case TS_RFC2822:
- return gmdate( 'D, d M Y H:i:s', $uts ) . ' GMT';
- case TS_ORACLE:
- return gmdate( 'd-m-Y H:i:s.000000', $uts);
- //return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00';
- case TS_POSTGRES:
- return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT';
- case TS_DB2:
- return gmdate( 'Y-m-d H:i:s', $uts);
- default:
- throw new MWException( 'wfTimestamp() called with illegal output type.');
+ }
+ $output = gmdate( $formats[$outputtype], $uts );
}
+
+ if ( ( $outputtype == TS_RFC2822 ) || ( $outputtype == TS_POSTGRES ) ) {
+ $output .= ' GMT';
+ }
+
+ return $output;
}
/**
@@ -2001,11 +2137,11 @@ function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
* @return Bool: true if it's Windows, False otherwise.
*/
function wfIsWindows() {
- if (substr(php_uname(), 0, 7) == 'Windows') {
- return true;
- } else {
- return false;
+ static $isWindows = null;
+ if ( $isWindows === null ) {
+ $isWindows = substr( php_uname(), 0, 7 ) == 'Windows';
}
+ return $isWindows;
}
/**
@@ -2059,11 +2195,11 @@ function wfGetCachedNotice( $name ) {
$parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
$notice = $parsed;
} else {
- wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available'."\n" );
+ wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' . "\n" );
$notice = '';
}
}
-
+ $notice = '<div id="localNotice">' .$notice . '</div>';
wfProfileOut( $fname );
return $notice;
}
@@ -2072,18 +2208,19 @@ function wfGetNamespaceNotice() {
global $wgTitle;
# Paranoia
- if ( !isset( $wgTitle ) || !is_object( $wgTitle ) )
- return "";
+ if ( !isset( $wgTitle ) || !is_object( $wgTitle ) ) {
+ return '';
+ }
$fname = 'wfGetNamespaceNotice';
wfProfileIn( $fname );
- $key = "namespacenotice-" . $wgTitle->getNsText();
+ $key = 'namespacenotice-' . $wgTitle->getNsText();
$namespaceNotice = wfGetCachedNotice( $key );
- if ( $namespaceNotice && substr ( $namespaceNotice , 0 ,7 ) != "<p>&lt;" ) {
- $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . "</div>";
+ if ( $namespaceNotice && substr( $namespaceNotice, 0, 7 ) != '<p>&lt;' ) {
+ $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . '</div>';
} else {
- $namespaceNotice = "";
+ $namespaceNotice = '';
}
wfProfileOut( $fname );
@@ -2091,7 +2228,7 @@ function wfGetNamespaceNotice() {
}
function wfGetSiteNotice() {
- global $wgUser, $wgSiteNotice;
+ global $wgUser;
$fname = 'wfGetSiteNotice';
wfProfileIn( $fname );
$siteNotice = '';
@@ -2119,40 +2256,42 @@ function wfGetSiteNotice() {
/**
* BC wrapper for MimeMagic::singleton()
- * @deprecated
+ * @deprecated No longer needed as of 1.17 (r68836).
*/
function &wfGetMimeMagic() {
+ wfDeprecated( __FUNCTION__ );
return MimeMagic::singleton();
}
/**
- * Tries to get the system directory for temporary files. For PHP >= 5.2.1,
- * we'll use sys_get_temp_dir(). The TMPDIR, TMP, and TEMP environment
- * variables are then checked in sequence, and if none are set /tmp is
- * returned as the generic Unix default.
+ * Tries to get the system directory for temporary files. The TMPDIR, TMP, and
+ * TEMP environment variables are then checked in sequence, and if none are set
+ * try sys_get_temp_dir() for PHP >= 5.2.1. All else fails, return /tmp for Unix
+ * or C:\Windows\Temp for Windows and hope for the best.
+ * It is common to call it with tempnam().
*
- * NOTE: When possible, use the tempfile() function to create temporary
- * files to avoid race conditions on file creation, etc.
+ * NOTE: When possible, use instead the tmpfile() function to create
+ * temporary files to avoid race conditions on file creation, etc.
*
* @return String
*/
function wfTempDir() {
- if( function_exists( 'sys_get_temp_dir' ) ) {
- return sys_get_temp_dir();
- }
foreach( array( 'TMPDIR', 'TMP', 'TEMP' ) as $var ) {
$tmp = getenv( $var );
if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
return $tmp;
}
}
- # Hope this is Unix of some kind!
- return '/tmp';
+ if( function_exists( 'sys_get_temp_dir' ) ) {
+ return sys_get_temp_dir();
+ }
+ # Usual defaults
+ return wfIsWindows() ? 'C:\Windows\Temp' : '/tmp';
}
/**
* Make directory, and make all parent directories if they don't exist
- *
+ *
* @param $dir String: full path to directory to create
* @param $mode Integer: chmod value to use, default is $wgDirectoryMode
* @param $caller String: optional caller param for debugging.
@@ -2165,15 +2304,21 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) {
wfDebug( "$caller: called wfMkdirParents($dir)" );
}
- if( strval( $dir ) === '' || file_exists( $dir ) )
+ if( strval( $dir ) === '' || file_exists( $dir ) ) {
return true;
+ }
$dir = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $dir );
- if ( is_null( $mode ) )
+ if ( is_null( $mode ) ) {
$mode = $wgDirectoryMode;
+ }
+
+ // Turn off the normal warning, we're doing our own below
+ wfSuppressWarnings();
+ $ok = mkdir( $dir, $mode, true ); // PHP5 <3
+ wfRestoreWarnings();
- $ok = mkdir( $dir, $mode, true ); // PHP5 <3
if( !$ok ) {
// PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
trigger_error( __FUNCTION__ . ": failed to mkdir \"$dir\" mode $mode", E_USER_WARNING );
@@ -2190,13 +2335,29 @@ function wfIncrStats( $key ) {
if( $wgStatsMethod == 'udp' ) {
global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname;
static $socket;
- if (!$socket) {
- $socket=socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
- $statline="stats/{$wgDBname} - 1 1 1 1 1 -total\n";
- socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort);
+ if ( !$socket ) {
+ $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
+ $statline = "stats/{$wgDBname} - 1 1 1 1 1 -total\n";
+ socket_sendto(
+ $socket,
+ $statline,
+ strlen( $statline ),
+ 0,
+ $wgUDPProfilerHost,
+ $wgUDPProfilerPort
+ );
}
- $statline="stats/{$wgDBname} - 1 1 1 1 1 {$key}\n";
- @socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort);
+ $statline = "stats/{$wgDBname} - 1 1 1 1 1 {$key}\n";
+ wfSuppressWarnings();
+ socket_sendto(
+ $socket,
+ $statline,
+ strlen( $statline ),
+ 0,
+ $wgUDPProfilerHost,
+ $wgUDPProfilerPort
+ );
+ wfRestoreWarnings();
} elseif( $wgStatsMethod == 'cache' ) {
global $wgMemc;
$key = wfMemcKey( 'stats', $key );
@@ -2230,7 +2391,7 @@ function wfPercent( $nr, $acc = 2, $round = true ) {
function wfEncryptPassword( $userid, $password ) {
wfDeprecated(__FUNCTION__);
# Just wrap around User::oldCrypt()
- return User::oldCrypt($password, $userid);
+ return User::oldCrypt( $password, $userid );
}
/**
@@ -2238,7 +2399,7 @@ function wfEncryptPassword( $userid, $password ) {
*/
function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
if ( is_null( $changed ) ) {
- throw new MWException('GlobalFunctions::wfAppendToArrayIfNotDefault got null');
+ throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
}
if ( $default[$key] !== $value ) {
$changed[$key] = $value;
@@ -2250,12 +2411,12 @@ function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
* looked up didn't exist but a XHTML string, this function checks for the
* nonexistance of messages by looking at wfMsg() output
*
- * @param $msg String: the message key looked up
- * @param $wfMsgOut String: the output of wfMsg*()
- * @return Boolean
+ * @param $key String: the message key looked up
+ * @return Boolean True if the message *doesn't* exist.
*/
-function wfEmptyMsg( $msg, $wfMsgOut ) {
- return $wfMsgOut === htmlspecialchars( "<$msg>" );
+function wfEmptyMsg( $key ) {
+ global $wgMessageCache;
+ return $wgMessageCache->get( $key, /*useDB*/true, /*content*/false ) === false;
}
/**
@@ -2271,7 +2432,7 @@ function in_string( $needle, $str ) {
function wfSpecialList( $page, $details ) {
global $wgContLang;
- $details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : "";
+ $details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : '';
return $page . $details;
}
@@ -2284,15 +2445,17 @@ function wfUrlProtocols() {
global $wgUrlProtocols;
static $retval = null;
- if ( !is_null( $retval ) )
+ if ( !is_null( $retval ) ) {
return $retval;
+ }
// Support old-style $wgUrlProtocols strings, for backwards compatibility
// with LocalSettings files from 1.5
if ( is_array( $wgUrlProtocols ) ) {
$protocols = array();
- foreach ($wgUrlProtocols as $protocol)
+ foreach ( $wgUrlProtocols as $protocol ) {
$protocols[] = preg_quote( $protocol, '/' );
+ }
$retval = implode( '|', $protocols );
} else {
@@ -2334,14 +2497,40 @@ function wfIniGetBool( $setting ) {
}
/**
+ * Wrapper function for PHP's dl(). This doesn't work in most situations from
+ * PHP 5.3 onward, and is usually disabled in shared environments anyway.
+ *
+ * @param $extension String A PHP extension. The file suffix (.so or .dll)
+ * should be omitted
+ * @return Bool - Whether or not the extension is loaded
+ */
+function wfDl( $extension ) {
+ if( extension_loaded( $extension ) ) {
+ return true;
+ }
+
+ $canDl = ( function_exists( 'dl' ) && is_callable( 'dl' )
+ && wfIniGetBool( 'enable_dl' ) && !wfIniGetBool( 'safe_mode' ) );
+
+ if( $canDl ) {
+ wfSuppressWarnings();
+ dl( $extension . '.' . PHP_SHLIB_SUFFIX );
+ wfRestoreWarnings();
+ }
+ return extension_loaded( $extension );
+}
+
+/**
* Execute a shell command, with time and memory limits mirrored from the PHP
* configuration if supported.
- * @param $cmd Command line, properly escaped for shell.
+ * @param $cmd String Command line, properly escaped for shell.
* @param &$retval optional, will receive the program's exit code.
* (non-zero is usually failure)
+ * @param $environ Array optional environment variables which should be
+ * added to the executed command environment.
* @return collected stdout as a string (trailing newlines stripped)
*/
-function wfShellExec( $cmd, &$retval=null ) {
+function wfShellExec( $cmd, &$retval = null, $environ = array() ) {
global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
static $disabled;
@@ -2349,24 +2538,55 @@ function wfShellExec( $cmd, &$retval=null ) {
$disabled = false;
if( wfIniGetBool( 'safe_mode' ) ) {
wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
- $disabled = true;
- }
- $functions = explode( ',', ini_get( 'disable_functions' ) );
- $functions = array_map( 'trim', $functions );
- $functions = array_map( 'strtolower', $functions );
- if ( in_array( 'passthru', $functions ) ) {
- wfDebug( "passthru is in disabled_functions\n" );
- $disabled = true;
+ $disabled = 'safemode';
+ } else {
+ $functions = explode( ',', ini_get( 'disable_functions' ) );
+ $functions = array_map( 'trim', $functions );
+ $functions = array_map( 'strtolower', $functions );
+ if ( in_array( 'passthru', $functions ) ) {
+ wfDebug( "passthru is in disabled_functions\n" );
+ $disabled = 'passthru';
+ }
}
}
if ( $disabled ) {
$retval = 1;
- return "Unable to run external programs in safe mode.";
+ return $disabled == 'safemode' ?
+ 'Unable to run external programs in safe mode.' :
+ 'Unable to run external programs, passthru() is disabled.';
}
wfInitShellLocale();
- if ( php_uname( 's' ) == 'Linux' ) {
+ $envcmd = '';
+ foreach( $environ as $k => $v ) {
+ if ( wfIsWindows() ) {
+ /* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
+ * appear in the environment variable, so we must use carat escaping as documented in
+ * http://technet.microsoft.com/en-us/library/cc723564.aspx
+ * Note however that the quote isn't listed there, but is needed, and the parentheses
+ * are listed there but doesn't appear to need it.
+ */
+ $envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
+ } else {
+ /* Assume this is a POSIX shell, thus required to accept variable assignments before the command
+ * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
+ */
+ $envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
+ }
+ }
+ $cmd = $envcmd . $cmd;
+
+ if ( wfIsWindows() ) {
+ if ( version_compare( PHP_VERSION, '5.3.0', '<' ) && /* Fixed in 5.3.0 :) */
+ ( version_compare( PHP_VERSION, '5.2.1', '>=' ) || php_uname( 's' ) == 'Windows NT' ) )
+ {
+ # Hack to work around PHP's flawed invocation of cmd.exe
+ # http://news.php.net/php.internals/21796
+ # Windows 9x doesn't accept any kind of quotes
+ $cmd = '"' . $cmd . '"';
+ }
+ } elseif ( php_uname( 's' ) == 'Linux' ) {
$time = intval( $wgMaxShellTime );
$mem = intval( $wgMaxShellMemory );
$filesize = intval( $wgMaxShellFileSize );
@@ -2374,16 +2594,9 @@ function wfShellExec( $cmd, &$retval=null ) {
if ( $time > 0 && $mem > 0 ) {
$script = "$IP/bin/ulimit4.sh";
if ( is_executable( $script ) ) {
- $cmd = escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
+ $cmd = '/bin/bash ' . escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
}
}
- } elseif ( php_uname( 's' ) == 'Windows NT' &&
- version_compare( PHP_VERSION, '5.3.0', '<' ) )
- {
- # This is a hack to work around PHP's flawed invocation of cmd.exe
- # http://news.php.net/php.internals/21796
- # Which is fixed in 5.3.0 :)
- $cmd = '"' . $cmd . '"';
}
wfDebug( "wfShellExec: $cmd\n" );
@@ -2405,7 +2618,9 @@ function wfShellExec( $cmd, &$retval=null ) {
*/
function wfInitShellLocale() {
static $done = false;
- if ( $done ) return;
+ if ( $done ) {
+ return;
+ }
$done = true;
global $wgShellLocale;
if ( !wfIniGetBool( 'safe_mode' ) ) {
@@ -2432,8 +2647,9 @@ function wfInitShellLocale() {
function wfUsePHP( $req_ver ) {
$php_ver = PHP_VERSION;
- if ( version_compare( $php_ver, (string)$req_ver, '<' ) )
- throw new MWException( "PHP $req_ver required--this is only $php_ver" );
+ if ( version_compare( $php_ver, (string)$req_ver, '<' ) ) {
+ throw new MWException( "PHP $req_ver required--this is only $php_ver" );
+ }
}
/**
@@ -2452,15 +2668,9 @@ function wfUsePHP( $req_ver ) {
function wfUseMW( $req_ver ) {
global $wgVersion;
- if ( version_compare( $wgVersion, (string)$req_ver, '<' ) )
+ if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) {
throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
-}
-
-/**
- * @deprecated use StringUtils::escapeRegexReplacement
- */
-function wfRegexReplacement( $string ) {
- return StringUtils::escapeRegexReplacement( $string );
+ }
}
/**
@@ -2475,8 +2685,8 @@ function wfRegexReplacement( $string ) {
* @param $suffix String: to remove if present
* @return String
*/
-function wfBaseName( $path, $suffix='' ) {
- $encSuffix = ($suffix == '')
+function wfBaseName( $path, $suffix = '' ) {
+ $encSuffix = ( $suffix == '' )
? ''
: ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
$matches = array();
@@ -2505,7 +2715,7 @@ function wfRelativePath( $path, $from ) {
$path = rtrim( $path, DIRECTORY_SEPARATOR );
$from = rtrim( $from, DIRECTORY_SEPARATOR );
- $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
+ $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
$against = explode( DIRECTORY_SEPARATOR, $from );
if( $pieces[0] !== $against[0] ) {
@@ -2553,24 +2763,26 @@ function wfArrayMerge( $array1/* ... */ ) {
/**
* Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
* e.g.
- * wfMergeErrorArrays(
- * array( array( 'x' ) ),
- * array( array( 'x', '2' ) ),
- * array( array( 'x' ) ),
+ * wfMergeErrorArrays(
+ * array( array( 'x' ) ),
+ * array( array( 'x', '2' ) ),
+ * array( array( 'x' ) ),
* array( array( 'y') )
* );
* returns:
- * array(
+ * array(
* array( 'x', '2' ),
* array( 'x' ),
* array( 'y' )
* )
*/
-function wfMergeErrorArrays(/*...*/) {
+function wfMergeErrorArrays( /*...*/ ) {
$args = func_get_args();
$out = array();
foreach ( $args as $errors ) {
foreach ( $errors as $params ) {
+ # FIXME: sometimes get nested arrays for $params,
+ # which leads to E_NOTICEs
$spec = implode( "\t", $params );
$out[$spec] = $params;
}
@@ -2604,7 +2816,7 @@ function wfParseUrl( $url ) {
$bits['delimiter'] = ':';
// parse_url detects for news: and mailto: the host part of an url as path
// We have to correct this wrong detection
- if ( isset ( $bits['path'] ) ) {
+ if ( isset( $bits['path'] ) ) {
$bits['host'] = $bits['path'];
$bits['path'] = '';
}
@@ -2625,7 +2837,7 @@ function wfMakeUrlIndex( $url ) {
// For emails reverse domainpart only
if ( $bits['scheme'] == 'mailto' ) {
$mailparts = explode( '@', $bits['host'], 2 );
- if ( count($mailparts) === 2 ) {
+ if ( count( $mailparts ) === 2 ) {
$domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
} else {
// No domain specified, don't mangle it
@@ -2644,39 +2856,53 @@ function wfMakeUrlIndex( $url ) {
$prot = $bits['scheme'];
$index = $prot . $bits['delimiter'] . $reversedHost;
// Leave out user and password. Add the port, path, query and fragment
- if ( isset( $bits['port'] ) ) $index .= ':' . $bits['port'];
+ if ( isset( $bits['port'] ) ) {
+ $index .= ':' . $bits['port'];
+ }
if ( isset( $bits['path'] ) ) {
$index .= $bits['path'];
} else {
$index .= '/';
}
- if ( isset( $bits['query'] ) ) $index .= '?' . $bits['query'];
- if ( isset( $bits['fragment'] ) ) $index .= '#' . $bits['fragment'];
+ if ( isset( $bits['query'] ) ) {
+ $index .= '?' . $bits['query'];
+ }
+ if ( isset( $bits['fragment'] ) ) {
+ $index .= '#' . $bits['fragment'];
+ }
return $index;
}
/**
* Do any deferred updates and clear the list
- * TODO: This could be in Wiki.php if that class made any sense at all
+ *
+ * @param $commit Boolean: commit after every update to prevent lock contention
*/
-function wfDoUpdates()
-{
- global $wgPostCommitUpdateList, $wgDeferredUpdateList;
- foreach ( $wgDeferredUpdateList as $update ) {
- $update->doUpdate();
+function wfDoUpdates( $commit = false ) {
+ global $wgDeferredUpdateList;
+
+ wfProfileIn( __METHOD__ );
+
+ // No need to get master connections in case of empty updates array
+ if ( !count( $wgDeferredUpdateList ) ) {
+ wfProfileOut( __METHOD__ );
+ return;
}
- foreach ( $wgPostCommitUpdateList as $update ) {
+
+ if ( $commit ) {
+ $dbw = wfGetDB( DB_MASTER );
+ }
+
+ foreach ( $wgDeferredUpdateList as $update ) {
$update->doUpdate();
+
+ if ( $commit && $dbw->trxLevel() ) {
+ $dbw->commit();
+ }
}
- $wgDeferredUpdateList = array();
- $wgPostCommitUpdateList = array();
-}
-/**
- * @deprecated use StringUtils::explodeMarkup
- */
-function wfExplodeMarkup( $separator, $text ) {
- return StringUtils::explodeMarkup( $separator, $text );
+ $wgDeferredUpdateList = array();
+ wfProfileOut( __METHOD__ );
}
/**
@@ -2693,7 +2919,7 @@ function wfExplodeMarkup( $separator, $text ) {
* @param $lowercase Boolean
* @return String or false on invalid input
*/
-function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true ) {
+function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true ) {
$input = strval( $input );
if( $sourceBase < 2 ||
$sourceBase > 36 ||
@@ -2707,7 +2933,7 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true
$input == '' ) {
return false;
}
- $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$inDigits = array();
$outChars = '';
@@ -2771,7 +2997,7 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true
* @param $name String
* @param $p Array: parameters
*/
-function wfCreateObject( $name, $p ){
+function wfCreateObject( $name, $p ) {
$p = array_values( $p );
switch ( count( $p ) ) {
case 0:
@@ -2789,32 +3015,15 @@ function wfCreateObject( $name, $p ){
case 6:
return new $name( $p[0], $p[1], $p[2], $p[3], $p[4], $p[5] );
default:
- throw new MWException( "Too many arguments to construtor in wfCreateObject" );
+ throw new MWException( 'Too many arguments to construtor in wfCreateObject' );
}
}
-/**
- * Alias for modularized function
- * @deprecated Use Http::get() instead
- */
-function wfGetHTTP( $url ) {
- wfDeprecated(__FUNCTION__);
- return Http::get( $url );
-}
-
-/**
- * Alias for modularized function
- * @deprecated Use Http::isLocalURL() instead
- */
-function wfIsLocalURL( $url ) {
- wfDeprecated(__FUNCTION__);
- return Http::isLocalURL( $url );
-}
-
function wfHttpOnlySafe() {
global $wgHttpOnlyBlacklist;
- if( !version_compare("5.2", PHP_VERSION, "<") )
+ if( !version_compare( '5.2', PHP_VERSION, '<' ) ) {
return false;
+ }
if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
foreach( $wgHttpOnlyBlacklist as $regex ) {
@@ -2830,15 +3039,15 @@ function wfHttpOnlySafe() {
/**
* Initialise php session
*/
-function wfSetupSession() {
- global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain,
+function wfSetupSession( $sessionId = false ) {
+ global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain,
$wgCookieSecure, $wgCookieHttpOnly, $wgSessionHandler;
if( $wgSessionsInMemcached ) {
require_once( 'MemcachedSessions.php' );
} elseif( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) {
# Only set this if $wgSessionHandler isn't null and session.save_handler
# hasn't already been set to the desired value (that causes errors)
- ini_set ( 'session.save_handler', $wgSessionHandler );
+ ini_set( 'session.save_handler', $wgSessionHandler );
}
$httpOnlySafe = wfHttpOnlySafe();
wfDebugLog( 'cookie',
@@ -2856,6 +3065,9 @@ function wfSetupSession() {
session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
}
session_cache_limiter( 'private, must-revalidate' );
+ if ( $sessionId ) {
+ session_id( $sessionId );
+ }
wfSuppressWarnings();
session_start();
wfRestoreWarnings();
@@ -2882,7 +3094,7 @@ function wfGetPrecompiledData( $name ) {
function wfGetCaller( $level = 2 ) {
$backtrace = wfDebugBacktrace();
if ( isset( $backtrace[$level] ) ) {
- return wfFormatStackFrame($backtrace[$level]);
+ return wfFormatStackFrame( $backtrace[$level] );
} else {
$caller = 'unknown';
}
@@ -2890,20 +3102,28 @@ function wfGetCaller( $level = 2 ) {
}
/**
- * Return a string consisting all callers in stack, somewhat useful sometimes
- * for profiling specific points
+ * Return a string consisting of callers in the stack. Useful sometimes
+ * for profiling specific points.
+ *
+ * @param $limit The maximum depth of the stack frame to return, or false for
+ * the entire stack.
*/
-function wfGetAllCallers() {
- return implode('/', array_map('wfFormatStackFrame',array_reverse(wfDebugBacktrace())));
+function wfGetAllCallers( $limit = 3 ) {
+ $trace = array_reverse( wfDebugBacktrace() );
+ if ( !$limit || $limit > count( $trace ) - 1 ) {
+ $limit = count( $trace ) - 1;
+ }
+ $trace = array_slice( $trace, -$limit - 1, $limit );
+ return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
}
/**
* Return a string representation of frame
*/
-function wfFormatStackFrame($frame) {
- return isset( $frame["class"] )?
- $frame["class"]."::".$frame["function"]:
- $frame["function"];
+function wfFormatStackFrame( $frame ) {
+ return isset( $frame['class'] ) ?
+ $frame['class'] . '::' . $frame['function'] :
+ $frame['function'];
}
/**
@@ -2953,7 +3173,7 @@ function wfSplitWikiID( $wiki ) {
return $bits;
}
-/*
+/**
* Get a Database object.
* @param $db Integer: index of the connection to get. May be DB_MASTER for the
* master (for write queries), DB_SLAVE for potentially lagged read
@@ -2968,6 +3188,8 @@ function wfSplitWikiID( $wiki ) {
* Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
* will always return the same object, unless the underlying connection or load
* balancer is manually destroyed.
+ *
+ * @return DatabaseBase
*/
function &wfGetDB( $db, $groups = array(), $wiki = false ) {
return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
@@ -2993,7 +3215,7 @@ function &wfGetLBFactory() {
/**
* Find a file.
* Shortcut for RepoGroup::singleton()->findFile()
- * @param $title Either a string or Title object
+ * @param $title String or Title object
* @param $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
@@ -3001,7 +3223,7 @@ function &wfGetLBFactory() {
*
* ignoreRedirect: If true, do not follow file redirects
*
- * private: If true, return restricted (deleted) files if the current
+ * private: If true, return restricted (deleted) files if the current
* user is allowed to view them. Otherwise, such files will not
* be found.
*
@@ -3016,7 +3238,7 @@ function wfFindFile( $title, $options = array() ) {
/**
* Get an object referring to a locally registered file.
* Returns a valid placeholder object if the file does not exist.
- * @param $title Either a string or Title object
+ * @param $title Title or String
* @return File, or null if passed an invalid Title
*/
function wfLocalFile( $title ) {
@@ -3047,12 +3269,13 @@ function wfScript( $script = 'index' ) {
global $wgScriptPath, $wgScriptExtension;
return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
}
+
/**
- * Get the script url.
+ * Get the script URL.
*
- * @return script url
+ * @return script URL
*/
-function wfGetScriptUrl(){
+function wfGetScriptUrl() {
if( isset( $_SERVER['SCRIPT_NAME'] ) ) {
#
# as it was called, minus the query string.
@@ -3083,7 +3306,7 @@ function wfBoolToStr( $value ) {
/**
* Load an extension messages file
- * @deprecated
+ * @deprecated in 1.16 (warnings in 1.18, removed in ?)
*/
function wfLoadExtensionMessages( $extensionName, $langcode = false ) {
}
@@ -3145,18 +3368,21 @@ function wfDeprecated( $function ) {
*/
function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
$callers = wfDebugBacktrace();
- if( isset( $callers[$callerOffset+1] ) ){
- $callerfunc = $callers[$callerOffset+1];
+ if( isset( $callers[$callerOffset + 1] ) ){
+ $callerfunc = $callers[$callerOffset + 1];
$callerfile = $callers[$callerOffset];
- if( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ){
+ if( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
$file = $callerfile['file'] . ' at line ' . $callerfile['line'];
} else {
$file = '(internal function)';
}
$func = '';
- if( isset( $callerfunc['class'] ) )
+ if( isset( $callerfunc['class'] ) ) {
$func .= $callerfunc['class'] . '::';
- $func .= @$callerfunc['function'];
+ }
+ if( isset( $callerfunc['function'] ) ) {
+ $func .= $callerfunc['function'];
+ }
$msg .= " [Called from $func in $file]";
}
@@ -3187,23 +3413,24 @@ function wfWaitForSlaves( $maxLag, $wiki = false ) {
$lb = wfGetLB( $wiki );
list( $host, $lag ) = $lb->getMaxLag( $wiki );
while( $lag > $maxLag ) {
- $name = @gethostbyaddr( $host );
+ wfSuppressWarnings();
+ $name = gethostbyaddr( $host );
+ wfRestoreWarnings();
if( $name !== false ) {
$host = $name;
}
print "Waiting for $host (lagged $lag seconds)...\n";
- sleep($maxLag);
+ sleep( $maxLag );
list( $host, $lag ) = $lb->getMaxLag();
}
}
}
/**
- * Output some plain text in command-line mode or in the installer (updaters.inc).
- * Do not use it in any other context, its behaviour is subject to change.
+ * Used to be used for outputting text in the installer/updater
+ * @deprecated Warnings in 1.19, removal in 1.20
*/
function wfOut( $s ) {
- static $lineStarted = false;
global $wgCommandLineMode;
if ( $wgCommandLineMode && !defined( 'MEDIAWIKI_INSTALL' ) ) {
echo $s;
@@ -3214,14 +3441,14 @@ function wfOut( $s ) {
}
/**
- * Count down from $n to zero on the terminal, with a one-second pause
+ * Count down from $n to zero on the terminal, with a one-second pause
* between showing each number. For use in command-line scripts.
*/
function wfCountDown( $n ) {
for ( $i = $n; $i >= 0; $i-- ) {
if ( $i != $n ) {
echo str_repeat( "\x08", strlen( $i + 1 ) );
- }
+ }
echo $i;
flush();
if ( $i ) {
@@ -3231,14 +3458,14 @@ function wfCountDown( $n ) {
echo "\n";
}
-/** Generate a random 32-character hexadecimal token.
+/**
+ * Generate a random 32-character hexadecimal token.
* @param $salt Mixed: some sort of salt, if necessary, to add to random
* characters before hashing.
*/
function wfGenerateToken( $salt = '' ) {
- $salt = serialize($salt);
-
- return md5( mt_rand( 0, 0x7fffffff ) . $salt );
+ $salt = serialize( $salt );
+ return md5( mt_rand( 0, 0x7fffffff ) . $salt );
}
/**
@@ -3248,7 +3475,13 @@ function wfGenerateToken( $salt = '' ) {
function wfStripIllegalFilenameChars( $name ) {
global $wgIllegalFileChars;
$name = wfBaseName( $name );
- $name = preg_replace("/[^".Title::legalChars()."]".($wgIllegalFileChars ? "|[".$wgIllegalFileChars."]":"")."/",'-',$name);
+ $name = preg_replace(
+ "/[^" . Title::legalChars() . "]" .
+ ( $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '' ) .
+ "/",
+ '-',
+ $name
+ );
return $name;
}
@@ -3260,31 +3493,34 @@ function wfStripIllegalFilenameChars( $name ) {
*/
function wfArrayInsertAfter( $array, $insert, $after ) {
// Find the offset of the element to insert after.
- $keys = array_keys($array);
+ $keys = array_keys( $array );
$offsetByKey = array_flip( $keys );
-
+
$offset = $offsetByKey[$after];
-
+
// Insert at the specified offset
$before = array_slice( $array, 0, $offset + 1, true );
- $after = array_slice( $array, $offset + 1, count($array)-$offset, true );
-
+ $after = array_slice( $array, $offset + 1, count( $array ) - $offset, true );
+
$output = $before + $insert + $after;
-
+
return $output;
}
/* Recursively converts the parameter (an object) to an array with the same data */
-function wfObjectToArray( $object, $recursive = true ) {
+function wfObjectToArray( $objOrArray, $recursive = true ) {
$array = array();
- foreach ( get_object_vars($object) as $key => $value ) {
- if ( is_object($value) && $recursive ) {
+ if( is_object( $objOrArray ) ) {
+ $objOrArray = get_object_vars( $objOrArray );
+ }
+ foreach ( $objOrArray as $key => $value ) {
+ if ( $recursive && ( is_object( $value ) || is_array( $value ) ) ) {
$value = wfObjectToArray( $value );
}
-
+
$array[$key] = $value;
}
-
+
return $array;
}
@@ -3292,22 +3528,21 @@ function wfObjectToArray( $object, $recursive = true ) {
* Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit;
* @return Integer value memory was set to.
*/
-
-function wfMemoryLimit () {
+function wfMemoryLimit() {
global $wgMemoryLimit;
- $memlimit = wfShorthandToInteger( ini_get( "memory_limit" ) );
- $conflimit = wfShorthandToInteger( $wgMemoryLimit );
+ $memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
if( $memlimit != -1 ) {
+ $conflimit = wfShorthandToInteger( $wgMemoryLimit );
if( $conflimit == -1 ) {
wfDebug( "Removing PHP's memory limit\n" );
wfSuppressWarnings();
- ini_set( "memory_limit", $conflimit );
+ ini_set( 'memory_limit', $conflimit );
wfRestoreWarnings();
return $conflimit;
} elseif ( $conflimit > $memlimit ) {
wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
wfSuppressWarnings();
- ini_set( "memory_limit", $conflimit );
+ ini_set( 'memory_limit', $conflimit );
wfRestoreWarnings();
return $conflimit;
}
@@ -3320,24 +3555,32 @@ function wfMemoryLimit () {
* @param $string String
* @return Integer
*/
-function wfShorthandToInteger ( $string = '' ) {
- $string = trim($string);
- if( empty($string) ) { return -1; }
- $last = strtolower($string[strlen($string)-1]);
- $val = intval($string);
- switch($last) {
+function wfShorthandToInteger( $string = '' ) {
+ $string = trim( $string );
+ if( $string === '' ) {
+ return -1;
+ }
+ $last = $string[strlen( $string ) - 1];
+ $val = intval( $string );
+ switch( $last ) {
case 'g':
+ case 'G':
$val *= 1024;
+ // break intentionally missing
case 'm':
+ case 'M':
$val *= 1024;
+ // break intentionally missing
case 'k':
+ case 'K':
$val *= 1024;
}
return $val;
}
-/* Get the normalised IETF language tag
+/**
+ * Get the normalised IETF language tag
* @param $code String: The language code.
* @return $langCode String: The language code which complying with BCP 47 standards.
*/
@@ -3346,19 +3589,31 @@ function wfBCP47( $code ) {
foreach ( $codeSegment as $segNo => $seg ) {
if ( count( $codeSegment ) > 0 ) {
// ISO 3166 country code
- if ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) )
+ if ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
$codeBCP[$segNo] = strtoupper( $seg );
// ISO 15924 script code
- else if ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) )
+ } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
$codeBCP[$segNo] = ucfirst( $seg );
// Use lowercase for other cases
- else
+ } else {
$codeBCP[$segNo] = strtolower( $seg );
+ }
} else {
// Use lowercase for single segment
$codeBCP[$segNo] = strtolower( $seg );
}
}
- $langCode = implode ( '-' , $codeBCP );
+ $langCode = implode( '-', $codeBCP );
return $langCode;
}
+
+function wfArrayMap( $function, $input ) {
+ $ret = array_map( $function, $input );
+ foreach ( $ret as $key => $value ) {
+ $taint = istainted( $input[$key] );
+ if ( $taint ) {
+ taint( $ret[$key], $taint );
+ }
+ }
+ return $ret;
+}
diff --git a/includes/HTMLCacheUpdate.php b/includes/HTMLCacheUpdate.php
index 6f90b2d9..f0456a22 100644
--- a/includes/HTMLCacheUpdate.php
+++ b/includes/HTMLCacheUpdate.php
@@ -63,7 +63,6 @@ class HTMLCacheUpdate
$this->invalidateTitles( $titleArray );
}
}
- wfRunHooks( 'HTMLCacheUpdate::doUpdate', array($this->mTitle) );
}
/**
diff --git a/includes/HTMLFileCache.php b/includes/HTMLFileCache.php
index 53af1e6e..26cb147d 100644
--- a/includes/HTMLFileCache.php
+++ b/includes/HTMLFileCache.php
@@ -7,8 +7,7 @@
/**
* Handles talking to the file cache, putting stuff in and taking it back out.
- * Mostly called from Article.php, also from DatabaseFunctions.php for the
- * emergency abort/fallback to cache.
+ * Mostly called from Article.php for the emergency abort/fallback to cache.
*
* Global options that affect this module:
* - $wgCachePages
@@ -31,7 +30,7 @@ class HTMLFileCache {
public function fileCacheName() {
if( !$this->mFileCache ) {
- global $wgCacheDirectory, $wgFileCacheDirectory, $wgRequest;
+ global $wgCacheDirectory, $wgFileCacheDirectory, $wgFileCacheDepth;
if ( $wgFileCacheDirectory ) {
$dir = $wgFileCacheDirectory;
@@ -43,17 +42,21 @@ class HTMLFileCache {
# Store raw pages (like CSS hits) elsewhere
$subdir = ($this->mType === 'raw') ? 'raw/' : '';
+
$key = $this->mTitle->getPrefixedDbkey();
- $hash = md5( $key );
+ if ( $wgFileCacheDepth > 0 ) {
+ $hash = md5( $key );
+ for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) {
+ $subdir .= substr( $hash, 0, $i ) . '/';
+ }
+ }
# Avoid extension confusion
$key = str_replace( '.', '%2E', urlencode( $key ) );
-
- $hash1 = substr( $hash, 0, 1 );
- $hash2 = substr( $hash, 0, 2 );
- $this->mFileCache = "{$wgFileCacheDirectory}/{$subdir}{$hash1}/{$hash2}/{$key}.html";
+ $this->mFileCache = "{$dir}/{$subdir}{$key}.html";
- if( $this->useGzip() )
+ if( $this->useGzip() ) {
$this->mFileCache .= '.gz';
+ }
wfDebug( __METHOD__ . ": {$this->mFileCache}\n" );
}
@@ -135,7 +138,7 @@ class HTMLFileCache {
/* Working directory to/from output */
public function loadFromFileCache() {
- global $wgOut, $wgMimeType, $wgOutputEncoding, $wgContLanguageCode;
+ global $wgOut, $wgMimeType, $wgOutputEncoding, $wgLanguageCode;
wfDebug( __METHOD__ . "()\n");
$filename = $this->fileCacheName();
// Raw pages should handle cache control on their own,
@@ -143,7 +146,7 @@ class HTMLFileCache {
if( $this->mType !== 'raw' ) {
$wgOut->sendCacheControl();
header( "Content-Type: $wgMimeType; charset={$wgOutputEncoding}" );
- header( "Content-Language: $wgContLanguageCode" );
+ header( "Content-Language: $wgLanguageCode" );
}
if( $this->useGzip() ) {
@@ -210,11 +213,21 @@ class HTMLFileCache {
public static function clearFileCache( $title ) {
global $wgUseFileCache;
- if( !$wgUseFileCache ) return false;
+
+ if ( !$wgUseFileCache ) {
+ return false;
+ }
+
+ wfSuppressWarnings();
+
$fc = new self( $title, 'view' );
- @unlink( $fc->fileCacheName() );
+ unlink( $fc->fileCacheName() );
+
$fc = new self( $title, 'raw' );
- @unlink( $fc->fileCacheName() );
+ unlink( $fc->fileCacheName() );
+
+ wfRestoreWarnings();
+
return true;
}
}
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php
index 12687dc4..be912daf 100644
--- a/includes/HTMLForm.php
+++ b/includes/HTMLForm.php
@@ -1,48 +1,52 @@
<?php
-
/**
- * Object handling generic submission, CSRF protection, layout and
- * other logic for UI forms. in a reusable manner.
+ * Object handling generic submission, CSRF protection, layout and
+ * other logic for UI forms. in a reusable manner.
*
- * In order to generate the form, the HTMLForm object takes an array
- * structure detailing the form fields available. Each element of the
- * array is a basic property-list, including the type of field, the
+ * In order to generate the form, the HTMLForm object takes an array
+ * structure detailing the form fields available. Each element of the
+ * array is a basic property-list, including the type of field, the
* label it is to be given in the form, callbacks for validation and
- * 'filtering', and other pertinent information.
+ * 'filtering', and other pertinent information.
*
* Field types are implemented as subclasses of the generic HTMLFormField
- * object, and typically implement at least getInputHTML, which generates
+ * object, and typically implement at least getInputHTML, which generates
* the HTML for the input field to be placed in the table.
- *
+ *
* The constructor input is an associative array of $fieldname => $info,
* where $info is an Associative Array with any of the following:
- *
- * 'class' -- the subclass of HTMLFormField that will be used
- * to create the object. *NOT* the CSS class!
- * 'type' -- roughly translates into the <select> type attribute.
- * if 'class' is not specified, this is used as a map
- * through HTMLForm::$typeMappings to get the class name.
- * 'default' -- default value when the form is displayed
- * 'id' -- HTML id attribute
- * 'options' -- varies according to the specific object.
- * 'label-message' -- message key for a message to use as the label.
- * can be an array of msg key and then parameters to
- * the message.
- * 'label' -- alternatively, a raw text message. Overridden by
- * label-message
- * 'help-message' -- message key for a message to use as a help text.
- * can be an array of msg key and then parameters to
- * the message.
- * 'required' -- passed through to the object, indicating that it
- * is a required field.
- * 'size' -- the length of text fields
- * 'filter-callback -- a function name to give you the chance to
- * massage the inputted value before it's processed.
- * @see HTMLForm::filter()
- * 'validation-callback' -- a function name to give you the chance
- * to impose extra validation on the field input.
- * @see HTMLForm::validate()
- *
+ *
+ * 'class' -- the subclass of HTMLFormField that will be used
+ * to create the object. *NOT* the CSS class!
+ * 'type' -- roughly translates into the <select> type attribute.
+ * if 'class' is not specified, this is used as a map
+ * through HTMLForm::$typeMappings to get the class name.
+ * 'default' -- default value when the form is displayed
+ * 'id' -- HTML id attribute
+ * 'cssclass' -- CSS class
+ * 'options' -- varies according to the specific object.
+ * 'label-message' -- message key for a message to use as the label.
+ * can be an array of msg key and then parameters to
+ * the message.
+ * 'label' -- alternatively, a raw text message. Overridden by
+ * label-message
+ * 'help-message' -- message key for a message to use as a help text.
+ * can be an array of msg key and then parameters to
+ * the message.
+ * 'required' -- passed through to the object, indicating that it
+ * is a required field.
+ * 'size' -- the length of text fields
+ * 'filter-callback -- a function name to give you the chance to
+ * massage the inputted value before it's processed.
+ * @see HTMLForm::filter()
+ * 'validation-callback' -- a function name to give you the chance
+ * to impose extra validation on the field input.
+ * @see HTMLForm::validate()
+ * 'name' -- By default, the 'name' attribute of the input field
+ * is "wp{$fieldname}". If you want a different name
+ * (eg one without the "wp" prefix), specify it here and
+ * it will be used without modification.
+ *
* TODO: Document 'section' / 'subsection' stuff
*/
class HTMLForm {
@@ -64,38 +68,40 @@ class HTMLForm {
'submit' => 'HTMLSubmitField',
'hidden' => 'HTMLHiddenField',
'edittools' => 'HTMLEditTools',
-
+
# HTMLTextField will output the correct type="" attribute automagically.
# There are about four zillion other HTML5 input types, like url, but
# we don't use those at the moment, so no point in adding all of them.
'email' => 'HTMLTextField',
'password' => 'HTMLTextField',
);
-
+
protected $mMessagePrefix;
protected $mFlatFields;
protected $mFieldTree;
protected $mShowReset = false;
public $mFieldData;
-
+
protected $mSubmitCallback;
protected $mValidationErrorMessage;
-
+
protected $mPre = '';
protected $mHeader = '';
+ protected $mFooter = '';
protected $mPost = '';
protected $mId;
-
+
protected $mSubmitID;
protected $mSubmitName;
protected $mSubmitText;
protected $mSubmitTooltip;
protected $mTitle;
-
+ protected $mMethod = 'post';
+
protected $mUseMultipart = false;
protected $mHiddenFields = array();
protected $mButtons = array();
-
+
protected $mWrapperLegend = false;
/**
@@ -103,31 +109,30 @@ class HTMLForm {
* @param $descriptor Array of Field constructs, as described above
* @param $messagePrefix String a prefix to go in front of default messages
*/
- public function __construct( $descriptor, $messagePrefix='' ) {
+ public function __construct( $descriptor, $messagePrefix = '' ) {
$this->mMessagePrefix = $messagePrefix;
// Expand out into a tree.
$loadedDescriptor = array();
$this->mFlatFields = array();
- foreach( $descriptor as $fieldname => $info ) {
- $section = '';
- if ( isset( $info['section'] ) )
- $section = $info['section'];
+ foreach ( $descriptor as $fieldname => $info ) {
+ $section = isset( $info['section'] )
+ ? $info['section']
+ : '';
- $info['name'] = $fieldname;
-
- if ( isset( $info['type'] ) && $info['type'] == 'file' )
+ if ( isset( $info['type'] ) && $info['type'] == 'file' ) {
$this->mUseMultipart = true;
+ }
- $field = self::loadInputFromParameters( $info );
+ $field = self::loadInputFromParameters( $fieldname, $info );
$field->mParent = $this;
$setSection =& $loadedDescriptor;
- if( $section ) {
+ if ( $section ) {
$sectionParts = explode( '/', $section );
- while( count( $sectionParts ) ) {
+ while ( count( $sectionParts ) ) {
$newName = array_shift( $sectionParts );
if ( !isset( $setSection[$newName] ) ) {
@@ -146,15 +151,15 @@ class HTMLForm {
}
/**
- * Add the HTMLForm-specific JavaScript, if it hasn't been
+ * Add the HTMLForm-specific JavaScript, if it hasn't been
* done already.
*/
static function addJS() {
- if( self::$jsAdded ) return;
+ if ( self::$jsAdded ) return;
- global $wgOut, $wgStylePath;
+ global $wgOut;
- $wgOut->addScriptFile( "$wgStylePath/common/htmlform.js" );
+ $wgOut->addModules( 'mediawiki.legacy.htmlform' );
}
/**
@@ -162,7 +167,7 @@ class HTMLForm {
* @param $descriptor input Descriptor, as described above
* @return HTMLFormField subclass
*/
- static function loadInputFromParameters( $descriptor ) {
+ static function loadInputFromParameters( $fieldname, $descriptor ) {
if ( isset( $descriptor['class'] ) ) {
$class = $descriptor['class'];
} elseif ( isset( $descriptor['type'] ) ) {
@@ -170,9 +175,11 @@ class HTMLForm {
$descriptor['class'] = $class;
}
- if( !$class ) {
+ if ( !$class ) {
throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) );
}
+
+ $descriptor['fieldname'] = $fieldname;
$obj = new $class( $descriptor );
@@ -180,31 +187,50 @@ class HTMLForm {
}
/**
- * The here's-one-I-made-earlier option: do the submission if
- * posted, or display the form with or without funky valiation
- * errors
- * @return Bool whether submission was successful.
+ * Prepare form for submission
*/
- function show() {
- $html = '';
+ function prepareForm() {
+ # Check if we have the info we need
+ if ( ! $this->mTitle ) {
+ throw new MWException( "You must call setTitle() on an HTMLForm" );
+ }
+ // FIXME shouldn't this be closer to displayForm() ?
self::addJS();
# Load data from the request.
$this->loadData();
+ }
- # Try a submission
+ /**
+ * Try submitting, with edit token check first
+ * @return Status|boolean
+ */
+ function tryAuthorizedSubmit() {
global $wgUser, $wgRequest;
$editToken = $wgRequest->getVal( 'wpEditToken' );
$result = false;
- if ( $wgUser->matchEditToken( $editToken ) )
+ if ( $this->getMethod() != 'post' || $wgUser->matchEditToken( $editToken ) ) {
$result = $this->trySubmit();
+ }
+ return $result;
+ }
+
+ /**
+ * The here's-one-I-made-earlier option: do the submission if
+ * posted, or display the form with or without funky valiation
+ * errors
+ * @return Bool or Status whether submission was successful.
+ */
+ function show() {
+ $this->prepareForm();
- if( $result === true )
+ $result = $this->tryAuthorizedSubmit();
+ if ( $result === true || ( $result instanceof Status && $result->isGood() ) ){
return $result;
+ }
- # Display form.
$this->displayForm( $result );
return false;
}
@@ -213,19 +239,20 @@ class HTMLForm {
* Validate all the fields, and call the submision callback
* function if everything is kosher.
* @return Mixed Bool true == Successful submission, Bool false
- * == No submission attempted, anything else == Error to
+ * == No submission attempted, anything else == Error to
* display.
*/
function trySubmit() {
# Check for validation
- foreach( $this->mFlatFields as $fieldname => $field ) {
- if ( !empty( $field->mParams['nodata'] ) )
+ foreach ( $this->mFlatFields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
continue;
- if ( $field->validate(
+ }
+ if ( $field->validate(
$this->mFieldData[$fieldname],
- $this->mFieldData )
- !== true )
- {
+ $this->mFieldData )
+ !== true
+ ) {
return isset( $this->mValidationErrorMessage )
? $this->mValidationErrorMessage
: array( 'htmlform-invalid-input' );
@@ -254,14 +281,14 @@ class HTMLForm {
}
/**
- * Set a message to display on a validation error.
+ * Set a message to display on a validation error.
* @param $msg Mixed String or Array of valid inputs to wfMsgExt()
* (so each entry can be either a String or Array)
*/
function setValidationErrorMessage( $msg ) {
$this->mValidationErrorMessage = $msg;
}
-
+
/**
* Set the introductory message, overwriting any existing message.
* @param $msg String complete text of message to display
@@ -273,52 +300,58 @@ class HTMLForm {
* @param $msg String complete text of message to display
*/
function addPreText( $msg ) { $this->mPre .= $msg; }
-
+
/**
* Add header text, inside the form.
* @param $msg String complete text of message to display
*/
function addHeaderText( $msg ) { $this->mHeader .= $msg; }
-
+
+ /**
+ * Add footer text, inside the form.
+ * @param $msg String complete text of message to display
+ */
+ function addFooterText( $msg ) { $this->mFooter .= $msg; }
+
/**
* Add text to the end of the display.
* @param $msg String complete text of message to display
*/
function addPostText( $msg ) { $this->mPost .= $msg; }
-
+
/**
* Add a hidden field to the output
- * @param $name String field name
+ * @param $name String field name. This will be used exactly as entered
* @param $value String field value
+ * @param $attribs Array
*/
- public function addHiddenField( $name, $value ){
- $this->mHiddenFields[ $name ] = $value;
+ public function addHiddenField( $name, $value, $attribs = array() ) {
+ $attribs += array( 'name' => $name );
+ $this->mHiddenFields[] = array( $value, $attribs );
}
-
- public function addButton( $name, $value, $id=null, $attribs=null ){
+
+ public function addButton( $name, $value, $id = null, $attribs = null ) {
$this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' );
}
/**
- * Display the form (sending to wgOut), with an appropriate error
+ * Display the form (sending to wgOut), with an appropriate error
* message or stack of messages, and any validation errors, etc.
* @param $submitResult Mixed output from HTMLForm::trySubmit()
*/
function displayForm( $submitResult ) {
global $wgOut;
- if ( $submitResult !== false ) {
- $this->displayErrors( $submitResult );
- }
-
# For good measure (it is the default)
$wgOut->preventClickjacking();
$html = ''
+ . $this->getErrors( $submitResult )
. $this->mHeader
. $this->getBody()
. $this->getHiddenFields()
. $this->getButtons()
+ . $this->mFooter
;
$html = $this->wrapForm( $html );
@@ -336,25 +369,26 @@ class HTMLForm {
* @return String wrapped HTML.
*/
function wrapForm( $html ) {
-
+
# Include a <fieldset> wrapper for style, if requested.
- if ( $this->mWrapperLegend !== false ){
+ if ( $this->mWrapperLegend !== false ) {
$html = Xml::fieldset( $this->mWrapperLegend, $html );
}
# Use multipart/form-data
- $encType = $this->mUseMultipart
+ $encType = $this->mUseMultipart
? 'multipart/form-data'
: 'application/x-www-form-urlencoded';
# Attributes
$attribs = array(
'action' => $this->getTitle()->getFullURL(),
- 'method' => 'post',
+ 'method' => $this->mMethod,
'class' => 'visualClear',
- 'enctype' => $encType,
+ 'enctype' => $encType,
);
- if ( !empty( $this->mId ) )
+ if ( !empty( $this->mId ) ) {
$attribs['id'] = $this->mId;
-
+ }
+
return Html::rawElement( 'form', $attribs, $html );
}
@@ -364,13 +398,17 @@ class HTMLForm {
*/
function getHiddenFields() {
global $wgUser;
- $html = '';
- $html .= Html::hidden( 'wpEditToken', $wgUser->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
- $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
+ $html = '';
- foreach( $this->mHiddenFields as $name => $value ){
- $html .= Html::hidden( $name, $value ) . "\n";
+ if( $this->getMethod() == 'post' ){
+ $html .= Html::hidden( 'wpEditToken', $wgUser->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
+ $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
+ }
+
+ foreach ( $this->mHiddenFields as $data ) {
+ list( $value, $attribs ) = $data;
+ $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n";
}
return $html;
@@ -382,13 +420,16 @@ class HTMLForm {
*/
function getButtons() {
$html = '';
-
$attribs = array();
- if ( isset( $this->mSubmitID ) )
+ if ( isset( $this->mSubmitID ) ) {
$attribs['id'] = $this->mSubmitID;
- if ( isset( $this->mSubmitName ) )
+ }
+
+ if ( isset( $this->mSubmitName ) ) {
$attribs['name'] = $this->mSubmitName;
+ }
+
if ( isset( $this->mSubmitTooltip ) ) {
global $wgUser;
$attribs += $wgUser->getSkin()->tooltipAndAccessKeyAttribs( $this->mSubmitTooltip );
@@ -398,7 +439,7 @@ class HTMLForm {
$html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
- if( $this->mShowReset ) {
+ if ( $this->mShowReset ) {
$html .= Html::element(
'input',
array(
@@ -407,20 +448,25 @@ class HTMLForm {
)
) . "\n";
}
-
- foreach( $this->mButtons as $button ){
+
+ 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'] ) )
+
+ if ( $button['attribs'] ) {
+ $attrs += $button['attribs'];
+ }
+
+ if ( isset( $button['id'] ) ) {
$attrs['id'] = $button['id'];
+ }
+
$html .= Html::element( 'input', $attrs );
}
-
+
return $html;
}
@@ -434,18 +480,25 @@ class HTMLForm {
/**
* Format and display an error message stack.
* @param $errors Mixed String or Array of message keys
+ * @return String
*/
- function displayErrors( $errors ) {
- if ( is_array( $errors ) ) {
+ function getErrors( $errors ) {
+ if ( $errors instanceof Status ) {
+ global $wgOut;
+ if ( $errors->isOK() ) {
+ $errorstr = '';
+ } else {
+ $errorstr = $wgOut->parse( $errors->getWikiText() );
+ }
+ } elseif ( is_array( $errors ) ) {
$errorstr = $this->formatErrors( $errors );
} else {
$errorstr = $errors;
}
-
- $errorstr = Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr );
- global $wgOut;
- $wgOut->addHTML( $errorstr );
+ return $errorstr
+ ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr )
+ : '';
}
/**
@@ -455,13 +508,15 @@ class HTMLForm {
*/
static function formatErrors( $errors ) {
$errorstr = '';
+
foreach ( $errors as $error ) {
- if( is_array( $error ) ) {
+ if ( is_array( $error ) ) {
$msg = array_shift( $error );
} else {
$msg = $error;
$error = array();
}
+
$errorstr .= Html::rawElement(
'li',
null,
@@ -488,27 +543,26 @@ class HTMLForm {
*/
function getSubmitText() {
return $this->mSubmitText
- ? $this->mSubmitText
+ ? $this->mSubmitText
: wfMsg( 'htmlform-submit' );
}
-
+
public function setSubmitName( $name ) {
$this->mSubmitName = $name;
}
-
+
public function setSubmitTooltip( $name ) {
$this->mSubmitTooltip = $name;
}
-
/**
- * Set the id for the submit button.
+ * Set the id for the submit button.
* @param $t String. FIXME: Integrity is *not* validated
*/
function setSubmitID( $t ) {
$this->mSubmitID = $t;
}
-
+
public function setId( $id ) {
$this->mId = $id;
}
@@ -518,11 +572,11 @@ class HTMLForm {
* @param $legend String HTML to go inside the <legend> element.
* Will be escaped
*/
- public function setWrapperLegend( $legend ){ $this->mWrapperLegend = $legend; }
+ public function setWrapperLegend( $legend ) { $this->mWrapperLegend = $legend; }
/**
* Set the prefix for various default messages
- * TODO: currently only used for the <fieldset> legend on forms
+ * TODO: currently only used for the <fieldset> legend on forms
* with multiple sections; should be used elsewhre?
* @param $p String
*/
@@ -545,6 +599,18 @@ class HTMLForm {
function getTitle() {
return $this->mTitle;
}
+
+ /**
+ * Set the method used to submit the form
+ * @param $method String
+ */
+ public function setMethod( $method='post' ){
+ $this->mMethod = $method;
+ }
+
+ public function getMethod(){
+ return $this->mMethod;
+ }
/**
* TODO: Document
@@ -555,14 +621,14 @@ class HTMLForm {
$subsectionHtml = '';
$hasLeftColumn = false;
- foreach( $fields as $key => $value ) {
+ foreach ( $fields as $key => $value ) {
if ( is_object( $value ) ) {
$v = empty( $value->mParams['nodata'] )
? $this->mFieldData[$key]
: $value->getDefault();
$tableHtml .= $value->getTableRow( $v );
- if( $value->getLabel() != '&nbsp;' )
+ if ( $value->getLabel() != '&#160;' )
$hasLeftColumn = true;
} elseif ( is_array( $value ) ) {
$section = $this->displaySection( $value, $key );
@@ -572,13 +638,18 @@ class HTMLForm {
}
$classes = array();
- if( !$hasLeftColumn ) // Avoid strange spacing when no labels exist
+
+ if ( !$hasLeftColumn ) { // Avoid strange spacing when no labels exist
$classes[] = 'mw-htmlform-nolabel';
+ }
+
$attribs = array(
- 'class' => implode( ' ', $classes ),
+ 'class' => implode( ' ', $classes ),
);
- if ( $sectionName )
+
+ if ( $sectionName ) {
$attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" );
+ }
$tableHtml = Html::rawElement( 'table', $attribs,
Html::rawElement( 'tbody', array(), "\n$tableHtml\n" ) ) . "\n";
@@ -594,9 +665,10 @@ class HTMLForm {
$fieldData = array();
- foreach( $this->mFlatFields as $fieldname => $field ) {
- if ( !empty( $field->mParams['nodata'] ) ) continue;
- if ( !empty( $field->mParams['disabled'] ) ) {
+ foreach ( $this->mFlatFields as $fieldname => $field ) {
+ if ( !empty( $field->mParams['nodata'] ) ) {
+ continue;
+ } elseif ( !empty( $field->mParams['disabled'] ) ) {
$fieldData[$fieldname] = $field->getDefault();
} else {
$fieldData[$fieldname] = $field->loadDataFromRequest( $wgRequest );
@@ -604,7 +676,7 @@ class HTMLForm {
}
# Filter data.
- foreach( $fieldData as $name => &$value ) {
+ foreach ( $fieldData as $name => &$value ) {
$field = $this->mFlatFields[$name];
$value = $field->filter( $value, $this->mFlatFields );
}
@@ -614,7 +686,7 @@ class HTMLForm {
/**
* Stop a reset button being shown for this form
- * @param $suppressReset Bool set to false to re-enable the
+ * @param $suppressReset Bool set to false to re-enable the
* button again
*/
function suppressReset( $suppressReset = true ) {
@@ -623,9 +695,9 @@ class HTMLForm {
/**
* Overload this if you want to apply special filtration routines
- * to the form as a whole, after it's submitted but before it's
+ * to the form as a whole, after it's submitted but before it's
* processed.
- * @param $data
+ * @param $data
* @return unknown_type
*/
function filterDataForSubmit( $data ) {
@@ -634,36 +706,37 @@ class HTMLForm {
}
/**
- * The parent class to generate form fields. Any field type should
+ * The parent class to generate form fields. Any field type should
* be a subclass of this.
*/
abstract class HTMLFormField {
-
+
protected $mValidationCallback;
protected $mFilterCallback;
protected $mName;
public $mParams;
protected $mLabel; # String label. Set on construction
protected $mID;
+ protected $mClass = '';
protected $mDefault;
public $mParent;
-
+
/**
* This function must be implemented to return the HTML to generate
* the input object itself. It should not implement the surrounding
* table cells/rows, or labels/help messages.
* @param $value String the value to set the input to; eg a default
- * text for a text input.
+ * text for a text input.
* @return String valid HTML.
*/
abstract function getInputHTML( $value );
/**
- * Override this function to add specific validation checks on the
+ * Override this function to add specific validation checks on the
* field input. Don't forget to call parent::validate() to ensure
* that the user-defined callback mValidationCallback is still run
* @param $value String the value the field was submitted with
- * @param $alldata $all the data collected from the form
+ * @param $alldata Array the data collected from the form
* @return Mixed Bool true on success, or String error to display.
*/
function validate( $value, $alldata ) {
@@ -671,11 +744,15 @@ abstract class HTMLFormField {
return call_user_func( $this->mValidationCallback, $value, $alldata );
}
+ if ( isset( $this->mParams['required'] ) && $value === '' ) {
+ return wfMsgExt( 'htmlform-required', 'parseinline' );
+ }
+
return true;
}
function filter( $value, $alldata ) {
- if( isset( $this->mFilterCallback ) ) {
+ if ( isset( $this->mFilterCallback ) ) {
$value = call_user_func( $this->mFilterCallback, $value, $alldata );
}
@@ -699,7 +776,7 @@ abstract class HTMLFormField {
* @return String the value
*/
function loadDataFromRequest( $request ) {
- if( $request->getCheck( $this->mName ) ) {
+ if ( $request->getCheck( $this->mName ) ) {
return $request->getText( $this->mName );
} else {
return $this->getDefault();
@@ -714,7 +791,7 @@ abstract class HTMLFormField {
$this->mParams = $params;
# Generate the label from a message, if possible
- if( isset( $params['label-message'] ) ) {
+ if ( isset( $params['label-message'] ) ) {
$msgInfo = $params['label-message'];
if ( is_array( $msgInfo ) ) {
@@ -729,15 +806,17 @@ abstract class HTMLFormField {
$this->mLabel = $params['label'];
}
+ $this->mName = "wp{$params['fieldname']}";
if ( isset( $params['name'] ) ) {
- $name = $params['name'];
- $validName = Sanitizer::escapeId( $name );
- if( $name != $validName ) {
- throw new MWException("Invalid name '$name' passed to " . __METHOD__ );
- }
- $this->mName = 'wp'.$name;
- $this->mID = 'mw-input-'.$name;
+ $this->mName = $params['name'];
}
+
+ $validName = Sanitizer::escapeId( $this->mName );
+ if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
+ throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ );
+ }
+
+ $this->mID = "mw-input-{$this->mName}";
if ( isset( $params['default'] ) ) {
$this->mDefault = $params['default'];
@@ -746,12 +825,18 @@ abstract class HTMLFormField {
if ( isset( $params['id'] ) ) {
$id = $params['id'];
$validId = Sanitizer::escapeId( $id );
- if( $id != $validId ) {
- throw new MWException("Invalid id '$id' passed to " . __METHOD__ );
+
+ if ( $id != $validId ) {
+ throw new MWException( "Invalid id '$id' passed to " . __METHOD__ );
}
+
$this->mID = $id;
}
+ if ( isset( $params['cssclass'] ) ) {
+ $this->mClass = $params['cssclass'];
+ }
+
if ( isset( $params['validation-callback'] ) ) {
$this->mValidationCallback = $params['validation-callback'];
}
@@ -772,22 +857,44 @@ abstract class HTMLFormField {
global $wgRequest;
$errors = $this->validate( $value, $this->mParent->mFieldData );
- if ( $errors === true || !$wgRequest->wasPosted() ) {
+
+ $cellAttributes = array();
+ $verticalLabel = false;
+
+ if ( !empty($this->mParams['vertical-label']) ) {
+ $cellAttributes['colspan'] = 2;
+ $verticalLabel = true;
+ }
+
+ if ( $errors === true || ( !$wgRequest->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) {
$errors = '';
} else {
$errors = Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
}
- $html = $this->getLabelHtml();
- $html .= Html::rawElement( 'td', array( 'class' => 'mw-input' ),
- $this->getInputHTML( $value ) ."\n$errors" );
-
+ $label = $this->getLabelHtml( $cellAttributes );
+ $field = Html::rawElement(
+ 'td',
+ array( 'class' => 'mw-input' ) + $cellAttributes,
+ $this->getInputHTML( $value ) . "\n$errors"
+ );
+
$fieldType = get_class( $this );
-
- $html = Html::rawElement( 'tr', array( 'class' => "mw-htmlform-field-$fieldType" ),
- $html ) . "\n";
+
+ if ($verticalLabel) {
+ $html = Html::rawElement( 'tr',
+ array( 'class' => 'mw-htmlform-vertical-label' ), $label );
+ $html .= Html::rawElement( 'tr',
+ array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass}" ),
+ $field );
+ } else {
+ $html = Html::rawElement( 'tr',
+ array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass}" ),
+ $label . $field );
+ }
$helptext = null;
+
if ( isset( $this->mParams['help-message'] ) ) {
$msg = $this->mParams['help-message'];
$helptext = wfMsgExt( $msg, 'parseinline' );
@@ -812,16 +919,18 @@ abstract class HTMLFormField {
function getLabel() {
return $this->mLabel;
}
- function getLabelHtml() {
+ function getLabelHtml( $cellAttributes = array() ) {
# Don't output a for= attribute for labels with no associated input.
# Kind of hacky here, possibly we don't want these to be <label>s at all.
$for = array();
+
if ( $this->needsLabel() ) {
$for['for'] = $this->mID;
}
- return Html::rawElement( 'td', array( 'class' => 'mw-label' ),
- Html::rawElement( 'label', $for, $this->getLabel() )
- );
+
+ return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
+ Html::rawElement( 'label', $for, $this->getLabel() )
+ );
}
function getDefault() {
@@ -831,23 +940,25 @@ abstract class HTMLFormField {
return null;
}
}
-
+
/**
* Returns the attributes required for the tooltip and accesskey.
- *
+ *
* @return array Attributes
*/
public function getTooltipAndAccessKey() {
- if ( empty( $this->mParams['tooltip'] ) )
+ if ( empty( $this->mParams['tooltip'] ) ) {
return array();
+ }
global $wgUser;
- return $wgUser->getSkin()->tooltipAndAccessKeyAttribs();
+
+ return $wgUser->getSkin()->tooltipAndAccessKeyAttribs( $this->mParams['tooltip'] );
}
/**
* flatten an array of options to a single array, for instance,
- * a set of <options> inside <optgroups>.
+ * a set of <options> inside <optgroups>.
* @param $options Associative Array with values either Strings
* or Arrays
* @return Array flattened input
@@ -855,7 +966,7 @@ abstract class HTMLFormField {
public static function flattenOptions( $options ) {
$flatOpts = array();
- foreach( $options as $key => $value ) {
+ foreach ( $options as $value ) {
if ( is_array( $value ) ) {
$flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
} else {
@@ -865,14 +976,12 @@ abstract class HTMLFormField {
return $flatOpts;
}
-
}
class HTMLTextField extends HTMLFormField {
-
function getSize() {
- return isset( $this->mParams['size'] )
- ? $this->mParams['size']
+ return isset( $this->mParams['size'] )
+ ? $this->mParams['size']
: 45;
}
@@ -887,7 +996,7 @@ class HTMLTextField extends HTMLFormField {
if ( isset( $this->mParams['maxlength'] ) ) {
$attribs['maxlength'] = $this->mParams['maxlength'];
}
-
+
if ( !empty( $this->mParams['disabled'] ) ) {
$attribs['disabled'] = 'disabled';
}
@@ -900,13 +1009,13 @@ class HTMLTextField extends HTMLFormField {
$attribs[$param] = $this->mParams[$param];
}
}
- foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as
- $param ) {
+
+ foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as $param ) {
if ( isset( $this->mParams[$param] ) ) {
$attribs[$param] = '';
}
}
-
+
# Implement tiny differences between some field variants
# here, rather than creating a new class for each one which
# is essentially just a clone of this one.
@@ -934,18 +1043,18 @@ class HTMLTextField extends HTMLFormField {
}
}
class HTMLTextAreaField extends HTMLFormField {
-
function getCols() {
- return isset( $this->mParams['cols'] )
- ? $this->mParams['cols']
+ return isset( $this->mParams['cols'] )
+ ? $this->mParams['cols']
: 80;
}
+
function getRows() {
- return isset( $this->mParams['rows'] )
- ? $this->mParams['rows']
+ return isset( $this->mParams['rows'] )
+ ? $this->mParams['rows']
: 25;
}
-
+
function getInputHTML( $value ) {
$attribs = array(
'id' => $this->mID,
@@ -958,10 +1067,11 @@ class HTMLTextAreaField extends HTMLFormField {
if ( !empty( $this->mParams['disabled'] ) ) {
$attribs['disabled'] = 'disabled';
}
+
if ( !empty( $this->mParams['readonly'] ) ) {
$attribs['readonly'] = 'readonly';
}
-
+
foreach ( array( 'required', 'autofocus' ) as $param ) {
if ( isset( $this->mParams[$param] ) ) {
$attribs[$param] = '';
@@ -976,36 +1086,43 @@ class HTMLTextAreaField extends HTMLFormField {
* A field that will contain a numeric value
*/
class HTMLFloatField extends HTMLTextField {
-
function getSize() {
- return isset( $this->mParams['size'] )
- ? $this->mParams['size']
+ return isset( $this->mParams['size'] )
+ ? $this->mParams['size']
: 20;
}
function validate( $value, $alldata ) {
$p = parent::validate( $value, $alldata );
- if ( $p !== true ) return $p;
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ $value = trim( $value );
- if ( floatval( $value ) != $value ) {
+ # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers
+ # with the addition that a leading '+' sign is ok.
+ if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
return wfMsgExt( 'htmlform-float-invalid', 'parse' );
}
- $in_range = true;
-
- # The "int" part of these message names is rather confusing.
+ # The "int" part of these message names is rather confusing.
# They make equal sense for all numbers.
if ( isset( $this->mParams['min'] ) ) {
$min = $this->mParams['min'];
- if ( $min > $value )
+
+ if ( $min > $value ) {
return wfMsgExt( 'htmlform-int-toolow', 'parse', array( $min ) );
+ }
}
if ( isset( $this->mParams['max'] ) ) {
$max = $this->mParams['max'];
- if( $max < $value )
+
+ if ( $max < $value ) {
return wfMsgExt( 'htmlform-int-toohigh', 'parse', array( $max ) );
+ }
}
return true;
@@ -1019,9 +1136,18 @@ class HTMLIntField extends HTMLFloatField {
function validate( $value, $alldata ) {
$p = parent::validate( $value, $alldata );
- if ( $p !== true ) return $p;
+ if ( $p !== true ) {
+ return $p;
+ }
- if ( intval( $value ) != $value ) {
+ # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers
+ # with the addition that a leading '+' sign is ok. Note that leading zeros
+ # are fine, and will be left in the input, which is useful for things like
+ # phone numbers when you know that they are integers (the HTML5 type=tel
+ # input does not require its value to be numeric). If you want a tidier
+ # value to, eg, save in the DB, clean it up with intval().
+ if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) )
+ ) {
return wfMsgExt( 'htmlform-int-invalid', 'parse' );
}
@@ -1034,17 +1160,19 @@ class HTMLIntField extends HTMLFloatField {
*/
class HTMLCheckField extends HTMLFormField {
function getInputHTML( $value ) {
- if ( !empty( $this->mParams['invert'] ) )
+ if ( !empty( $this->mParams['invert'] ) ) {
$value = !$value;
+ }
- $attr = $this->getTooltipAndAccessKey();
+ $attr = $this->getTooltipAndAccessKey();
$attr['id'] = $this->mID;
- if( !empty( $this->mParams['disabled'] ) ) {
+
+ if ( !empty( $this->mParams['disabled'] ) ) {
$attr['disabled'] = 'disabled';
}
- return Xml::check( $this->mName, $value, $attr ) . '&nbsp;' .
- Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
+ return Xml::check( $this->mName, $value, $attr ) . '&#160;' .
+ Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
}
/**
@@ -1052,7 +1180,7 @@ class HTMLCheckField extends HTMLFormField {
* added in getInputHTML(), rather than HTMLFormField::getRow()
*/
function getLabel() {
- return '&nbsp;';
+ return '&#160;';
}
function loadDataFromRequest( $request ) {
@@ -1062,7 +1190,7 @@ class HTMLCheckField extends HTMLFormField {
}
// GetCheck won't work like we want for checks.
- if( $request->getCheck( 'wpEditToken' ) ) {
+ if ( $request->getCheck( 'wpEditToken' ) ) {
// XOR has the following truth table, which is what we want
// INVERT VALUE | OUTPUT
// true true | false
@@ -1080,12 +1208,15 @@ class HTMLCheckField extends HTMLFormField {
* A select dropdown field. Basically a wrapper for Xmlselect class
*/
class HTMLSelectField extends HTMLFormField {
-
function validate( $value, $alldata ) {
$p = parent::validate( $value, $alldata );
- if( $p !== true ) return $p;
+
+ if ( $p !== true ) {
+ return $p;
+ }
$validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
+
if ( in_array( $value, $validOptions ) )
return true;
else
@@ -1098,13 +1229,18 @@ class HTMLSelectField extends HTMLFormField {
# If one of the options' 'name' is int(0), it is automatically selected.
# because PHP sucks and things int(0) == 'some string'.
# Working around this by forcing all of them to strings.
- $options = array_map( 'strval', $this->mParams['options'] );
+ foreach( $this->mParams['options'] as $key => &$opt ){
+ if( is_int( $opt ) ){
+ $opt = strval( $opt );
+ }
+ }
+ unset( $opt ); # PHP keeps $opt around as a reference, which is a bit scary
- if( !empty( $this->mParams['disabled'] ) ) {
+ if ( !empty( $this->mParams['disabled'] ) ) {
$select->setAttribute( 'disabled', 'disabled' );
}
- $select->addOptions( $options );
+ $select->addOptions( $this->mParams['options'] );
return $select->getHTML();
}
@@ -1117,30 +1253,33 @@ class HTMLSelectOrOtherField extends HTMLTextField {
static $jsAdded = false;
function __construct( $params ) {
- if( !in_array( 'other', $params['options'], true ) ) {
+ if ( !in_array( 'other', $params['options'], true ) ) {
$params['options'][wfMsg( 'htmlform-selectorother-other' )] = 'other';
}
parent::__construct( $params );
}
-
+
static function forceToStringRecursive( $array ) {
- if ( is_array($array) ) {
- return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array);
+ if ( is_array( $array ) ) {
+ return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array );
} else {
- return strval($array);
+ return strval( $array );
}
}
function getInputHTML( $value ) {
$valInSelect = false;
- if( $value !== false )
- $valInSelect = in_array( $value,
- HTMLFormField::flattenOptions( $this->mParams['options'] ) );
+ if ( $value !== false ) {
+ $valInSelect = in_array(
+ $value,
+ HTMLFormField::flattenOptions( $this->mParams['options'] )
+ );
+ }
$selected = $valInSelect ? $value : 'other';
-
+
$opts = self::forceToStringRecursive( $this->mParams['options'] );
$select = new XmlSelect( $this->mName, $this->mID, $selected );
@@ -1149,7 +1288,8 @@ class HTMLSelectOrOtherField extends HTMLTextField {
$select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
$tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() );
- if( !empty( $this->mParams['disabled'] ) ) {
+
+ if ( !empty( $this->mParams['disabled'] ) ) {
$select->setAttribute( 'disabled', 'disabled' );
$tbAttribs['disabled'] = 'disabled';
}
@@ -1160,19 +1300,21 @@ class HTMLSelectOrOtherField extends HTMLTextField {
$tbAttribs['maxlength'] = $this->mParams['maxlength'];
}
- $textbox = Html::input( $this->mName . '-other',
- $valInSelect ? '' : $value,
- 'text',
- $tbAttribs );
+ $textbox = Html::input(
+ $this->mName . '-other',
+ $valInSelect ? '' : $value,
+ 'text',
+ $tbAttribs
+ );
return "$select<br />\n$textbox";
}
function loadDataFromRequest( $request ) {
- if( $request->getCheck( $this->mName ) ) {
+ if ( $request->getCheck( $this->mName ) ) {
$val = $request->getText( $this->mName );
- if( $val == 'other' ) {
+ if ( $val == 'other' ) {
$val = $request->getText( $this->mName . '-other' );
}
@@ -1187,22 +1329,27 @@ class HTMLSelectOrOtherField extends HTMLTextField {
* Multi-select field
*/
class HTMLMultiSelectField extends HTMLFormField {
-
function validate( $value, $alldata ) {
$p = parent::validate( $value, $alldata );
- if( $p !== true ) return $p;
- if( !is_array( $value ) ) return false;
+ if ( $p !== true ) {
+ return $p;
+ }
- # If all options are valid, array_intersect of the valid options
+ if ( !is_array( $value ) ) {
+ return false;
+ }
+
+ # If all options are valid, array_intersect of the valid options
# and the provided options will return the provided options.
$validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
$validValues = array_intersect( $value, $validOptions );
- if ( count( $validValues ) == count( $value ) )
+ if ( count( $validValues ) == count( $value ) ) {
return true;
- else
+ } else {
return wfMsgExt( 'htmlform-select-badoption', 'parseinline' );
+ }
}
function getInputHTML( $value ) {
@@ -1215,20 +1362,23 @@ class HTMLMultiSelectField extends HTMLFormField {
$html = '';
$attribs = array();
+
if ( !empty( $this->mParams['disabled'] ) ) {
$attribs['disabled'] = 'disabled';
}
- foreach( $options as $label => $info ) {
- if( is_array( $info ) ) {
+ foreach ( $options as $label => $info ) {
+ if ( is_array( $info ) ) {
$html .= Html::rawElement( 'h1', array(), $label ) . "\n";
$html .= $this->formatOptions( $info, $value );
} else {
- $thisAttribs = array( 'id' => $this->mID . "-$info", 'value' => $info );
-
- $checkbox = Xml::check( $this->mName . '[]', in_array( $info, $value ),
- $attribs + $thisAttribs );
- $checkbox .= '&nbsp;' . Html::rawElement( 'label', array( 'for' => $this->mID . "-$info" ), $label );
+ $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info );
+
+ $checkbox = Xml::check(
+ $this->mName . '[]',
+ in_array( $info, $value, true ),
+ $attribs + $thisAttribs );
+ $checkbox .= '&#160;' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label );
$html .= $checkbox . '<br />';
}
@@ -1239,11 +1389,12 @@ class HTMLMultiSelectField extends HTMLFormField {
function loadDataFromRequest( $request ) {
# won't work with getCheck
- if( $request->getCheck( 'wpEditToken' ) ) {
+ if ( $request->getCheck( 'wpEditToken' ) ) {
$arr = $request->getArray( $this->mName );
- if( !$arr )
+ if ( !$arr ) {
$arr = array();
+ }
return $arr;
} else {
@@ -1270,17 +1421,22 @@ class HTMLMultiSelectField extends HTMLFormField {
class HTMLRadioField extends HTMLFormField {
function validate( $value, $alldata ) {
$p = parent::validate( $value, $alldata );
- if( $p !== true ) return $p;
- if( !is_string( $value ) && !is_int( $value ) )
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ if ( !is_string( $value ) && !is_int( $value ) ) {
return false;
+ }
$validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
- if ( in_array( $value, $validOptions ) )
+ if ( in_array( $value, $validOptions ) ) {
return true;
- else
+ } else {
return wfMsgExt( 'htmlform-select-badoption', 'parseinline' );
+ }
}
/**
@@ -1292,7 +1448,7 @@ class HTMLRadioField extends HTMLFormField {
return $html;
}
-
+
function formatOptions( $options, $value ) {
$html = '';
@@ -1302,15 +1458,19 @@ class HTMLRadioField extends HTMLFormField {
}
# TODO: should this produce an unordered list perhaps?
- foreach( $options as $label => $info ) {
- if( is_array( $info ) ) {
+ foreach ( $options as $label => $info ) {
+ if ( is_array( $info ) ) {
$html .= Html::rawElement( 'h1', array(), $label ) . "\n";
$html .= $this->formatOptions( $info, $value );
} else {
$id = Sanitizer::escapeId( $this->mID . "-$info" );
- $html .= Xml::radio( $this->mName, $info, $info == $value,
- $attribs + array( 'id' => $id ) );
- $html .= '&nbsp;' .
+ $html .= Xml::radio(
+ $this->mName,
+ $info,
+ $info == $value,
+ $attribs + array( 'id' => $id )
+ );
+ $html .= '&#160;' .
Html::rawElement( 'label', array( 'for' => $id ), $label );
$html .= "<br />\n";
@@ -1353,41 +1513,76 @@ class HTMLInfoField extends HTMLFormField {
}
class HTMLHiddenField extends HTMLFormField {
-
- public function getTableRow( $value ){
- $this->mParent->addHiddenField(
- $this->mParams['name'],
- $this->mParams['default']
+ public function __construct( $params ) {
+ parent::__construct( $params );
+
+ # Per HTML5 spec, hidden fields cannot be 'required'
+ # http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#hidden-state
+ unset( $this->mParams['required'] );
+ }
+
+ public function getTableRow( $value ) {
+ $params = array();
+ if ( $this->mID ) {
+ $params['id'] = $this->mID;
+ }
+
+ $this->mParent->addHiddenField(
+ $this->mName,
+ $this->mDefault,
+ $params
);
+
return '';
}
- public function getInputHTML( $value ){ return ''; }
+ public function getInputHTML( $value ) { return ''; }
}
+/**
+ * Add a submit button inline in the form (as opposed to
+ * HTMLForm::addButton(), which will add it at the end).
+ */
class HTMLSubmitField extends HTMLFormField {
-
- public function getTableRow( $value ){
- $this->mParent->addButton(
- $this->mParams['name'],
- $this->mParams['default'],
- isset($this->mParams['id']) ? $this->mParams['id'] : null,
- $this->getTooltipAndAccessKey()
+
+ function __construct( $info ) {
+ $info['nodata'] = true;
+ parent::__construct( $info );
+ }
+
+ function getInputHTML( $value ) {
+ return Xml::submitButton(
+ $value,
+ array(
+ 'class' => 'mw-htmlform-submit',
+ 'name' => $this->mName,
+ 'id' => $this->mID,
+ )
);
}
+
+ protected function needsLabel() {
+ return false;
+ }
- public function getInputHTML( $value ){ return ''; }
+ /**
+ * Button cannot be invalid
+ */
+ public function validate( $value, $alldata ){
+ return true;
+ }
}
class HTMLEditTools extends HTMLFormField {
public function getInputHTML( $value ) {
return '';
}
+
public function getTableRow( $value ) {
- return "<tr><td></td><td class=\"mw-input\">"
- . '<div class="mw-editTools">'
- . wfMsgExt( empty( $this->mParams['message'] )
- ? 'edittools' : $this->mParams['message'],
+ return "<tr><td></td><td class=\"mw-input\">"
+ . '<div class="mw-editTools">'
+ . wfMsgExt( empty( $this->mParams['message'] )
+ ? 'edittools' : $this->mParams['message'],
array( 'parse', 'content' ) )
. "</div></td></tr>\n";
}
diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php
index 664ceb4f..fe2b48bf 100644
--- a/includes/HistoryBlob.php
+++ b/includes/HistoryBlob.php
@@ -11,12 +11,15 @@ interface HistoryBlob
* Adds an item of text, returns a stub object which points to the item.
* You must call setLocation() on the stub object before storing it to the
* database
- * Returns the key for getItem()
+ *
+ * @return String: the key for getItem()
*/
public function addItem( $text );
/**
* Get item by key, or false if the key is not present
+ *
+ * @return String or false
*/
public function getItem( $key );
@@ -32,6 +35,8 @@ interface HistoryBlob
/**
* Get default text. This is called from Revision::getRevisionText()
+ *
+ * @return String
*/
function getText();
}
@@ -132,27 +137,27 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
}
-/**
- * One-step cache variable to hold base blobs; operations that
- * pull multiple revisions may often pull multiple times from
- * the same blob. By keeping the last-used one open, we avoid
- * redundant unserialization and decompression overhead.
- */
-global $wgBlobCache;
-$wgBlobCache = array();
/**
* Pointer object for an item within a CGZ blob stored in the text table.
*/
class HistoryBlobStub {
+ /**
+ * One-step cache variable to hold base blobs; operations that
+ * pull multiple revisions may often pull multiple times from
+ * the same blob. By keeping the last-used one open, we avoid
+ * redundant unserialization and decompression overhead.
+ */
+ protected static $blobCache = array();
+
var $mOldId, $mHash, $mRef;
/**
- * @param string $hash The content hash of the text
- * @param integer $oldid The old_id for the CGZ object
+ * @param $hash Strng: the content hash of the text
+ * @param $oldid Integer: the old_id for the CGZ object
*/
- function HistoryBlobStub( $hash = '', $oldid = 0 ) {
+ function __construct( $hash = '', $oldid = 0 ) {
$this->mHash = $hash;
}
@@ -180,9 +185,9 @@ class HistoryBlobStub {
function getText() {
$fname = 'HistoryBlobStub::getText';
- global $wgBlobCache;
- if( isset( $wgBlobCache[$this->mOldId] ) ) {
- $obj = $wgBlobCache[$this->mOldId];
+
+ if( isset( self::$blobCache[$this->mOldId] ) ) {
+ $obj = self::$blobCache[$this->mOldId];
} else {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'text', array( 'old_flags', 'old_text' ), array( 'old_id' => $this->mOldId ) );
@@ -220,7 +225,7 @@ class HistoryBlobStub {
// Save this item for reference; if pulling many
// items in a row we'll likely use it again.
$obj->uncompress();
- $wgBlobCache = array( $this->mOldId => $obj );
+ self::$blobCache = array( $this->mOldId => $obj );
}
return $obj->getItem( $this->mHash );
}
@@ -246,9 +251,9 @@ class HistoryBlobCurStub {
var $mCurId;
/**
- * @param integer $curid The cur_id pointed to
+ * @param $curid Integer: the cur_id pointed to
*/
- function HistoryBlobCurStub( $curid = 0 ) {
+ function __construct( $curid = 0 ) {
$this->mCurId = $curid;
}
diff --git a/includes/HistoryPage.php b/includes/HistoryPage.php
index 8f5c2dda..b2cf044a 100644
--- a/includes/HistoryPage.php
+++ b/includes/HistoryPage.php
@@ -19,7 +19,12 @@ class HistoryPage {
const DIR_PREV = 0;
const DIR_NEXT = 1;
- var $article, $title, $skin;
+ /** Contains the Article object. Passed on construction. */
+ private $article;
+ /** The $article title object. Found on construction. */
+ private $title;
+ /** Shortcut to the user Skin object. */
+ private $skin;
/**
* Construct a new HistoryPage.
@@ -34,11 +39,13 @@ class HistoryPage {
$this->preCacheMessages();
}
- function getArticle() {
+ /** Get the Article object we are working on. */
+ public function getArticle() {
return $this->article;
}
- function getTitle() {
+ /** Get the Title object. */
+ public function getTitle() {
return $this->title;
}
@@ -46,12 +53,12 @@ class HistoryPage {
* As we use the same small set of messages in various methods and that
* they are called often, we call them once and save them in $this->message
*/
- function preCacheMessages() {
+ private function preCacheMessages() {
// Precache various messages
- if( !isset( $this->message ) ) {
+ if ( !isset( $this->message ) ) {
$msgs = array( 'cur', 'last', 'pipe-separator' );
- foreach( $msgs as $msg ) {
- $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities') );
+ foreach ( $msgs as $msg ) {
+ $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
}
}
}
@@ -63,17 +70,15 @@ class HistoryPage {
function history() {
global $wgOut, $wgRequest, $wgScript;
- /*
+ /**
* Allow client caching.
*/
- if( $wgOut->checkLastModified( $this->article->getTouched() ) )
+ if ( $wgOut->checkLastModified( $this->article->getTouched() ) )
return; // Client cache fresh and headers sent, nothing more to do.
wfProfileIn( __METHOD__ );
- /*
- * Setup page variables.
- */
+ // Setup page variables.
$wgOut->setPageTitle( wfMsg( 'history-title', $this->title->getPrefixedText() ) );
$wgOut->setPageTitleActionText( wfMsg( 'history_short' ) );
$wgOut->setArticleFlag( false );
@@ -81,8 +86,9 @@ class HistoryPage {
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setSyndicated( true );
$wgOut->setFeedAppendQuery( 'action=history' );
- $wgOut->addScriptFile( 'history.js' );
+ $wgOut->addModules( array( 'mediawiki.legacy.history', 'mediawiki.action.history' ) );
+ // Creation of a subtitle link pointing to [[Special:Log]]
$logPage = SpecialPage::getTitleFor( 'Log' );
$logLink = $this->skin->link(
$logPage,
@@ -93,16 +99,15 @@ class HistoryPage {
);
$wgOut->setSubtitle( $logLink );
+ // Handle atom/RSS feeds.
$feedType = $wgRequest->getVal( 'feed' );
- if( $feedType ) {
+ if ( $feedType ) {
wfProfileOut( __METHOD__ );
return $this->feed( $feedType );
}
- /*
- * Fail if article doesn't exist.
- */
- if( !$this->title->exists() ) {
+ // Fail nicely if article doesn't exist.
+ if ( !$this->title->exists() ) {
$wgOut->addWikiMsg( 'nohistory' );
# show deletion/move log if there is an entry
LogEventsList::showLogExtract(
@@ -123,21 +128,23 @@ class HistoryPage {
/**
* Add date selector to quickly get to a certain time
*/
- $year = $wgRequest->getInt( 'year' );
- $month = $wgRequest->getInt( 'month' );
- $tagFilter = $wgRequest->getVal( 'tagfilter' );
+ $year = $wgRequest->getInt( 'year' );
+ $month = $wgRequest->getInt( 'month' );
+ $tagFilter = $wgRequest->getVal( 'tagfilter' );
$tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
+
/**
* Option to show only revisions that have been (partially) hidden via RevisionDelete
*/
if ( $wgRequest->getBool( 'deleted' ) ) {
- $conds = array("rev_deleted != '0'");
+ $conds = array( "rev_deleted != '0'" );
} else {
$conds = array();
}
$checkDeleted = Xml::checkLabel( wfMsg( 'history-show-deleted' ),
'deleted', 'mw-show-deleted-only', $wgRequest->getBool( 'deleted' ) ) . "\n";
+ // Add the general form
$action = htmlspecialchars( $wgScript );
$wgOut->addHTML(
"<form action=\"$action\" method=\"get\" id=\"mw-history-searchform\">" .
@@ -146,10 +153,10 @@ class HistoryPage {
false,
array( 'id' => 'mw-history-search' )
) .
- Xml::hidden( 'title', $this->title->getPrefixedDBKey() ) . "\n" .
- Xml::hidden( 'action', 'history' ) . "\n" .
- Xml::dateMenu( $year, $month ) . '&nbsp;' .
- ( $tagSelector ? ( implode( '&nbsp;', $tagSelector ) . '&nbsp;' ) : '' ) .
+ Html::hidden( 'title', $this->title->getPrefixedDBKey() ) . "\n" .
+ Html::hidden( 'action', 'history' ) . "\n" .
+ Xml::dateMenu( $year, $month ) . '&#160;' .
+ ( $tagSelector ? ( implode( '&#160;', $tagSelector ) . '&#160;' ) : '' ) .
$checkDeleted .
Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
'</fieldset></form>'
@@ -157,9 +164,7 @@ class HistoryPage {
wfRunHooks( 'PageHistoryBeforeList', array( &$this->article ) );
- /**
- * Do the list
- */
+ // Create and output the list.
$pager = new HistoryPager( $this, $year, $month, $tagFilter, $conds );
$wgOut->addHTML(
$pager->getNavigationBar() .
@@ -184,24 +189,26 @@ class HistoryPage {
function fetchRevisions( $limit, $offset, $direction ) {
$dbr = wfGetDB( DB_SLAVE );
- if( $direction == HistoryPage::DIR_PREV )
- list($dirs, $oper) = array("ASC", ">=");
- else /* $direction == HistoryPage::DIR_NEXT */
- list($dirs, $oper) = array("DESC", "<=");
+ if ( $direction == HistoryPage::DIR_PREV ) {
+ list( $dirs, $oper ) = array( "ASC", ">=" );
+ } else { /* $direction == HistoryPage::DIR_NEXT */
+ list( $dirs, $oper ) = array( "DESC", "<=" );
+ }
- if( $offset )
- $offsets = array("rev_timestamp $oper '$offset'");
- else
+ if ( $offset ) {
+ $offsets = array( "rev_timestamp $oper '$offset'" );
+ } else {
$offsets = array();
+ }
$page_id = $this->title->getArticleID();
return $dbr->select( 'revision',
Revision::selectFields(),
- array_merge(array("rev_page=$page_id"), $offsets),
+ array_merge( array( "rev_page=$page_id" ), $offsets ),
__METHOD__,
array( 'ORDER BY' => "rev_timestamp $dirs",
- 'USE INDEX' => 'page_timestamp', 'LIMIT' => $limit)
+ 'USE INDEX' => 'page_timestamp', 'LIMIT' => $limit )
);
}
@@ -212,7 +219,7 @@ class HistoryPage {
*/
function feed( $type ) {
global $wgFeedClasses, $wgRequest, $wgFeedLimit;
- if( !FeedUtils::checkFeedOutput($type) ) {
+ if ( !FeedUtils::checkFeedOutput( $type ) ) {
return;
}
@@ -226,14 +233,15 @@ class HistoryPage {
// Get a limit on number of feed entries. Provide a sane default
// of 10 if none is defined (but limit to $wgFeedLimit max)
$limit = $wgRequest->getInt( 'limit', 10 );
- if( $limit > $wgFeedLimit || $limit < 1 ) {
+ if ( $limit > $wgFeedLimit || $limit < 1 ) {
$limit = 10;
}
- $items = $this->fetchRevisions($limit, 0, HistoryPage::DIR_NEXT);
+ $items = $this->fetchRevisions( $limit, 0, HistoryPage::DIR_NEXT );
+ // Generate feed elements enclosed between header and footer.
$feed->outHeader();
- if( $items ) {
- foreach( $items as $row ) {
+ if ( $items ) {
+ foreach ( $items as $row ) {
$feed->outItem( $this->feedItem( $row ) );
}
} else {
@@ -272,7 +280,7 @@ class HistoryPage {
$rev->getTimestamp(),
$rev->getComment()
);
- if( $rev->getComment() == '' ) {
+ if ( $rev->getComment() == '' ) {
global $wgContLang;
$title = wfMsgForContent( 'history-feed-item-nocomment',
$rev->getUserText(),
@@ -304,10 +312,10 @@ class HistoryPager extends ReverseChronologicalPager {
protected $oldIdChecked;
protected $preventClickjacking = false;
- function __construct( $historyPage, $year='', $month='', $tagFilter = '', $conds = array() ) {
+ function __construct( $historyPage, $year = '', $month = '', $tagFilter = '', $conds = array() ) {
parent::__construct();
$this->historyPage = $historyPage;
- $this->title = $this->historyPage->title;
+ $this->title = $this->historyPage->getTitle();
$this->tagFilter = $tagFilter;
$this->getDateCond( $year, $month );
$this->conds = $conds;
@@ -328,12 +336,12 @@ class HistoryPager extends ReverseChronologicalPager {
function getQueryInfo() {
$queryInfo = array(
- 'tables' => array('revision'),
+ 'tables' => array( 'revision' ),
'fields' => Revision::selectFields(),
'conds' => array_merge(
- array( 'rev_page' => $this->historyPage->title->getArticleID() ),
+ array( 'rev_page' => $this->historyPage->getTitle()->getArticleID() ),
$this->conds ),
- 'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') ),
+ 'options' => array( 'USE INDEX' => array( 'revision' => 'page_timestamp' ) ),
'join_conds' => array( 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ),
);
ChangeTags::modifyDisplayQuery(
@@ -353,10 +361,11 @@ class HistoryPager extends ReverseChronologicalPager {
}
function formatRow( $row ) {
- if( $this->lastRow ) {
- $latest = ($this->counter == 1 && $this->mIsFirst);
+ if ( $this->lastRow ) {
+ $latest = ( $this->counter == 1 && $this->mIsFirst );
$firstInList = $this->counter == 1;
- $s = $this->historyLine( $this->lastRow, $row, $this->counter++,
+ $this->counter++;
+ $s = $this->historyLine( $this->lastRow, $row,
$this->title->getNotificationTimestamp(), $latest, $firstInList );
} else {
$s = '';
@@ -376,18 +385,26 @@ class HistoryPager extends ReverseChronologicalPager {
$this->counter = 1;
$this->oldIdChecked = 0;
- $wgOut->wrapWikiMsg( "<div class='mw-history-legend'>\n$1</div>", 'histlegend' );
- $s = Xml::openElement( 'form', array( 'action' => $wgScript,
+ $wgOut->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
+ $s = Html::openElement( 'form', array( 'action' => $wgScript,
'id' => 'mw-history-compare' ) ) . "\n";
- $s .= Xml::hidden( 'title', $this->title->getPrefixedDbKey() ) . "\n";
- $s .= Xml::hidden( 'action', 'historysubmit' ) . "\n";
+ $s .= Html::hidden( 'title', $this->title->getPrefixedDbKey() ) . "\n";
+ $s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
+
+ $s .= '<div>' . $this->submitButton( wfMsg( 'compareselectedversions' ),
+ array( 'class' => 'historysubmit' ) ) . "\n";
$this->buttons = '<div>';
- if( $wgUser->isAllowed('deleterevision') ) {
+ $this->buttons .= $this->submitButton( wfMsg( 'compareselectedversions' ),
+ array( 'class' => 'historysubmit' )
+ + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'compareselectedversions' )
+ ) . "\n";
+
+ if ( $wgUser->isAllowed( 'deleterevision' ) ) {
$this->preventClickjacking();
$float = $wgContLang->alignEnd();
# Note bug #20966, <button> is non-standard in IE<8
- $this->buttons .= Xml::element( 'button',
+ $element = Html::element( 'button',
array(
'type' => 'submit',
'name' => 'revisiondelete',
@@ -397,26 +414,21 @@ class HistoryPager extends ReverseChronologicalPager {
),
wfMsg( 'showhideselectedversions' )
) . "\n";
+ $s .= $element;
+ $this->buttons .= $element;
}
- $this->buttons .= $this->submitButton( wfMsg( 'compareselectedversions'),
- array(
- 'class' => 'historysubmit',
- 'accesskey' => wfMsg( 'accesskey-compareselectedversions' ),
- 'title' => wfMsg( 'tooltip-compareselectedversions' ),
- )
- ) . "\n";
$this->buttons .= '</div>';
- $s .= $this->buttons . '<ul id="pagehistory">' . "\n";
+ $s .= '</div><ul id="pagehistory">' . "\n";
return $s;
}
function getEndBody() {
- if( $this->lastRow ) {
+ if ( $this->lastRow ) {
$latest = $this->counter == 1 && $this->mIsFirst;
$firstInList = $this->counter == 1;
- if( $this->mIsBackwards ) {
+ if ( $this->mIsBackwards ) {
# Next row is unknown, but for UI reasons, probably exists if an offset has been specified
- if( $this->mOffset == '' ) {
+ if ( $this->mOffset == '' ) {
$next = null;
} else {
$next = 'unknown';
@@ -425,14 +437,15 @@ class HistoryPager extends ReverseChronologicalPager {
# The next row is the past-the-end row
$next = $this->mPastTheEndRow;
}
- $s = $this->historyLine( $this->lastRow, $next, $this->counter++,
+ $this->counter++;
+ $s = $this->historyLine( $this->lastRow, $next,
$this->title->getNotificationTimestamp(), $latest, $firstInList );
} else {
$s = '';
}
$s .= "</ul>\n";
# Add second buttons only if there is more than one rev
- if( $this->getNumRows() > 2 ) {
+ if ( $this->getNumRows() > 2 ) {
$s .= $this->buttons;
}
$s .= '</form>';
@@ -448,7 +461,7 @@ class HistoryPager extends ReverseChronologicalPager {
*/
function submitButton( $message, $attributes = array() ) {
# Disable submit button if history has 1 revision only
- if( $this->getNumRows() > 1 ) {
+ if ( $this->getNumRows() > 1 ) {
return Xml::submitButton( $message , $attributes );
} else {
return '';
@@ -462,13 +475,12 @@ class HistoryPager extends ReverseChronologicalPager {
*
* @param $row Object: the database row corresponding to the previous line.
* @param $next Mixed: the database row corresponding to the next line.
- * @param $counter Integer: apparently a counter of what row number we're at, counted from the top row = 1.
* @param $notificationtimestamp
* @param $latest Boolean: whether this row corresponds to the page's latest revision.
* @param $firstInList Boolean: whether this row corresponds to the first displayed on this history page.
* @return String: HTML output for the row
*/
- function historyLine( $row, $next, $counter = '', $notificationtimestamp = false,
+ function historyLine( $row, $next, $notificationtimestamp = false,
$latest = false, $firstInList = false )
{
global $wgUser, $wgLang;
@@ -476,8 +488,8 @@ class HistoryPager extends ReverseChronologicalPager {
$rev->setTitle( $this->title );
$curlink = $this->curLink( $rev, $latest );
- $lastlink = $this->lastLink( $rev, $next, $counter );
- $diffButtons = $this->diffButtons( $rev, $firstInList, $counter );
+ $lastlink = $this->lastLink( $rev, $next );
+ $diffButtons = $this->diffButtons( $rev, $firstInList );
$histLinks = Html::rawElement(
'span',
array( 'class' => 'mw-history-histlinks' ),
@@ -489,61 +501,63 @@ class HistoryPager extends ReverseChronologicalPager {
$classes = array();
$del = '';
- // User can delete revisions...
- if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ // Show checkboxes for each revision
+ if ( $wgUser->isAllowed( 'deleterevision' ) ) {
$this->preventClickjacking();
// If revision was hidden from sysops, disable the checkbox
- if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
$del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) );
// Otherwise, enable the checkbox...
} else {
$del = Xml::check( 'showhiderevisions', false,
- array( 'name' => 'ids['.$rev->getId().']' ) );
+ array( 'name' => 'ids[' . $rev->getId() . ']' ) );
}
// User can only view deleted revisions...
- } else if( $rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) {
+ } else if ( $rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) {
// If revision was hidden from sysops, disable the link
- if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
$cdel = $this->getSkin()->revDeleteLinkDisabled( false );
// Otherwise, show the link...
} else {
$query = array( 'type' => 'revision',
- 'target' => $this->title->getPrefixedDbkey(), 'ids' => $rev->getId() );
+ 'target' => $this->title->getPrefixedDbkey(), 'ids' => $rev->getId() );
$del .= $this->getSkin()->revDeleteLink( $query,
$rev->isDeleted( Revision::DELETED_RESTRICTED ), false );
}
}
- if( $del ) $s .= " $del ";
+ if ( $del ) {
+ $s .= " $del ";
+ }
$s .= " $link";
$s .= " <span class='history-user'>" .
$this->getSkin()->revUserTools( $rev, true ) . "</span>";
- if( $rev->isMinor() ) {
+ if ( $rev->isMinor() ) {
$s .= ' ' . ChangesList::flag( 'minor' );
}
- if( !is_null( $size = $rev->getSize() ) && !$rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( !is_null( $size = $rev->getSize() ) && !$rev->isDeleted( Revision::DELETED_TEXT ) ) {
$s .= ' ' . $this->getSkin()->formatRevisionSize( $size );
}
$s .= $this->getSkin()->revComment( $rev, false, true );
- if( $notificationtimestamp && ($row->rev_timestamp >= $notificationtimestamp) ) {
+ if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) {
$s .= ' <span class="updatedmarker">' . wfMsgHtml( 'updatedmarker' ) . '</span>';
}
$tools = array();
# Rollback and undo links
- if( !is_null( $next ) && is_object( $next ) ) {
- if( $latest && $this->title->userCan( 'rollback' ) && $this->title->userCan( 'edit' ) ) {
+ if ( !is_null( $next ) && is_object( $next ) ) {
+ if ( $latest && $this->title->userCan( 'rollback' ) && $this->title->userCan( 'edit' ) ) {
$this->preventClickjacking();
- $tools[] = '<span class="mw-rollback-link">'.
- $this->getSkin()->buildRollbackLink( $rev ).'</span>';
+ $tools[] = '<span class="mw-rollback-link">' .
+ $this->getSkin()->buildRollbackLink( $rev ) . '</span>';
}
- if( $this->title->quickUserCan( 'edit' )
+ if ( $this->title->quickUserCan( 'edit' )
&& !$rev->isDeleted( Revision::DELETED_TEXT )
&& !$next->rev_deleted & Revision::DELETED_TEXT )
{
@@ -566,12 +580,12 @@ class HistoryPager extends ReverseChronologicalPager {
}
}
- if( $tools ) {
+ if ( $tools ) {
$s .= ' (' . $wgLang->pipeList( $tools ) . ')';
}
# Tags
- list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' );
+ list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' );
$classes = array_merge( $classes, $newClasses );
$s .= " $tagSummary";
@@ -593,9 +607,9 @@ class HistoryPager extends ReverseChronologicalPager {
*/
function revLink( $rev ) {
global $wgLang;
- $date = $wgLang->timeanddate( wfTimestamp(TS_MW, $rev->getTimestamp()), true );
+ $date = $wgLang->timeanddate( wfTimestamp( TS_MW, $rev->getTimestamp() ), true );
$date = htmlspecialchars( $date );
- if( !$rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $rev->userCan( Revision::DELETED_TEXT ) ) {
$link = $this->getSkin()->link(
$this->title,
$date,
@@ -604,7 +618,10 @@ class HistoryPager extends ReverseChronologicalPager {
array( 'known', 'noclasses' )
);
} else {
- $link = "<span class=\"history-deleted\">$date</span>";
+ $link = $date;
+ }
+ if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $link = "<span class=\"history-deleted\">$link</span>";
}
return $link;
}
@@ -618,7 +635,7 @@ class HistoryPager extends ReverseChronologicalPager {
*/
function curLink( $rev, $latest ) {
$cur = $this->historyPage->message['cur'];
- if( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ if ( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) {
return $cur;
} else {
return $this->getSkin()->link(
@@ -639,17 +656,16 @@ class HistoryPager extends ReverseChronologicalPager {
*
* @param $prevRev Revision: the previous revision
* @param $next Mixed: the newer revision
- * @param $counter Integer: what row on the history list this is
* @return String
*/
- function lastLink( $prevRev, $next, $counter ) {
+ function lastLink( $prevRev, $next ) {
$last = $this->historyPage->message['last'];
# $next may either be a Row, null, or "unkown"
- $nextRev = is_object($next) ? new Revision( $next ) : $next;
- if( is_null($next) ) {
+ $nextRev = is_object( $next ) ? new Revision( $next ) : $next;
+ if ( is_null( $next ) ) {
# Probably no next row
return $last;
- } elseif( $next === 'unknown' ) {
+ } elseif ( $next === 'unknown' ) {
# Next row probably exists but is unknown, use an oldid=prev link
return $this->getSkin()->link(
$this->title,
@@ -661,8 +677,8 @@ class HistoryPager extends ReverseChronologicalPager {
),
array( 'known', 'noclasses' )
);
- } elseif( !$prevRev->userCan(Revision::DELETED_TEXT)
- || !$nextRev->userCan(Revision::DELETED_TEXT) )
+ } elseif ( !$prevRev->userCan( Revision::DELETED_TEXT )
+ || !$nextRev->userCan( Revision::DELETED_TEXT ) )
{
return $last;
} else {
@@ -684,15 +700,15 @@ class HistoryPager extends ReverseChronologicalPager {
*
* @param $rev Revision object
* @param $firstInList Boolean: is this version the first one?
- * @param $counter Integer: a counter of what row number we're at, counted from the top row = 1.
+ *
* @return String: HTML output for the radio buttons
*/
- function diffButtons( $rev, $firstInList, $counter ) {
- if( $this->getNumRows() > 1 ) {
+ function diffButtons( $rev, $firstInList ) {
+ if ( $this->getNumRows() > 1 ) {
$id = $rev->getId();
$radio = array( 'type' => 'radio', 'value' => $id );
/** @todo: move title texts to javascript */
- if( $firstInList ) {
+ if ( $firstInList ) {
$first = Xml::element( 'input',
array_merge( $radio, array(
'style' => 'visibility:hidden',
@@ -702,10 +718,10 @@ class HistoryPager extends ReverseChronologicalPager {
$checkmark = array( 'checked' => 'checked' );
} else {
# Check visibility of old revisions
- if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ if ( !$rev->userCan( Revision::DELETED_TEXT ) ) {
$radio['disabled'] = 'disabled';
$checkmark = array(); // We will check the next possible one
- } else if( $counter == 2 || !$this->oldIdChecked ) {
+ } else if ( !$this->oldIdChecked ) {
$checkmark = array( 'checked' => 'checked' );
$this->oldIdChecked = $id;
} else {
diff --git a/includes/Hooks.php b/includes/Hooks.php
index dec1c442..168f4bd9 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -1,6 +1,7 @@
<?php
/**
- * Hooks.php -- a tool for running hook functions
+ * A tool for running hook functions.
+ *
* Copyright 2004, 2005 Evan Prodromou <evan@wikitravel.org>.
*
* This program is free software; you can redistribute it and/or modify
@@ -24,9 +25,15 @@
/**
+ * Call hook functions defined in $wgHooks
+ *
* Because programmers assign to $wgHooks, we need to be very
* careful about its contents. So, there's a lot more error-checking
* in here than would normally be necessary.
+ *
+ * @param $event String: event name
+ * @param $args Array: parameters passed to hook functions
+ * @return Boolean
*/
function wfRunHooks($event, $args = array()) {
@@ -39,12 +46,10 @@ function wfRunHooks($event, $args = array()) {
if (!is_array($wgHooks)) {
throw new MWException("Global hooks array is not an array!\n");
- return false;
}
if (!is_array($wgHooks[$event])) {
throw new MWException("Hooks array for event '$event' is not an array!\n");
- return false;
}
foreach ($wgHooks[$event] as $index => $hook) {
@@ -55,6 +60,7 @@ function wfRunHooks($event, $args = array()) {
$data = null;
$have_data = false;
$closure = false;
+ $badhookmsg = false;
/* $hook can be: a function, an object, an array of $function and $data,
* an array of just a function, an array of object and method, or an
@@ -128,13 +134,35 @@ function wfRunHooks($event, $args = array()) {
// Run autoloader (workaround for call_user_func_array bug)
is_callable( $callback );
- /* Call the hook. */
+ /* Call the hook. The documentation of call_user_func_array clearly
+ * states that FALSE is returned on failure. However this is not
+ * case always. In some version of PHP if the function signature
+ * does not match the call signature, PHP will issue an warning:
+ * Param y in x expected to be a reference, value given.
+ *
+ * In that case the call will also return null. The following code
+ * catches that warning and provides better error message. The
+ * function documentation also says that:
+ * In other words, it does not depend on the function signature
+ * whether the parameter is passed by a value or by a reference.
+ * There is also PHP bug http://bugs.php.net/bug.php?id=47554 which
+ * is unsurprisingly marked as bogus. In short handling of failures
+ * with call_user_func_array is a failure, the documentation for that
+ * function is wrong and misleading and PHP developers don't see any
+ * problem here.
+ */
+ $retval = null;
+ set_error_handler( 'hookErrorHandler' );
wfProfileIn( $func );
- $retval = call_user_func_array( $callback, $hook_args );
+ try {
+ $retval = call_user_func_array( $callback, $hook_args );
+ } catch ( MWHookException $e ) {
+ $badhookmsg = $e->getMessage();
+ }
wfProfileOut( $func );
+ restore_error_handler();
/* String return is an error; false return means stop processing. */
-
if ( is_string( $retval ) ) {
global $wgOut;
$wgOut->showFatalError( $retval );
@@ -152,9 +180,14 @@ function wfRunHooks($event, $args = array()) {
} else {
$prettyFunc = strval( $callback );
}
- throw new MWException( "Detected bug in an extension! " .
- "Hook $prettyFunc failed to return a value; " .
- "should return true to continue hook processing or false to abort." );
+ if ( $badhookmsg ) {
+ throw new MWException( "Detected bug in an extension! " .
+ "Hook $prettyFunc has invalid call signature; " . $badhookmsg );
+ } else {
+ throw new MWException( "Detected bug in an extension! " .
+ "Hook $prettyFunc failed to return a value; " .
+ "should return true to continue hook processing or false to abort." );
+ }
} else if ( !$retval ) {
return false;
}
@@ -162,3 +195,12 @@ function wfRunHooks($event, $args = array()) {
return true;
}
+
+function hookErrorHandler( $errno, $errstr ) {
+ if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) {
+ throw new MWHookException( $errstr );
+ }
+ return false;
+}
+
+class MWHookException extends MWException {} \ No newline at end of file
diff --git a/includes/Html.php b/includes/Html.php
index 00183b31..6c802ca3 100644
--- a/includes/Html.php
+++ b/includes/Html.php
@@ -1,21 +1,27 @@
<?php
-# Copyright (C) 2009 Aryeh Gregor
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * Collection of methods to generate HTML content
+ *
+ * Copyright © 2009 Aryeh Gregor
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
/**
* This class is a collection of static functions that serve two purposes:
@@ -38,6 +44,8 @@
* This class is meant to be confined to utility functions that are called from
* trusted code paths. It does not do enforcement of policy like not allowing
* <a> elements.
+ *
+ * @since 1.16
*/
class Html {
# List of void elements from HTML5, section 9.1.2 as of 2009-08-10
@@ -59,10 +67,9 @@ class Html {
);
# Boolean attributes, which may have the value omitted entirely. Manually
- # collected from the HTML5 spec as of 2009-08-10.
+ # collected from the HTML5 spec as of 2010-06-07.
private static $boolAttribs = array(
'async',
- 'autobuffer',
'autofocus',
'autoplay',
'checked',
@@ -72,15 +79,18 @@ class Html {
'formnovalidate',
'hidden',
'ismap',
+ 'itemscope',
'loop',
'multiple',
'novalidate',
'open',
+ 'pubdate',
'readonly',
'required',
'reversed',
'scoped',
'seamless',
+ 'selected',
);
/**
@@ -115,7 +125,7 @@ class Html {
}
return $start;
} else {
- return "$start$contents</$element>";
+ return "$start$contents" . self::closeElement( $element );
}
}
@@ -134,15 +144,23 @@ class Html {
/**
* Identical to rawElement(), but has no third parameter and omits the end
- * tag (and the self-closing / in XML mode for empty elements).
+ * tag (and the self-closing '/' in XML mode for empty elements).
*/
public static function openElement( $element, $attribs = array() ) {
- global $wgHtml5;
+ global $wgHtml5, $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/specs/web-apps/current-work/multipage/syntax.html#optional-tags>
+ if ( !$wgWellFormedXml && !$attribs
+ && in_array( $element, array( 'html', 'head' ) ) ) {
+ return '';
+ }
+
# Remove HTML5-only attributes if we aren't doing HTML5, and disable
# form validation regardless (see bug 23769 and the more detailed
# comment in expandAttributes())
@@ -170,22 +188,6 @@ class Html {
&& !$wgHtml5 ) {
unset( $attribs['type'] );
}
- # Here we're blacklisting some HTML5-only attributes...
- $html5attribs = array(
- 'autocomplete',
- 'autofocus',
- 'max',
- 'min',
- 'multiple',
- 'pattern',
- 'placeholder',
- 'required',
- 'step',
- 'spellcheck',
- );
- foreach ( $html5attribs as $badAttr ) {
- unset( $attribs[$badAttr] );
- }
}
if ( !$wgHtml5 && $element == 'textarea' && isset( $attribs['maxlength'] ) ) {
unset( $attribs['maxlength'] );
@@ -196,6 +198,36 @@ class Html {
}
/**
+ * Returns "</$element>", except if $wgWellFormedXml is off, in which case
+ * it returns the empty string when that's guaranteed to be safe.
+ *
+ * @param $element string Name of the element, e.g., 'a'
+ * @return string A closing tag, if required
+ */
+ public static function closeElement( $element ) {
+ global $wgWellFormedXml;
+
+ $element = strtolower( $element );
+
+ # Reference:
+ # http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
+ if ( !$wgWellFormedXml && in_array( $element, array(
+ 'html',
+ 'head',
+ 'body',
+ 'li',
+ 'dt',
+ 'dd',
+ 'tr',
+ 'td',
+ 'th',
+ ) ) ) {
+ return '';
+ }
+ return "</$element>";
+ }
+
+ /**
* Given an element name and an associative array of element attributes,
* return an array that is functionally identical to the input array, but
* possibly smaller. In particular, attributes might be stripped if they
@@ -344,6 +376,22 @@ class Html {
continue;
}
+ # Here we're blacklisting some HTML5-only attributes...
+ if ( !$wgHtml5 && in_array( $key, array(
+ 'autocomplete',
+ 'autofocus',
+ 'max',
+ 'min',
+ 'multiple',
+ 'pattern',
+ 'placeholder',
+ 'required',
+ 'step',
+ 'spellcheck',
+ ) ) ) {
+ continue;
+ }
+
# See the "Attributes" section in the HTML syntax part of HTML5,
# 9.1.2.3 as of 2009-08-10. Most attributes can have quotation
# marks omitted, but not all. (Although a literal " is not
@@ -499,8 +547,7 @@ class Html {
}
/**
- * Convenience function to produce an input element with type=hidden, like
- * Xml::hidden.
+ * Convenience function to produce an input element with type=hidden
*
* @param $name string name attribute
* @param $value string value attribute
@@ -529,11 +576,68 @@ class Html {
global $wgHtml5;
$attribs['name'] = $name;
if ( !$wgHtml5 ) {
- if ( !isset( $attribs['cols'] ) )
+ if ( !isset( $attribs['cols'] ) ) {
$attribs['cols'] = "";
- if ( !isset( $attribs['rows'] ) )
+ }
+ if ( !isset( $attribs['rows'] ) ) {
$attribs['rows'] = "";
+ }
}
return self::element( 'textarea', $attribs, $value );
}
+
+ /**
+ * Constructs the opening html-tag with necessary doctypes depending on
+ * global variables.
+ *
+ * @param $attribs array Associative array of miscellaneous extra
+ * attributes, passed to Html::element() of html tag.
+ * @return string Raw HTML
+ */
+ public static function htmlHeader( $attribs = array() ) {
+ $ret = '';
+
+ global $wgMimeType, $wgOutputEncoding;
+ if ( self::isXmlMimeType( $wgMimeType ) ) {
+ $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?" . ">\n";
+ }
+
+ global $wgHtml5, $wgHtml5Version, $wgDocType, $wgDTD;
+ global $wgXhtmlNamespaces, $wgXhtmlDefaultNamespace;
+ if ( $wgHtml5 ) {
+ $ret .= "<!DOCTYPE html>\n";
+ if ( $wgHtml5Version ) {
+ $attribs['version'] = $wgHtml5Version;
+ }
+ } else {
+ $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
+ $attribs['xmlns'] = $wgXhtmlDefaultNamespace;
+ foreach ( $wgXhtmlNamespaces as $tag => $ns ) {
+ $attribs["xmlns:$tag"] = $ns;
+ }
+ }
+ $html = Html::openElement( 'html', $attribs );
+ if ( $html ) {
+ $html .= "\n";
+ }
+ $ret .= $html;
+ return $ret;
+ }
+
+ /**
+ * Determines if the given mime type is xml.
+ *
+ * @param $mimetype string MimeType
+ * @return Boolean
+ */
+ public static function isXmlMimeType( $mimetype ) {
+ switch ( $mimetype ) {
+ case 'text/xml':
+ case 'application/xhtml+xml':
+ case 'application/xml':
+ return true;
+ default:
+ return false;
+ }
+ }
}
diff --git a/includes/HttpFunctions.old.php b/includes/HttpFunctions.old.php
new file mode 100644
index 00000000..6d28abc6
--- /dev/null
+++ b/includes/HttpFunctions.old.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * HttpRequest was renamed to MWHttpRequest in order
+ * to prevent conflicts with PHP's HTTP extension
+ * which also defines an HttpRequest class.
+ * http://www.php.net/manual/en/class.httprequest.php
+ *
+ * This is for backwards compatibility.
+ */
+
+class HttpRequest extends MWHttpRequest { }
diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php
index d5983635..e6124426 100644
--- a/includes/HttpFunctions.php
+++ b/includes/HttpFunctions.php
@@ -12,28 +12,37 @@ class Http {
/**
* Perform an HTTP request
- * @param $method string HTTP method. Usually GET/POST
- * @param $url string Full URL to act on
- * @param $options options to pass to HttpRequest object
- * Possible keys for the array:
- * timeout Timeout length in seconds
- * postData An array of key-value pairs or a url-encoded form data
- * proxy The proxy to use. Will use $wgHTTPProxy (if set) otherwise.
- * noProxy Override $wgHTTPProxy (if set) and don't use any proxy at all.
- * sslVerifyHost (curl only) Verify the SSL certificate
- * caInfo (curl only) Provide CA information
- * maxRedirects Maximum number of redirects to follow (defaults to 5)
- * followRedirects Whether to follow redirects (defaults to true)
- * @returns mixed (bool)false on failure or a string on success
+ *
+ * @param $method String: HTTP method. Usually GET/POST
+ * @param $url String: full URL to act on
+ * @param $options Array: options to pass to MWHttpRequest object.
+ * Possible keys for the array:
+ * - timeout Timeout length in seconds
+ * - postData An array of key-value pairs or a url-encoded form data
+ * - proxy The proxy to use.
+ * Will use $wgHTTPProxy (if set) otherwise.
+ * - noProxy Override $wgHTTPProxy (if set) and don't use any proxy at all.
+ * - sslVerifyHost (curl only) Verify hostname against certificate
+ * - sslVerifyCert (curl only) Verify SSL certificate
+ * - caInfo (curl only) Provide CA information
+ * - maxRedirects Maximum number of redirects to follow (defaults to 5)
+ * - followRedirects Whether to follow redirects (defaults to false).
+ * Note: this should only be used when the target URL is trusted,
+ * to avoid attacks on intranet services accessible by HTTP.
+ * @return Mixed: (bool)false on failure or a string on success
*/
public static function request( $method, $url, $options = array() ) {
- wfDebug( "HTTP: $method: $url" );
+ $url = wfExpandUrl( $url );
+ wfDebug( "HTTP: $method: $url\n" );
$options['method'] = strtoupper( $method );
+
if ( !isset( $options['timeout'] ) ) {
$options['timeout'] = 'default';
}
- $req = HttpRequest::factory( $url, $options );
+
+ $req = MWHttpRequest::factory( $url, $options );
$status = $req->execute();
+
if ( $status->isOK() ) {
return $req->getContent();
} else {
@@ -60,11 +69,13 @@ class Http {
/**
* Check if the URL can be served by localhost
- * @param $url string Full url to check
- * @return bool
+ *
+ * @param $url String: full url to check
+ * @return Boolean
*/
public static function isLocalURL( $url ) {
global $wgCommandLineMode, $wgConf;
+
if ( $wgCommandLineMode ) {
return false;
}
@@ -77,6 +88,7 @@ class Http {
$domainParts = explode( '.', $host );
// Check if this domain or any superdomain is listed in $wgConf as a local virtual host
$domainParts = array_reverse( $domainParts );
+
for ( $i = 0; $i < count( $domainParts ); $i++ ) {
$domainPart = $domainParts[$i];
if ( $i == 0 ) {
@@ -84,17 +96,19 @@ class Http {
} else {
$domain = $domainPart . '.' . $domain;
}
+
if ( $wgConf->isLocalVHost( $domain ) ) {
return true;
}
}
}
+
return false;
}
/**
* A standard user-agent we can use for external requests.
- * @returns string
+ * @return String
*/
public static function userAgent() {
global $wgVersion;
@@ -102,15 +116,19 @@ class Http {
}
/**
- * Checks that the given URI is a valid one
+ * Checks that the given URI is a valid one. Hardcoding the
+ * protocols, because we only want protocols that both cURL
+ * and php support.
+ *
+ * @fixme this is wildly inaccurate and fails to actually check most stuff
+ *
* @param $uri Mixed: URI to check for validity
- * @returns bool
+ * @returns Boolean
*/
public static function isValidURI( $uri ) {
return preg_match(
- '/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/',
- $uri,
- $matches
+ '/^https?:\/\/[^\/\s]\S*$/D',
+ $uri
);
}
}
@@ -118,8 +136,11 @@ class Http {
/**
* This wrapper class will call out to curl (if available) or fallback
* to regular PHP if necessary for handling internal HTTP requests.
+ *
+ * Renamed from HttpRequest to MWHttpRequst to avoid conflict with
+ * php's HTTP extension.
*/
-class HttpRequest {
+class MWHttpRequest {
protected $content;
protected $timeout = 'default';
protected $headersOnly = null;
@@ -127,6 +148,7 @@ class HttpRequest {
protected $proxy = null;
protected $noProxy = false;
protected $sslVerifyHost = true;
+ protected $sslVerifyCert = true;
protected $caInfo = null;
protected $method = "GET";
protected $reqHeaders = array();
@@ -134,7 +156,7 @@ class HttpRequest {
protected $parsedUrl;
protected $callback;
protected $maxRedirects = 5;
- protected $followRedirects = true;
+ protected $followRedirects = false;
protected $cookieJar;
@@ -146,8 +168,8 @@ class HttpRequest {
public $status;
/**
- * @param $url string url to use
- * @param $options array (optional) extra params to pass (see Http::request())
+ * @param $url String: url to use
+ * @param $options Array: (optional) extra params to pass (see Http::request())
*/
function __construct( $url, $options = array() ) {
global $wgHTTPTimeout;
@@ -156,21 +178,22 @@ class HttpRequest {
$this->parsedUrl = parse_url( $url );
if ( !Http::isValidURI( $this->url ) ) {
- $this->status = Status::newFatal('http-invalid-url');
+ $this->status = Status::newFatal( 'http-invalid-url' );
} else {
$this->status = Status::newGood( 100 ); // continue
}
- if ( isset($options['timeout']) && $options['timeout'] != 'default' ) {
+ if ( isset( $options['timeout'] ) && $options['timeout'] != 'default' ) {
$this->timeout = $options['timeout'];
} else {
$this->timeout = $wgHTTPTimeout;
}
$members = array( "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
- "method", "followRedirects", "maxRedirects" );
+ "method", "followRedirects", "maxRedirects", "sslVerifyCert" );
+
foreach ( $members as $o ) {
- if ( isset($options[$o]) ) {
+ if ( isset( $options[$o] ) ) {
$this->$o = $options[$o];
}
}
@@ -178,42 +201,54 @@ class HttpRequest {
/**
* Generate a new request object
- * @see HttpRequest::__construct
+ * @see MWHttpRequest::__construct
*/
public static function factory( $url, $options = null ) {
if ( !Http::$httpEngine ) {
Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
} elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
- throw new MWException( __METHOD__.': curl (http://php.net/curl) is not installed, but'.
+ throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
' Http::$httpEngine is set to "curl"' );
}
switch( Http::$httpEngine ) {
- case 'curl':
- return new CurlHttpRequest( $url, $options );
- case 'php':
- if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
- throw new MWException( __METHOD__.': allow_url_fopen needs to be enabled for pure PHP'.
- ' http requests to work. If possible, curl should be used instead. See http://php.net/curl.' );
- }
- return new PhpHttpRequest( $url, $options );
- default:
- throw new MWException( __METHOD__.': The setting of Http::$httpEngine is not valid.' );
+ case 'curl':
+ return new CurlHttpRequest( $url, $options );
+ case 'php':
+ if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
+ throw new MWException( __METHOD__ . ': allow_url_fopen needs to be enabled for pure PHP' .
+ ' http requests to work. If possible, curl should be used instead. See http://php.net/curl.' );
+ }
+ return new PhpHttpRequest( $url, $options );
+ default:
+ throw new MWException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' );
}
}
/**
* Get the body, or content, of the response to the request
- * @return string
+ *
+ * @return String
*/
public function getContent() {
return $this->content;
}
/**
+ * Set the parameters of the request
+
+ * @param $args Array
+ * @todo overload the args param
+ */
+ public function setData( $args ) {
+ $this->postData = $args;
+ }
+
+ /**
* Take care of setting up the proxy
* (override in subclass)
- * @return string
+ *
+ * @return String
*/
public function proxySetup() {
global $wgHTTPProxy;
@@ -221,6 +256,7 @@ class HttpRequest {
if ( $this->proxy ) {
return;
}
+
if ( Http::isLocalURL( $this->url ) ) {
$this->proxy = 'http://localhost:80/';
} elseif ( $wgHTTPProxy ) {
@@ -234,20 +270,20 @@ class HttpRequest {
* Set the refererer header
*/
public function setReferer( $url ) {
- $this->setHeader('Referer', $url);
+ $this->setHeader( 'Referer', $url );
}
/**
* Set the user agent
*/
public function setUserAgent( $UA ) {
- $this->setHeader('User-Agent', $UA);
+ $this->setHeader( 'User-Agent', $UA );
}
/**
* Set an arbitrary header
*/
- public function setHeader($name, $value) {
+ public function setHeader( $name, $value ) {
// I feel like I should normalize the case here...
$this->reqHeaders[$name] = $value;
}
@@ -258,30 +294,51 @@ class HttpRequest {
public function getHeaderList() {
$list = array();
- if( $this->cookieJar ) {
+ if ( $this->cookieJar ) {
$this->reqHeaders['Cookie'] =
- $this->cookieJar->serializeToHttpRequest($this->parsedUrl['path'],
- $this->parsedUrl['host']);
+ $this->cookieJar->serializeToHttpRequest(
+ $this->parsedUrl['path'],
+ $this->parsedUrl['host']
+ );
}
- foreach($this->reqHeaders as $name => $value) {
+
+ foreach ( $this->reqHeaders as $name => $value ) {
$list[] = "$name: $value";
}
+
return $list;
}
/**
- * Set the callback
- * @param $callback callback
+ * Set a read callback to accept data read from the HTTP request.
+ * By default, data is appended to an internal buffer which can be
+ * retrieved through $req->getContent().
+ *
+ * To handle data as it comes in -- especially for large files that
+ * would not fit in memory -- you can instead set your own callback,
+ * in the form function($resource, $buffer) where the first parameter
+ * is the low-level resource being read (implementation specific),
+ * and the second parameter is the data buffer.
+ *
+ * You MUST return the number of bytes handled in the buffer; if fewer
+ * bytes are reported handled than were passed to you, the HTTP fetch
+ * will be aborted.
+ *
+ * @param $callback Callback
*/
public function setCallback( $callback ) {
+ if ( !is_callable( $callback ) ) {
+ throw new MWException( 'Invalid MwHttpRequest callback' );
+ }
$this->callback = $callback;
}
/**
* A generic callback to read the body of the response from a remote
* server.
+ *
* @param $fh handle
- * @param $content string
+ * @param $content String
*/
public function read( $fh, $content ) {
$this->content .= $content;
@@ -290,12 +347,15 @@ class HttpRequest {
/**
* Take care of whatever is necessary to perform the URI request.
+ *
* @return Status
*/
public function execute() {
global $wgTitle;
- if( strtoupper($this->method) == "HEAD" ) {
+ $this->content = "";
+
+ if ( strtoupper( $this->method ) == "HEAD" ) {
$this->headersOnly = true;
}
@@ -303,7 +363,7 @@ class HttpRequest {
$this->postData = wfArrayToCGI( $this->postData );
}
- if ( is_object( $wgTitle ) && !isset($this->reqHeaders['Referer']) ) {
+ if ( is_object( $wgTitle ) && !isset( $this->reqHeaders['Referer'] ) ) {
$this->setReferer( $wgTitle->getFullURL() );
}
@@ -315,8 +375,8 @@ class HttpRequest {
$this->setCallback( array( $this, 'read' ) );
}
- if ( !isset($this->reqHeaders['User-Agent']) ) {
- $this->setUserAgent(Http::userAgent());
+ if ( !isset( $this->reqHeaders['User-Agent'] ) ) {
+ $this->setUserAgent( Http::userAgent() );
}
}
@@ -324,18 +384,20 @@ class HttpRequest {
* Parses the headers, including the HTTP status code and any
* Set-Cookie headers. This function expectes the headers to be
* found in an array in the member variable headerList.
- * @returns nothing
+ *
+ * @return nothing
*/
protected function parseHeader() {
$lastname = "";
- foreach( $this->headerList as $header ) {
- if( preg_match( "#^HTTP/([0-9.]+) (.*)#", $header, $match ) ) {
+
+ foreach ( $this->headerList as $header ) {
+ if ( preg_match( "#^HTTP/([0-9.]+) (.*)#", $header, $match ) ) {
$this->respVersion = $match[1];
$this->respStatus = $match[2];
- } elseif( preg_match( "#^[ \t]#", $header ) ) {
- $last = count($this->respHeaders[$lastname]) - 1;
+ } elseif ( preg_match( "#^[ \t]#", $header ) ) {
+ $last = count( $this->respHeaders[$lastname] ) - 1;
$this->respHeaders[$lastname][$last] .= "\r\n$header";
- } elseif( preg_match( "#^([^:]*):[\t ]*(.*)#", $header, $match ) ) {
+ } elseif ( preg_match( "#^([^:]*):[\t ]*(.*)#", $header, $match ) ) {
$this->respHeaders[strtolower( $match[1] )][] = $match[2];
$lastname = strtolower( $match[1] );
}
@@ -345,35 +407,58 @@ class HttpRequest {
}
/**
- * Sets the member variable status to a fatal status if the HTTP
- * status code was not 200.
- * @returns nothing
+ * Sets HTTPRequest status member to a fatal value with the error
+ * message if the returned integer value of the status code was
+ * not successful (< 300) or a redirect (>=300 and < 400). (see
+ * RFC2616, section 10,
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for a
+ * list of status codes.)
+ *
+ * @return nothing
*/
protected function setStatus() {
- if( !$this->respHeaders ) {
+ if ( !$this->respHeaders ) {
$this->parseHeader();
}
- if((int)$this->respStatus !== 200) {
- list( $code, $message ) = explode(" ", $this->respStatus, 2);
- $this->status->fatal("http-bad-status", $code, $message );
+ if ( (int)$this->respStatus > 399 ) {
+ list( $code, $message ) = explode( " ", $this->respStatus, 2 );
+ $this->status->fatal( "http-bad-status", $code, $message );
}
}
+ /**
+ * Get the integer value of the HTTP status code (e.g. 200 for "200 Ok")
+ * (see RFC2616, section 10, http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ * for a list of status codes.)
+ *
+ * @return Integer
+ */
+ public function getStatus() {
+ if ( !$this->respHeaders ) {
+ $this->parseHeader();
+ }
+
+ return (int)$this->respStatus;
+ }
+
/**
* Returns true if the last status code was a redirect.
- * @return bool
+ *
+ * @return Boolean
*/
public function isRedirect() {
- if( !$this->respHeaders ) {
+ if ( !$this->respHeaders ) {
$this->parseHeader();
}
$status = (int)$this->respStatus;
- if ( $status >= 300 && $status < 400 ) {
+
+ if ( $status >= 300 && $status <= 303 ) {
return true;
}
+
return false;
}
@@ -382,33 +467,39 @@ class HttpRequest {
* request has been executed. Because some headers
* (e.g. Set-Cookie) can appear more than once the, each value of
* the associative array is an array of the values given.
- * @return array
+ *
+ * @return Array
*/
public function getResponseHeaders() {
- if( !$this->respHeaders ) {
+ if ( !$this->respHeaders ) {
$this->parseHeader();
}
+
return $this->respHeaders;
}
/**
* Returns the value of the given response header.
- * @param $header string
- * @return string
+ *
+ * @param $header String
+ * @return String
*/
- public function getResponseHeader($header) {
- if( !$this->respHeaders ) {
+ public function getResponseHeader( $header ) {
+ if ( !$this->respHeaders ) {
$this->parseHeader();
}
+
if ( isset( $this->respHeaders[strtolower ( $header ) ] ) ) {
$v = $this->respHeaders[strtolower ( $header ) ];
return $v[count( $v ) - 1];
}
+
return null;
}
/**
- * Tells the HttpRequest object to use this pre-loaded CookieJar.
+ * Tells the MWHttpRequest object to use this pre-loaded CookieJar.
+ *
* @param $jar CookieJar
*/
public function setCookieJar( $jar ) {
@@ -417,12 +508,14 @@ class HttpRequest {
/**
* Returns the cookie jar in use.
+ *
* @returns CookieJar
*/
public function getCookieJar() {
- if( !$this->respHeaders ) {
+ if ( !$this->respHeaders ) {
$this->parseHeader();
}
+
return $this->cookieJar;
}
@@ -432,23 +525,25 @@ class HttpRequest {
* Set-Cookie headers.
* @see Cookie::set
*/
- public function setCookie( $name, $value = null, $attr = null) {
- if( !$this->cookieJar ) {
+ public function setCookie( $name, $value = null, $attr = null ) {
+ if ( !$this->cookieJar ) {
$this->cookieJar = new CookieJar;
}
- $this->cookieJar->setCookie($name, $value, $attr);
+
+ $this->cookieJar->setCookie( $name, $value, $attr );
}
/**
* Parse the cookies in the response headers and store them in the cookie jar.
*/
protected function parseCookies() {
- if( !$this->cookieJar ) {
+ if ( !$this->cookieJar ) {
$this->cookieJar = new CookieJar;
}
- if( isset( $this->respHeaders['set-cookie'] ) ) {
+
+ if ( isset( $this->respHeaders['set-cookie'] ) ) {
$url = parse_url( $this->getFinalUrl() );
- foreach( $this->respHeaders['set-cookie'] as $cookie ) {
+ foreach ( $this->respHeaders['set-cookie'] as $cookie ) {
$this->cookieJar->parseCookieResponseHeader( $cookie, $url['host'] );
}
}
@@ -456,16 +551,26 @@ class HttpRequest {
/**
* Returns the final URL after all redirections.
- * @returns string
+ *
+ * @return String
*/
public function getFinalUrl() {
- $location = $this->getResponseHeader("Location");
+ $location = $this->getResponseHeader( "Location" );
+
if ( $location ) {
return $location;
}
return $this->url;
}
+
+ /**
+ * Returns true if the backend can follow redirects. Overridden by the
+ * child classes.
+ */
+ public function canFollowRedirects() {
+ return true;
+ }
}
@@ -490,30 +595,33 @@ class Cookie {
* Sets a cookie. Used before a request to set up any individual
* cookies. Used internally after a request to parse the
* Set-Cookie headers.
- * @param $name string the name of the cookie
- * @param $value string the value of the cookie
- * @param $attr array possible key/values:
+ *
+ * @param $value String: the value of the cookie
+ * @param $attr Array: possible key/values:
* expires A date string
* path The path this cookie is used on
* domain Domain this cookie is used on
*/
public function set( $value, $attr ) {
$this->value = $value;
- if( isset( $attr['expires'] ) ) {
+
+ if ( isset( $attr['expires'] ) ) {
$this->isSessionKey = false;
$this->expires = strtotime( $attr['expires'] );
}
- if( isset( $attr['path'] ) ) {
+
+ if ( isset( $attr['path'] ) ) {
$this->path = $attr['path'];
} else {
$this->path = "/";
}
- if( isset( $attr['domain'] ) ) {
- if( self::validateCookieDomain( $attr['domain'] ) ) {
+
+ if ( isset( $attr['domain'] ) ) {
+ if ( self::validateCookieDomain( $attr['domain'] ) ) {
$this->domain = $attr['domain'];
}
} else {
- throw new MWException("You must specify a domain.");
+ throw new MWException( "You must specify a domain." );
}
}
@@ -525,46 +633,52 @@ class Cookie {
* A better method might be to use a blacklist like
* http://publicsuffix.org/
*
- * @param $domain string the domain to validate
- * @param $originDomain string (optional) the domain the cookie originates from
- * @return bool
+ * @param $domain String: the domain to validate
+ * @param $originDomain String: (optional) the domain the cookie originates from
+ * @return Boolean
*/
- public static function validateCookieDomain( $domain, $originDomain = null) {
+ public static function validateCookieDomain( $domain, $originDomain = null ) {
// Don't allow a trailing dot
- if( substr( $domain, -1 ) == "." ) return false;
-
- $dc = explode(".", $domain);
+ if ( substr( $domain, -1 ) == "." ) {
+ return false;
+ }
- // Don't allow cookies for "localhost", "ls" or other dot-less hosts
- if( count($dc) < 2 ) return false;
+ $dc = explode( ".", $domain );
// Only allow full, valid IP addresses
- if( preg_match( '/^[0-9.]+$/', $domain ) ) {
- if( count( $dc ) != 4 ) return false;
+ if ( preg_match( '/^[0-9.]+$/', $domain ) ) {
+ if ( count( $dc ) != 4 ) {
+ return false;
+ }
- if( ip2long( $domain ) === false ) return false;
+ if ( ip2long( $domain ) === false ) {
+ return false;
+ }
- if( $originDomain == null || $originDomain == $domain ) return true;
+ if ( $originDomain == null || $originDomain == $domain ) {
+ return true;
+ }
}
// Don't allow cookies for "co.uk" or "gov.uk", etc, but allow "supermarket.uk"
- if( strrpos( $domain, "." ) - strlen( $domain ) == -3 ) {
- if( (count($dc) == 2 && strlen( $dc[0] ) <= 2 )
- || (count($dc) == 3 && strlen( $dc[0] ) == "" && strlen( $dc[1] ) <= 2 ) ) {
+ if ( strrpos( $domain, "." ) - strlen( $domain ) == -3 ) {
+ if ( ( count( $dc ) == 2 && strlen( $dc[0] ) <= 2 )
+ || ( count( $dc ) == 3 && strlen( $dc[0] ) == "" && strlen( $dc[1] ) <= 2 ) ) {
return false;
}
- if( (count($dc) == 2 || (count($dc) == 3 && $dc[0] == "") )
- && preg_match( '/(com|net|org|gov|edu)\...$/', $domain) ) {
+ if ( ( count( $dc ) == 2 || ( count( $dc ) == 3 && $dc[0] == "" ) )
+ && preg_match( '/(com|net|org|gov|edu)\...$/', $domain ) ) {
return false;
}
}
- if( $originDomain != null ) {
- if( substr( $domain, 0, 1 ) != "." && $domain != $originDomain ) {
+ if ( $originDomain != null ) {
+ if ( substr( $domain, 0, 1 ) != "." && $domain != $originDomain ) {
return false;
}
- if( substr( $domain, 0, 1 ) == "."
+
+ if ( substr( $domain, 0, 1 ) == "."
&& substr_compare( $originDomain, $domain, -strlen( $domain ),
strlen( $domain ), TRUE ) != 0 ) {
return false;
@@ -576,47 +690,50 @@ class Cookie {
/**
* Serialize the cookie jar into a format useful for HTTP Request headers.
- * @param $path string the path that will be used. Required.
- * @param $domain string the domain that will be used. Required.
- * @return string
+ *
+ * @param $path String: the path that will be used. Required.
+ * @param $domain String: the domain that will be used. Required.
+ * @return String
*/
public function serializeToHttpRequest( $path, $domain ) {
$ret = "";
- if( $this->canServeDomain( $domain )
+ if ( $this->canServeDomain( $domain )
&& $this->canServePath( $path )
&& $this->isUnExpired() ) {
- $ret = $this->name ."=". $this->value;
+ $ret = $this->name . "=" . $this->value;
}
return $ret;
}
protected function canServeDomain( $domain ) {
- if( $domain == $this->domain
- || ( strlen( $domain) > strlen( $this->domain )
- && substr( $this->domain, 0, 1) == "."
+ if ( $domain == $this->domain
+ || ( strlen( $domain ) > strlen( $this->domain )
+ && substr( $this->domain, 0, 1 ) == "."
&& substr_compare( $domain, $this->domain, -strlen( $this->domain ),
strlen( $this->domain ), TRUE ) == 0 ) ) {
return true;
}
+
return false;
}
protected function canServePath( $path ) {
- if( $this->path && substr_compare( $this->path, $path, 0, strlen( $this->path ) ) == 0 ) {
+ if ( $this->path && substr_compare( $this->path, $path, 0, strlen( $this->path ) ) == 0 ) {
return true;
}
+
return false;
}
protected function isUnExpired() {
- if( $this->isSessionKey || $this->expires > time() ) {
+ if ( $this->isSessionKey || $this->expires > time() ) {
return true;
}
+
return false;
}
-
}
class CookieJar {
@@ -626,12 +743,13 @@ class CookieJar {
* Set a cookie in the cookie jar. Make sure only one cookie per-name exists.
* @see Cookie::set()
*/
- public function setCookie ($name, $value, $attr) {
+ public function setCookie ( $name, $value, $attr ) {
/* cookies: case insensitive, so this should work.
* We'll still send the cookies back in the same case we got them, though.
*/
- $index = strtoupper($name);
- if( isset( $this->cookie[$index] ) ) {
+ $index = strtoupper( $name );
+
+ if ( isset( $this->cookie[$index] ) ) {
$this->cookie[$index]->set( $value, $attr );
} else {
$this->cookie[$index] = new Cookie( $name, $value, $attr );
@@ -644,38 +762,46 @@ class CookieJar {
public function serializeToHttpRequest( $path, $domain ) {
$cookies = array();
- foreach( $this->cookie as $c ) {
+ foreach ( $this->cookie as $c ) {
$serialized = $c->serializeToHttpRequest( $path, $domain );
- if ( $serialized ) $cookies[] = $serialized;
+
+ if ( $serialized ) {
+ $cookies[] = $serialized;
+ }
}
- return implode("; ", $cookies);
+ return implode( "; ", $cookies );
}
/**
* Parse the content of an Set-Cookie HTTP Response header.
- * @param $cookie string
+ *
+ * @param $cookie String
+ * @param $domain String: cookie's domain
*/
public function parseCookieResponseHeader ( $cookie, $domain ) {
$len = strlen( "Set-Cookie:" );
+
if ( substr_compare( "Set-Cookie:", $cookie, 0, $len, TRUE ) === 0 ) {
$cookie = substr( $cookie, $len );
}
$bit = array_map( 'trim', explode( ";", $cookie ) );
- if ( count($bit) >= 1 ) {
- list($name, $value) = explode( "=", array_shift( $bit ), 2 );
+
+ if ( count( $bit ) >= 1 ) {
+ list( $name, $value ) = explode( "=", array_shift( $bit ), 2 );
$attr = array();
- foreach( $bit as $piece ) {
+
+ foreach ( $bit as $piece ) {
$parts = explode( "=", $piece );
- if( count( $parts ) > 1 ) {
+ if ( count( $parts ) > 1 ) {
$attr[strtolower( $parts[0] )] = $parts[1];
} else {
$attr[strtolower( $parts[0] )] = true;
}
}
- if( !isset( $attr['domain'] ) ) {
+ if ( !isset( $attr['domain'] ) ) {
$attr['domain'] = $domain;
} elseif ( !Cookie::validateCookieDomain( $attr['domain'], $domain ) ) {
return null;
@@ -686,11 +812,10 @@ class CookieJar {
}
}
-
/**
- * HttpRequest implemented using internal curl compiled into PHP
+ * MWHttpRequest implemented using internal curl compiled into PHP
*/
-class CurlHttpRequest extends HttpRequest {
+class CurlHttpRequest extends MWHttpRequest {
static $curlMessageMap = array(
6 => 'http-host-unreachable',
28 => 'http-timed-out'
@@ -706,26 +831,33 @@ class CurlHttpRequest extends HttpRequest {
public function execute() {
parent::execute();
+
if ( !$this->status->isOK() ) {
return $this->status;
}
+
$this->curlOptions[CURLOPT_PROXY] = $this->proxy;
$this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout;
$this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
$this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback;
- $this->curlOptions[CURLOPT_HEADERFUNCTION] = array($this, "readHeader");
+ $this->curlOptions[CURLOPT_HEADERFUNCTION] = array( $this, "readHeader" );
$this->curlOptions[CURLOPT_MAXREDIRS] = $this->maxRedirects;
+ $this->curlOptions[CURLOPT_ENCODING] = ""; # Enable compression
/* not sure these two are actually necessary */
- if(isset($this->reqHeaders['Referer'])) {
+ if ( isset( $this->reqHeaders['Referer'] ) ) {
$this->curlOptions[CURLOPT_REFERER] = $this->reqHeaders['Referer'];
}
$this->curlOptions[CURLOPT_USERAGENT] = $this->reqHeaders['User-Agent'];
- if ( $this->sslVerifyHost ) {
+ if ( isset( $this->sslVerifyHost ) ) {
$this->curlOptions[CURLOPT_SSL_VERIFYHOST] = $this->sslVerifyHost;
}
+ if ( isset( $this->sslVerifyCert ) ) {
+ $this->curlOptions[CURLOPT_SSL_VERIFYPEER] = $this->sslVerifyCert;
+ }
+
if ( $this->caInfo ) {
$this->curlOptions[CURLOPT_CAINFO] = $this->caInfo;
}
@@ -747,12 +879,18 @@ class CurlHttpRequest extends HttpRequest {
$this->curlOptions[CURLOPT_HTTPHEADER] = $this->getHeaderList();
$curlHandle = curl_init( $this->url );
+
if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) {
- throw new MWException("Error setting curl options.");
+ throw new MWException( "Error setting curl options." );
}
- if ( ! @curl_setopt( $curlHandle, CURLOPT_FOLLOWLOCATION, $this->followRedirects ) ) {
- wfDebug("Couldn't set CURLOPT_FOLLOWLOCATION. Probably safe_mode or open_basedir is set.");
- /* Continue the processing. If it were in curl_setopt_array, processing would have halted on its entry */
+
+ if ( $this->followRedirects && $this->canFollowRedirects() ) {
+ if ( ! @curl_setopt( $curlHandle, CURLOPT_FOLLOWLOCATION, true ) ) {
+ wfDebug( __METHOD__ . ": Couldn't set CURLOPT_FOLLOWLOCATION. " .
+ "Probably safe_mode or open_basedir is set.\n" );
+ // Continue the processing. If it were in curl_setopt_array,
+ // processing would have halted on its entry
+ }
}
if ( false === curl_exec( $curlHandle ) ) {
@@ -764,20 +902,33 @@ class CurlHttpRequest extends HttpRequest {
$this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) );
}
} else {
- $this->headerList = explode("\r\n", $this->headerText);
+ $this->headerList = explode( "\r\n", $this->headerText );
}
curl_close( $curlHandle );
$this->parseHeader();
$this->setStatus();
+
return $this->status;
}
-}
-class PhpHttpRequest extends HttpRequest {
- protected $manuallyRedirect = false;
+ public function canFollowRedirects() {
+ if ( strval( ini_get( 'open_basedir' ) ) !== '' || wfIniGetBool( 'safe_mode' ) ) {
+ wfDebug( "Cannot follow redirects in safe mode\n" );
+ return false;
+ }
+
+ if ( !defined( 'CURLOPT_REDIR_PROTOCOLS' ) ) {
+ wfDebug( "Cannot follow redirects with libcurl < 7.19.4 due to CVE-2009-0037\n" );
+ return false;
+ }
+
+ return true;
+ }
+}
+class PhpHttpRequest extends MWHttpRequest {
protected function urlToTcp( $url ) {
$parsedUrl = parse_url( $url );
@@ -789,11 +940,10 @@ class PhpHttpRequest extends HttpRequest {
// At least on Centos 4.8 with PHP 5.1.6, using max_redirects to follow redirects
// causes a segfault
- if ( version_compare( '5.1.7', phpversion(), '>' ) ) {
- $this->manuallyRedirect = true;
- }
+ $manuallyRedirect = version_compare( phpversion(), '5.1.7', '<' );
- if ( $this->parsedUrl['scheme'] != 'http' ) {
+ if ( $this->parsedUrl['scheme'] != 'http' &&
+ $this->parsedUrl['scheme'] != 'https' ) {
$this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] );
}
@@ -810,14 +960,14 @@ class PhpHttpRequest extends HttpRequest {
$options['request_fulluri'] = true;
}
- if ( !$this->followRedirects || $this->manuallyRedirect ) {
+ if ( !$this->followRedirects || $manuallyRedirect ) {
$options['max_redirects'] = 0;
} else {
$options['max_redirects'] = $this->maxRedirects;
}
$options['method'] = $this->method;
- $options['header'] = implode("\r\n", $this->getHeaderList());
+ $options['header'] = implode( "\r\n", $this->getHeaderList() );
// Note that at some future point we may want to support
// HTTP/1.1, but we'd have to write support for chunking
// in version of PHP < 5.3.1
@@ -833,7 +983,7 @@ class PhpHttpRequest extends HttpRequest {
$oldTimeout = false;
if ( version_compare( '5.2.1', phpversion(), '>' ) ) {
- $oldTimeout = ini_set('default_socket_timeout', $this->timeout);
+ $oldTimeout = ini_set( 'default_socket_timeout', $this->timeout );
} else {
$options['timeout'] = $this->timeout;
}
@@ -843,25 +993,42 @@ class PhpHttpRequest extends HttpRequest {
$this->headerList = array();
$reqCount = 0;
$url = $this->url;
+
do {
- $again = false;
$reqCount++;
wfSuppressWarnings();
$fh = fopen( $url, "r", false, $context );
wfRestoreWarnings();
- if ( $fh ) {
- $result = stream_get_meta_data( $fh );
- $this->headerList = $result['wrapper_data'];
- $this->parseHeader();
- $url = $this->getResponseHeader("Location");
- $again = $this->manuallyRedirect && $this->followRedirects && $url
- && $this->isRedirect() && $this->maxRedirects > $reqCount;
+
+ if ( !$fh ) {
+ break;
+ }
+
+ $result = stream_get_meta_data( $fh );
+ $this->headerList = $result['wrapper_data'];
+ $this->parseHeader();
+
+ if ( !$manuallyRedirect || !$this->followRedirects ) {
+ break;
}
- } while ( $again );
+
+ # Handle manual redirection
+ if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) {
+ break;
+ }
+ # Check security of URL
+ $url = $this->getResponseHeader( "Location" );
+
+ if ( substr( $url, 0, 7 ) !== 'http://' ) {
+ wfDebug( __METHOD__ . ": insecure redirection\n" );
+ break;
+ }
+ } while ( true );
if ( $oldTimeout !== false ) {
- ini_set('default_socket_timeout', $oldTimeout);
+ ini_set( 'default_socket_timeout', $oldTimeout );
}
+
$this->setStatus();
if ( $fh === false ) {
@@ -874,13 +1041,15 @@ class PhpHttpRequest extends HttpRequest {
return $this->status;
}
- if($this->status->isOK()) {
+ if ( $this->status->isOK() ) {
while ( !feof( $fh ) ) {
$buf = fread( $fh, 8192 );
+
if ( $buf === false ) {
$this->status->fatal( 'http-read-error' );
break;
}
+
if ( strlen( $buf ) ) {
call_user_func( $this->callback, $fh, $buf );
}
diff --git a/includes/IP.php b/includes/IP.php
index bbe70339..5f492c66 100644
--- a/includes/IP.php
+++ b/includes/IP.php
@@ -1,39 +1,69 @@
<?php
-/*
- * @Author "Ashar Voultoiz" <hashar@altern.org>
- * @License GPL v2 or later
+/**
+ * Functions and constants to play with IP addresses and ranges
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 Ashar Voultoiz <hashar at free dot fr>, Aaron Schulz
*/
// Some regex definition to "play" with IP address and IP address blocks
-// An IP is made of 4 bytes from x00 to xFF which is d0 to d255
-define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])');
-define( 'RE_IP_ADD' , RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
+// An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255
+define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' );
+define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
// An IPv4 block is an IP address and a prefix (d1 to d32)
-define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)');
-define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX);
-// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
+define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
+define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
+
+// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
+// However, the "::" abbreviation can be used on consecutive x0000 words.
define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
-define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
-define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
-// An IPv6 block is an IP address and a prefix (d1 to d128)
define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)');
-// An IPv6 IP is made up of 8 octets. However abbreviations like "::" can be used.
-// This is lax! Number of octets/double colons validation not done.
define( 'RE_IPV6_ADD',
- '(' .
- ':(:' . RE_IPV6_WORD . '){1,7}' . // IPs that start with ":"
- '|' .
- RE_IPV6_WORD . '(:{1,2}' . RE_IPV6_WORD . '|::$){1,7}' . // IPs that don't start with ":"
+ '(?:' . // starts with "::" (including "::")
+ ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
+ '|' . // ends with "::" (except "::")
+ RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
+ '|' . // contains one "::" in the middle, ending in "::WORD"
+ RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,5}' . '::' . RE_IPV6_WORD .
+ '|' . // contains one "::" in the middle, not ending in "::WORD" (regex for PCRE 4.0+)
+ RE_IPV6_WORD . '(?::(?P<abn>:(?P<iabn>))?' . RE_IPV6_WORD . '(?!:(?P=abn))){1,5}' .
+ ':' . RE_IPV6_WORD . '(?P=iabn)' .
+ // NOTE: (?!(?P=abn)) fails iff "::" used twice; (?P=iabn) passes iff a "::" was found.
+ '|' . // contains no "::"
+ RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
')'
+ // NOTE: With PCRE 7.2+, we can combine the two '"::" in the middle' cases into:
+ // RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)'
+ // This also improves regex concatenation by using relative references.
);
+// An IPv6 block is an IP address and a prefix (d1 to d128)
define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
+// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
+define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
+define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
+
// This might be useful for regexps used elsewhere, matches any IPv6 or IPv6 address or network
define( 'IP_ADDRESS_STRING',
'(?:' .
- RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)' . // IPv4
+ RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
'|' .
- RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)' . // IPv6
+ RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
')'
);
@@ -43,284 +73,193 @@ define( 'IP_ADDRESS_STRING',
*/
class IP {
/**
- * Given a string, determine if it as valid IP
- * Unlike isValid(), this looks for networks too
- * @param $ip IP address.
- * @return string
+ * Determine if a string is as valid IP address or network (CIDR prefix).
+ * SIIT IPv4-translated addresses are rejected.
+ * Note: canonicalize() tries to convert translated addresses to IPv4.
+ *
+ * @param $ip String: possible IP address
+ * @return Boolean
*/
public static function isIPAddress( $ip ) {
- if ( !$ip ) return false;
- if ( is_array( $ip ) ) {
- throw new MWException( "invalid value passed to " . __METHOD__ );
- }
- // IPv6 IPs with two "::" strings are ambiguous and thus invalid
- return preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip) && ( substr_count($ip, '::') < 2 );
+ return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
}
+ /**
+ * Given a string, determine if it as valid IP in IPv6 only.
+ * Note: Unlike isValid(), this looks for networks too.
+ *
+ * @param $ip String: possible IP address
+ * @return Boolean
+ */
public static function isIPv6( $ip ) {
- if ( !$ip ) return false;
- if( is_array( $ip ) ) {
- throw new MWException( "invalid value passed to " . __METHOD__ );
- }
- $doubleColons = substr_count($ip, '::');
- // IPv6 IPs with two "::" strings are ambiguous and thus invalid
- return preg_match( '/^' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)$/', $ip)
- && ( $doubleColons == 1 || substr_count($ip,':') == 7 );
+ return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
}
+ /**
+ * Given a string, determine if it as valid IP in IPv4 only.
+ * Note: Unlike isValid(), this looks for networks too.
+ *
+ * @param $ip String: possible IP address
+ * @return Boolean
+ */
public static function isIPv4( $ip ) {
- if ( !$ip ) return false;
- return preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip);
+ return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
}
/**
- * Given an IP address in dotted-quad notation, returns an IPv6 octet.
- * See http://www.answers.com/topic/ipv4-compatible-address
- * IPs with the first 92 bits as zeros are reserved from IPv6
- * @param $ip quad-dotted IP address.
- * @return string
+ * Validate an IP address. Ranges are NOT considered valid.
+ * SIIT IPv4-translated addresses are rejected.
+ * Note: canonicalize() tries to convert translated addresses to IPv4.
+ *
+ * @param $ip String
+ * @return Boolean: True if it is valid.
*/
- public static function IPv4toIPv6( $ip ) {
- if ( !$ip ) return null;
- // Convert only if needed
- if ( self::isIPv6( $ip ) ) return $ip;
- // IPv4 CIDRs
- if ( strpos( $ip, '/' ) !== false ) {
- $parts = explode( '/', $ip, 2 );
- if ( count( $parts ) != 2 ) {
- return false;
- }
- $network = self::toUnsigned( $parts[0] );
- if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
- $bits = $parts[1] + 96;
- return self::toOctet( $network ) . "/$bits";
- } else {
- return false;
- }
- }
- return self::toOctet( self::toUnsigned( $ip ) );
+ public static function isValid( $ip ) {
+ return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
+ || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
}
/**
- * Given an IPv6 address in octet notation, returns an unsigned integer.
- * @param $ip octet ipv6 IP address.
- * @return string
+ * Validate an IP Block (valid address WITH a valid prefix).
+ * SIIT IPv4-translated addresses are rejected.
+ * Note: canonicalize() tries to convert translated addresses to IPv4.
+ *
+ * @param $ipblock String
+ * @return Boolean: True if it is valid.
*/
- public static function toUnsigned6( $ip ) {
- if ( !$ip ) return null;
- $ip = explode(':', self::sanitizeIP( $ip ) );
- $r_ip = '';
- foreach ($ip as $v) {
- $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
- }
- $r_ip = wfBaseConvert( $r_ip, 16, 10 );
- return $r_ip;
+ public static function isValidBlock( $ipblock ) {
+ return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
+ || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
}
/**
- * Given an IPv6 address in octet notation, returns the expanded octet.
- * IPv4 IPs will be trimmed, thats it...
- * @param $ip octet ipv6 IP address.
- * @return string
+ * Convert an IP into a nice standard form.
+ * IPv6 addresses in octet notation are expanded to 8 words.
+ * IPv4 addresses are just trimmed.
+ *
+ * @param $ip String: IP address in quad or octet form (CIDR or not).
+ * @return String
*/
public static function sanitizeIP( $ip ) {
$ip = trim( $ip );
- if ( $ip === '' ) return null;
- // Trim and return IPv4 addresses
- if ( self::isIPv4($ip) ) return $ip;
- // Only IPv6 addresses can be expanded
- if ( !self::isIPv6($ip) ) return $ip;
+ if ( $ip === '' ) {
+ return null;
+ }
+ if ( self::isIPv4( $ip ) || !self::isIPv6( $ip ) ) {
+ return $ip; // nothing else to do for IPv4 addresses or invalid ones
+ }
// Remove any whitespaces, convert to upper case
$ip = strtoupper( $ip );
// Expand zero abbreviations
$abbrevPos = strpos( $ip, '::' );
if ( $abbrevPos !== false ) {
+ // We know this is valid IPv6. Find the last index of the
+ // address before any CIDR number (e.g. "a:b:c::/24").
+ $CIDRStart = strpos( $ip, "/" );
+ $addressEnd = ( $CIDRStart !== false )
+ ? $CIDRStart - 1
+ : strlen( $ip ) - 1;
// If the '::' is at the beginning...
- if( $abbrevPos == 0 ) {
- $repeat = '0:'; $extra = ''; $pad = 9; // 7+2 (due to '::')
- // If the '::' is at the end...
- } else if( $abbrevPos == (strlen($ip)-2) ) {
- $repeat = ':0'; $extra = ''; $pad = 9; // 7+2 (due to '::')
+ if ( $abbrevPos == 0 ) {
+ $repeat = '0:';
+ $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
+ $pad = 9; // 7+2 (due to '::')
// If the '::' is at the end...
+ } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
+ $repeat = ':0';
+ $extra = '';
+ $pad = 9; // 7+2 (due to '::')
+ // If the '::' is in the middle...
} else {
- $repeat = ':0'; $extra = ':'; $pad = 8; // 6+2 (due to '::')
+ $repeat = ':0';
+ $extra = ':';
+ $pad = 8; // 6+2 (due to '::')
}
- $ip = str_replace('::', str_repeat($repeat, $pad-substr_count($ip,':')).$extra, $ip);
+ $ip = str_replace( '::',
+ str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
+ $ip
+ );
}
// Remove leading zereos from each bloc as needed
- $ip = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip );
+ $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
return $ip;
}
/**
* Given an unsigned integer, returns an IPv6 address in octet notation
- * @param $ip integer IP address.
- * @return string
+ *
+ * @param $ip_int String: IP address.
+ * @return String
*/
public static function toOctet( $ip_int ) {
- // Convert to padded uppercase hex
- $ip_hex = wfBaseConvert($ip_int, 10, 16, 32, false);
- // Separate into 8 octets
- $ip_oct = substr( $ip_hex, 0, 4 );
- for ($n=1; $n < 8; $n++) {
- $ip_oct .= ':' . substr($ip_hex, 4*$n, 4);
- }
- // NO leading zeroes
- $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct );
- return $ip_oct;
+ return self::hexToOctet( wfBaseConvert( $ip_int, 10, 16, 32, false ) );
}
/**
* Convert an IPv4 or IPv6 hexadecimal representation back to readable format
+ *
+ * @param $hex String: number, with "v6-" prefix if it is IPv6
+ * @return String: quad-dotted (IPv4) or octet notation (IPv6)
*/
public static function formatHex( $hex ) {
- if ( substr( $hex, 0, 3 ) == 'v6-' ) {
- return self::hexToOctet( $hex );
- } else {
+ if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
+ return self::hexToOctet( substr( $hex, 3 ) );
+ } else { // IPv4
return self::hexToQuad( $hex );
}
}
-
+
/**
- * Given a hexadecimal number, returns to an IPv6 address in octet notation
- * @param $ip string hex IP
- * @return string
+ * Converts a hexadecimal number to an IPv6 address in octet notation
+ *
+ * @param $ip_hex String: pure hex (no v6- prefix)
+ * @return String (of format a:b:c:d:e:f:g:h)
*/
- public static function hextoOctet( $ip_hex ) {
- // Convert to padded uppercase hex
- $ip_hex = str_pad( strtoupper($ip_hex), 32, '0');
- // Separate into 8 octets
- $ip_oct = substr( $ip_hex, 0, 4 );
- for ($n=1; $n < 8; $n++) {
- $ip_oct .= ':' . substr($ip_hex, 4*$n, 4);
- }
- // NO leading zeroes
- $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct );
+ public static function hexToOctet( $ip_hex ) {
+ // Pad hex to 32 chars (128 bits)
+ $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
+ // Separate into 8 words
+ $ip_oct = substr( $ip_hex, 0, 4 );
+ for ( $n = 1; $n < 8; $n++ ) {
+ $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
+ }
+ // NO leading zeroes
+ $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
return $ip_oct;
}
-
+
/**
- * Converts a hexadecimal number to an IPv4 address in octet notation
- * @param $ip string Hex IP
- * @return string
- */
- public static function hexToQuad( $ip ) {
- // Converts a hexadecimal IP to nnn.nnn.nnn.nnn format
+ * Converts a hexadecimal number to an IPv4 address in quad-dotted notation
+ *
+ * @param $ip_hex String: pure hex
+ * @return String (of format a.b.c.d)
+ */
+ public static function hexToQuad( $ip_hex ) {
+ // Pad hex to 8 chars (32 bits)
+ $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
+ // Separate into four quads
$s = '';
for ( $i = 0; $i < 4; $i++ ) {
if ( $s !== '' ) {
$s .= '.';
}
- $s .= base_convert( substr( $ip, $i * 2, 2 ), 16, 10 );
+ $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
}
return $s;
}
/**
- * Convert a network specification in IPv6 CIDR notation to an integer network and a number of bits
- * @return array(string, int)
- */
- public static function parseCIDR6( $range ) {
- # Expand any IPv6 IP
- $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
- if ( count( $parts ) != 2 ) {
- return array( false, false );
- }
- $network = self::toUnsigned6( $parts[0] );
- if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 128 ) {
- $bits = $parts[1];
- if ( $bits == 0 ) {
- $network = 0;
- } else {
- # Native 32 bit functions WONT work here!!!
- # Convert to a padded binary number
- $network = wfBaseConvert( $network, 10, 2, 128 );
- # Truncate the last (128-$bits) bits and replace them with zeros
- $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
- # Convert back to an integer
- $network = wfBaseConvert( $network, 2, 10 );
- }
- } else {
- $network = false;
- $bits = false;
- }
- return array( $network, $bits );
- }
-
- /**
- * Given a string range in a number of formats, return the start and end of
- * the range in hexadecimal. For IPv6.
- *
- * Formats are:
- * 2001:0db8:85a3::7344/96 CIDR
- * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
- * 2001:0db8:85a3::7344/96 Single IP
- * @return array(string, int)
- */
- public static function parseRange6( $range ) {
- # Expand any IPv6 IP
- $range = IP::sanitizeIP( $range );
- if ( strpos( $range, '/' ) !== false ) {
- # CIDR
- list( $network, $bits ) = self::parseCIDR6( $range );
- if ( $network === false ) {
- $start = $end = false;
- } else {
- $start = wfBaseConvert( $network, 10, 16, 32, false );
- # Turn network to binary (again)
- $end = wfBaseConvert( $network, 10, 2, 128 );
- # Truncate the last (128-$bits) bits and replace them with ones
- $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
- # Convert to hex
- $end = wfBaseConvert( $end, 2, 16, 32, false );
- # see toHex() comment
- $start = "v6-$start"; $end = "v6-$end";
- }
- } elseif ( strpos( $range, '-' ) !== false ) {
- # Explicit range
- list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
- $start = self::toUnsigned6( $start ); $end = self::toUnsigned6( $end );
- if ( $start > $end ) {
- $start = $end = false;
- } else {
- $start = wfBaseConvert( $start, 10, 16, 32, false );
- $end = wfBaseConvert( $end, 10, 16, 32, false );
- }
- # see toHex() comment
- $start = "v6-$start"; $end = "v6-$end";
- } else {
- # Single IP
- $start = $end = self::toHex( $range );
- }
- if ( $start === false || $end === false ) {
- return array( false, false );
- } else {
- return array( $start, $end );
- }
- }
-
- /**
- * Validate an IP address.
- * @return boolean True if it is valid.
- */
- public static function isValid( $ip ) {
- return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip) || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip) );
- }
-
- /**
- * Validate an IP Block.
- * @return boolean True if it is valid.
- */
- public static function isValidBlock( $ipblock ) {
- return ( count(self::toArray($ipblock)) == 1 + 5 );
- }
-
- /**
* Determine if an IP address really is an IP address, and if it is public,
* i.e. not RFC 1918 or similar
* Comes from ProxyTools.php
+ *
+ * @param $ip String
+ * @return Boolean
*/
public static function isPublic( $ip ) {
+ if ( self::isIPv6( $ip ) ) {
+ return self::isPublic6( $ip );
+ }
$n = self::toUnsigned( $ip );
if ( !$n ) {
return false;
@@ -328,7 +267,7 @@ class IP {
// ip2long accepts incomplete addresses, as well as some addresses
// followed by garbage characters. Check that it's really valid.
- if( $ip != long2ip( $n ) ) {
+ if ( $ip != long2ip( $n ) ) {
return false;
}
@@ -354,100 +293,118 @@ class IP {
}
/**
- * Split out an IP block as an array of 4 bytes and a mask,
- * return false if it can't be determined
+ * Determine if an IPv6 address really is an IP address, and if it is public,
+ * i.e. not RFC 4193 or similar
*
- * @param $ip string A quad dotted/octet IP address
- * @return array
+ * @param $ip String
+ * @return Boolean
*/
- public static function toArray( $ipblock ) {
- $matches = array();
- if( preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
- return $matches;
- } else if ( preg_match( '/^' . RE_IPV6_ADD . '(?:\/(?:'.RE_IPV6_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
- return $matches;
- } else {
- return false;
+ private static function isPublic6( $ip ) {
+ static $privateRanges = false;
+ if ( !$privateRanges ) {
+ $privateRanges = array(
+ array( 'fc::', 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ), # RFC 4193 (local)
+ array( '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1' ), # loopback
+ );
}
+ $n = self::toHex( $ip );
+ foreach ( $privateRanges as $r ) {
+ $start = self::toHex( $r[0] );
+ $end = self::toHex( $r[1] );
+ if ( $n >= $start && $n <= $end ) {
+ return false;
+ }
+ }
+ return true;
}
/**
- * Return a zero-padded hexadecimal representation of an IP address.
+ * Return a zero-padded upper case hexadecimal representation of an IP address.
*
* Hexadecimal addresses are used because they can easily be extended to
* IPv6 support. To separate the ranges, the return value from this
* function for an IPv6 address will be prefixed with "v6-", a non-
* hexadecimal string which sorts after the IPv4 addresses.
*
- * @param $ip Quad dotted/octet IP address.
- * @return hexidecimal
+ * @param $ip String: quad dotted/octet IP address.
+ * @return String
*/
public static function toHex( $ip ) {
- $n = self::toUnsigned( $ip );
- if ( $n !== false ) {
- $n = self::isIPv6($ip) ? "v6-" . wfBaseConvert( $n, 10, 16, 32, false ) : wfBaseConvert( $n, 10, 16, 8, false );
+ if ( self::isIPv6( $ip ) ) {
+ $n = 'v6-' . self::IPv6ToRawHex( $ip );
+ } else {
+ $n = self::toUnsigned( $ip );
+ if ( $n !== false ) {
+ $n = wfBaseConvert( $n, 10, 16, 8, false );
+ }
}
return $n;
}
/**
+ * Given an IPv6 address in octet notation, returns a pure hex string.
+ *
+ * @param $ip String: octet ipv6 IP address.
+ * @return String: pure hex (uppercase)
+ */
+ private static function IPv6ToRawHex( $ip ) {
+ $ip = self::sanitizeIP( $ip );
+ if ( !$ip ) {
+ return null;
+ }
+ $r_ip = '';
+ foreach ( explode( ':', $ip ) as $v ) {
+ $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
+ }
+ return $r_ip;
+ }
+
+ /**
* Given an IP address in dotted-quad/octet notation, returns an unsigned integer.
* Like ip2long() except that it actually works and has a consistent error return value.
* Comes from ProxyTools.php
- * @param $ip Quad dotted IP address.
- * @return integer
+ *
+ * @param $ip String: quad dotted IP address.
+ * @return Mixed: string/int/false
*/
public static function toUnsigned( $ip ) {
- // Use IPv6 functions if needed
if ( self::isIPv6( $ip ) ) {
- return self::toUnsigned6( $ip );
- }
- if ( $ip == '255.255.255.255' ) {
- $n = -1;
+ $n = self::toUnsigned6( $ip );
} else {
$n = ip2long( $ip );
- if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
- $n = false;
+ if ( $n < 0 ) {
+ $n += pow( 2, 32 );
}
}
- if ( $n < 0 ) {
- $n += pow( 2, 32 );
- }
return $n;
}
- /**
- * Convert a dotted-quad IP to a signed integer
- * Returns false on failure
- */
- public static function toSigned( $ip ) {
- if ( $ip == '255.255.255.255' ) {
- $n = -1;
- } else {
- $n = ip2long( $ip );
- if ( $n == -1 ) {
- $n = false;
- }
- }
- return $n;
+ private static function toUnsigned6( $ip ) {
+ return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
}
/**
- * Convert a network specification in CIDR notation to an integer network and a number of bits
- * @return array(string, int)
+ * Convert a network specification in CIDR notation
+ * to an integer network and a number of bits
+ *
+ * @param $range String: IP with CIDR prefix
+ * @return array(int or string, int)
*/
public static function parseCIDR( $range ) {
+ if ( self::isIPv6( $range ) ) {
+ return self::parseCIDR6( $range );
+ }
$parts = explode( '/', $range, 2 );
if ( count( $parts ) != 2 ) {
return array( false, false );
}
- $network = self::toSigned( $parts[0] );
- if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
- $bits = $parts[1];
+ list( $network, $bits ) = $parts;
+ $network = ip2long( $network );
+ if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
if ( $bits == 0 ) {
$network = 0;
} else {
- $network &= ~((1 << (32 - $bits)) - 1);
+ $network &= ~( ( 1 << ( 32 - $bits ) ) - 1);
}
# Convert to unsigned
if ( $network < 0 ) {
@@ -461,8 +418,8 @@ class IP {
}
/**
- * Given a string range in a number of formats, return the start and end of
- * the range in hexadecimal.
+ * Given a string range in a number of formats,
+ * return the start and end of the range in hexadecimal.
*
* Formats are:
* 1.2.3.4/24 CIDR
@@ -472,27 +429,31 @@ class IP {
* 2001:0db8:85a3::7344/96 CIDR
* 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
* 2001:0db8:85a3::7344 Single IP
- * @return array(string, int)
+ * @param $range String: IP range
+ * @return array(string, string)
*/
public static function parseRange( $range ) {
- // Use IPv6 functions if needed
- if ( self::isIPv6( $range ) ) {
- return self::parseRange6( $range );
- }
+ // CIDR notation
if ( strpos( $range, '/' ) !== false ) {
- # CIDR
+ if ( self::isIPv6( $range ) ) {
+ return self::parseRange6( $range );
+ }
list( $network, $bits ) = self::parseCIDR( $range );
if ( $network === false ) {
$start = $end = false;
} else {
$start = sprintf( '%08X', $network );
- $end = sprintf( '%08X', $network + pow( 2, (32 - $bits) ) - 1 );
+ $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
}
+ // Explicit range
} elseif ( strpos( $range, '-' ) !== false ) {
- # Explicit range
list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
- if( self::isIPAddress( $start ) && self::isIPAddress( $end ) ) {
- $start = self::toUnsigned( $start ); $end = self::toUnsigned( $end );
+ if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
+ return self::parseRange6( $range );
+ }
+ if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
+ $start = self::toUnsigned( $start );
+ $end = self::toUnsigned( $end );
if ( $start > $end ) {
$start = $end = false;
} else {
@@ -514,17 +475,105 @@ class IP {
}
/**
+ * Convert a network specification in IPv6 CIDR notation to an
+ * integer network and a number of bits
+ *
+ * @return array(string, int)
+ */
+ private static function parseCIDR6( $range ) {
+ # Explode into <expanded IP,range>
+ $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
+ if ( count( $parts ) != 2 ) {
+ return array( false, false );
+ }
+ list( $network, $bits ) = $parts;
+ $network = self::IPv6ToRawHex( $network );
+ if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
+ if ( $bits == 0 ) {
+ $network = "0";
+ } else {
+ # Native 32 bit functions WONT work here!!!
+ # Convert to a padded binary number
+ $network = wfBaseConvert( $network, 16, 2, 128 );
+ # Truncate the last (128-$bits) bits and replace them with zeros
+ $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
+ # Convert back to an integer
+ $network = wfBaseConvert( $network, 2, 10 );
+ }
+ } else {
+ $network = false;
+ $bits = false;
+ }
+ return array( $network, (int)$bits );
+ }
+
+ /**
+ * Given a string range in a number of formats, return the
+ * start and end of the range in hexadecimal. For IPv6.
+ *
+ * Formats are:
+ * 2001:0db8:85a3::7344/96 CIDR
+ * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
+ * 2001:0db8:85a3::7344/96 Single IP
+ * @return array(string, string)
+ */
+ private static function parseRange6( $range ) {
+ # Expand any IPv6 IP
+ $range = IP::sanitizeIP( $range );
+ // CIDR notation...
+ if ( strpos( $range, '/' ) !== false ) {
+ list( $network, $bits ) = self::parseCIDR6( $range );
+ if ( $network === false ) {
+ $start = $end = false;
+ } else {
+ $start = wfBaseConvert( $network, 10, 16, 32, false );
+ # Turn network to binary (again)
+ $end = wfBaseConvert( $network, 10, 2, 128 );
+ # Truncate the last (128-$bits) bits and replace them with ones
+ $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
+ # Convert to hex
+ $end = wfBaseConvert( $end, 2, 16, 32, false );
+ # see toHex() comment
+ $start = "v6-$start";
+ $end = "v6-$end";
+ }
+ // Explicit range notation...
+ } elseif ( strpos( $range, '-' ) !== false ) {
+ list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+ $start = self::toUnsigned6( $start );
+ $end = self::toUnsigned6( $end );
+ if ( $start > $end ) {
+ $start = $end = false;
+ } else {
+ $start = wfBaseConvert( $start, 10, 16, 32, false );
+ $end = wfBaseConvert( $end, 10, 16, 32, false );
+ }
+ # see toHex() comment
+ $start = "v6-$start";
+ $end = "v6-$end";
+ } else {
+ # Single IP
+ $start = $end = self::toHex( $range );
+ }
+ if ( $start === false || $end === false ) {
+ return array( false, false );
+ } else {
+ return array( $start, $end );
+ }
+ }
+
+ /**
* Determine if a given IPv4/IPv6 address is in a given CIDR network
- * @param $addr The address to check against the given range.
- * @param $range The range to check the given address against.
- * @return bool Whether or not the given address is in the given range.
+ *
+ * @param $addr String: the address to check against the given range.
+ * @param $range String: the range to check the given address against.
+ * @return Boolean: whether or not the given address is in the given range.
*/
public static function isInRange( $addr, $range ) {
- // Convert to IPv6 if needed
$hexIP = self::toHex( $addr );
list( $start, $end ) = self::parseRange( $range );
- return (strcmp($hexIP, $start) >= 0 &&
- strcmp($hexIP, $end) <= 0);
+ return ( strcmp( $hexIP, $start ) >= 0 &&
+ strcmp( $hexIP, $end ) <= 0 );
}
/**
@@ -534,29 +583,34 @@ class IP {
* This currently only checks a few IPV4-to-IPv6 related cases. More
* unusual representations may be added later.
*
- * @param $addr something that might be an IP address
- * @return valid dotted quad IPv4 address or null
+ * @param $addr String: something that might be an IP address
+ * @return String: valid dotted quad IPv4 address or null
*/
public static function canonicalize( $addr ) {
- if ( self::isValid( $addr ) )
+ if ( self::isValid( $addr ) ) {
return $addr;
-
+ }
// Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
- if ( strpos($addr,':') !==false && strpos($addr,'.') !==false ) {
- $addr = substr( $addr, strrpos($addr,':')+1 );
- if( self::isIPv4($addr) ) return $addr;
+ if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
+ $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
+ if ( self::isIPv4( $addr ) ) {
+ return $addr;
+ }
}
-
// IPv6 loopback address
$m = array();
- if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) )
- return '127.0.0.1';
-
+ if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
+ return '127.0.0.1';
+ }
// IPv4-mapped and IPv4-compatible IPv6 addresses
- if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) )
+ if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) {
return $m[1];
- if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD . ':' . RE_IPV6_WORD . '$/i', $addr, $m ) )
+ }
+ if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
+ ':' . RE_IPV6_WORD . '$/i', $addr, $m ) )
+ {
return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
+ }
return null; // give up
}
diff --git a/includes/ImageFunctions.php b/includes/ImageFunctions.php
index 5f01ab6e..8eaebd26 100644
--- a/includes/ImageFunctions.php
+++ b/includes/ImageFunctions.php
@@ -1,110 +1,9 @@
<?php
/**
- * Return a rounded pixel equivalent for a labeled CSS/SVG length.
- * http://www.w3.org/TR/SVG11/coords.html#UnitIdentifiers
+ * Global functions related to images
*
- * @param $length String: CSS/SVG length.
- * @param $viewportSize: Float optional scale for percentage units...
- * @return float: length in pixels
+ * @file
*/
-function wfScaleSVGUnit( $length, $viewportSize=512 ) {
- static $unitLength = array(
- 'px' => 1.0,
- 'pt' => 1.25,
- 'pc' => 15.0,
- 'mm' => 3.543307,
- 'cm' => 35.43307,
- 'in' => 90.0,
- 'em' => 16.0, // fake it?
- 'ex' => 12.0, // fake it?
- '' => 1.0, // "User units" pixels by default
- );
- $matches = array();
- if( preg_match( '/^\s*(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)\s*$/', $length, $matches ) ) {
- $length = floatval( $matches[1] );
- $unit = $matches[2];
- if( $unit == '%' ) {
- return $length * 0.01 * $viewportSize;
- } else {
- return $length * $unitLength[$unit];
- }
- } else {
- // Assume pixels
- return floatval( $length );
- }
-}
-
-class XmlSizeFilter {
- const DEFAULT_WIDTH = 512;
- const DEFAULT_HEIGHT = 512;
- var $first = true;
- var $width = self::DEFAULT_WIDTH;
- var $height = self::DEFAULT_HEIGHT;
- function filter( $name, $attribs ) {
- if( $this->first ) {
- $defaultWidth = self::DEFAULT_WIDTH;
- $defaultHeight = self::DEFAULT_HEIGHT;
- $aspect = 1.0;
- $width = null;
- $height = null;
-
- if( isset( $attribs['viewBox'] ) ) {
- // min-x min-y width height
- $viewBox = preg_split( '/\s+/', trim( $attribs['viewBox'] ) );
- if( count( $viewBox ) == 4 ) {
- $viewWidth = wfScaleSVGUnit( $viewBox[2] );
- $viewHeight = wfScaleSVGUnit( $viewBox[3] );
- if( $viewWidth > 0 && $viewHeight > 0 ) {
- $aspect = $viewWidth / $viewHeight;
- $defaultHeight = $defaultWidth / $aspect;
- }
- }
- }
- if( isset( $attribs['width'] ) ) {
- $width = wfScaleSVGUnit( $attribs['width'], $defaultWidth );
- }
- if( isset( $attribs['height'] ) ) {
- $height = wfScaleSVGUnit( $attribs['height'], $defaultHeight );
- }
-
- if( !isset( $width ) && !isset( $height ) ) {
- $width = $defaultWidth;
- $height = $width / $aspect;
- } elseif( isset( $width ) && !isset( $height ) ) {
- $height = $width / $aspect;
- } elseif( isset( $height ) && !isset( $width ) ) {
- $width = $height * $aspect;
- }
-
- if( $width > 0 && $height > 0 ) {
- $this->width = intval( round( $width ) );
- $this->height = intval( round( $height ) );
- }
-
- $this->first = false;
- }
- }
-}
-
-/**
- * Compatible with PHP getimagesize()
- * @todo support gzipped SVGZ
- * @todo check XML more carefully
- * @todo sensible defaults
- *
- * @param $filename String: full name of the file (passed to php fopen()).
- * @return array
- */
-function wfGetSVGsize( $filename ) {
- $filter = new XmlSizeFilter();
- $xml = new XmlTypeCheck( $filename, array( $filter, 'filter' ) );
- if( $xml->wellFormed ) {
- return array( $filter->width, $filter->height, 'SVG',
- "width=\"$filter->width\" height=\"$filter->height\"" );
- }
-
- return false;
-}
/**
* Determine if an image exists on the 'bad image list'.
diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php
index 5bff0ae3..f7020d63 100644
--- a/includes/ImageGallery.php
+++ b/includes/ImageGallery.php
@@ -32,20 +32,30 @@ class ImageGallery
*/
private $contextTitle = false;
- private $mPerRow = 4; // How many images wide should the gallery be?
- private $mWidths = 120, $mHeights = 120; // How wide/tall each thumbnail should be
-
private $mAttribs = array();
/**
+ * Fixed margins
+ */
+ const THUMB_PADDING = 30;
+ const GB_PADDING = 5;
+ //2px borders on each side + 2px implied padding on each side
+ const GB_BORDERS = 8;
+
+ /**
* Create a new image gallery object.
*/
function __construct( ) {
+ global $wgGalleryOptions;
$this->mImages = array();
- $this->mShowBytes = true;
+ $this->mShowBytes = $wgGalleryOptions['showBytes'];
$this->mShowFilename = true;
$this->mParser = false;
$this->mHideBadImages = false;
+ $this->mPerRow = $wgGalleryOptions['imagesPerRow'];
+ $this->mWidths = $wgGalleryOptions['imageWidth'];
+ $this->mHeights = $wgGalleryOptions['imageHeight'];
+ $this->mCaptionLength = $wgGalleryOptions['captionLength'];
}
/**
@@ -74,7 +84,7 @@ class ImageGallery
/**
* Set the caption (as HTML)
*
- * @param $caption Caption
+ * @param $caption String: Caption
*/
public function setCaptionHtml( $caption ) {
$this->mCaption = $caption;
@@ -83,10 +93,11 @@ class ImageGallery
/**
* Set how many images will be displayed per row.
*
- * @param int $num > 0; invalid numbers will be rejected
+ * @param $num Integer >= 0; If perrow=0 the gallery layout will adapt to screensize
+ * invalid numbers will be rejected
*/
public function setPerRow( $num ) {
- if ($num > 0) {
+ if ($num >= 0) {
$this->mPerRow = (int)$num;
}
}
@@ -94,7 +105,7 @@ class ImageGallery
/**
* Set how wide each image will be, in pixels.
*
- * @param int $num > 0; invalid numbers will be ignored
+ * @param $num Integer > 0; invalid numbers will be ignored
*/
public function setWidths( $num ) {
if ($num > 0) {
@@ -105,7 +116,7 @@ class ImageGallery
/**
* Set how high each image will be, in pixels.
*
- * @param int $num > 0; invalid numbers will be ignored
+ * @param $num Integer > 0; invalid numbers will be ignored
*/
public function setHeights( $num ) {
if ($num > 0) {
@@ -153,11 +164,11 @@ class ImageGallery
}
/**
- * Add an image at the beginning of the gallery.
- *
- * @param $title Title object of the image that is added to the gallery
- * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
- */
+ * Add an image at the beginning of the gallery.
+ *
+ * @param $title Title object of the image that is added to the gallery
+ * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
+ */
function insert( $title, $html='' ) {
if ( $title instanceof File ) {
// Old calling convention
@@ -181,7 +192,7 @@ class ImageGallery
* @param $f Boolean: set to false to disable.
*/
function setShowBytes( $f ) {
- $this->mShowBytes = ( $f == true);
+ $this->mShowBytes = (bool)$f;
}
/**
@@ -191,17 +202,17 @@ class ImageGallery
* @param $f Boolean: set to false to disable.
*/
function setShowFilename( $f ) {
- $this->mShowFilename = ( $f == true);
+ $this->mShowFilename = (bool)$f;
}
/**
* Set arbitrary attributes to go on the HTML gallery output element.
- * Should be suitable for a &lt;table&gt; element.
+ * Should be suitable for a <ul> element.
*
* Note -- if taking from user input, you should probably run through
* Sanitizer::validateAttributes() first.
*
- * @param array of HTML attribute pairs
+ * @param $attribs Array of HTML attribute pairs
*/
function setAttributes( $attribs ) {
$this->mAttribs = $attribs;
@@ -222,15 +233,20 @@ class ImageGallery
$sk = $this->getSkin();
+ if ( $this->mPerRow > 0 ) {
+ $maxwidth = $this->mPerRow * ( $this->mWidths + self::THUMB_PADDING + self::GB_PADDING + self::GB_BORDERS );
+ $oldStyle = isset( $this->mAttribs['style'] ) ? $this->mAttribs['style'] : "";
+ $this->mAttribs['style'] = "max-width: {$maxwidth}px;_width: {$maxwidth}px;" . $oldStyle;
+ }
+
$attribs = Sanitizer::mergeAttributes(
array(
- 'class' => 'gallery',
- 'cellspacing' => '0',
- 'cellpadding' => '0' ),
+ 'class' => 'gallery'),
$this->mAttribs );
- $s = Xml::openElement( 'table', $attribs );
- if( $this->mCaption )
- $s .= "\n\t<caption>{$this->mCaption}</caption>";
+ $s = Xml::openElement( 'ul', $attribs );
+ if ( $this->mCaption ) {
+ $s .= "\n\t<li class='gallerycaption'>{$this->mCaption}</li>";
+ }
$params = array( 'width' => $this->mWidths, 'height' => $this->mHeights );
$i = 0;
@@ -242,15 +258,19 @@ class ImageGallery
$time = $descQuery = false;
wfRunHooks( 'BeforeGalleryFindFile', array( &$this, &$nt, &$time, &$descQuery ) );
- $img = wfFindFile( $nt, array( 'time' => $time ) );
+ if ( $nt->getNamespace() == NS_FILE ) {
+ $img = wfFindFile( $nt, array( 'time' => $time ) );
+ } else {
+ $img = false;
+ }
- if( $nt->getNamespace() != NS_FILE || !$img ) {
+ if( !$img ) {
# We're dealing with a non-image, spit out the name and be done with it.
- $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
+ $thumbhtml = "\n\t\t\t".'<div style="height: '.(self::THUMB_PADDING + $this->mHeights).'px;">'
. htmlspecialchars( $nt->getText() ) . '</div>';
} elseif( $this->mHideBadImages && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() ) ) {
# The image is blacklisted, just show it as a text link.
- $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">' .
+ $thumbhtml = "\n\t\t\t".'<div style="height: '.(self::THUMB_PADDING + $this->mHeights).'px;">' .
$sk->link(
$nt,
htmlspecialchars( $nt->getText() ),
@@ -261,10 +281,14 @@ class ImageGallery
'</div>';
} elseif( !( $thumb = $img->transform( $params ) ) ) {
# Error generating thumbnail.
- $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
+ $thumbhtml = "\n\t\t\t".'<div style="height: '.(self::THUMB_PADDING + $this->mHeights).'px;">'
. htmlspecialchars( $img->getLastError() ) . '</div>';
} else {
- $vpad = floor( ( 1.25*$this->mHeights - $thumb->height ) /2 ) - 2;
+ //We get layout problems with the margin, if the image is smaller
+ //than the line-height, so we less margin in these cases.
+ $minThumbHeight = $thumb->height > 17 ? $thumb->height : 17;
+ $vpad = floor(( self::THUMB_PADDING + $this->mHeights - $minThumbHeight ) /2);
+
$imageParameters = array(
'desc-link' => true,
@@ -274,13 +298,14 @@ class ImageGallery
if ( $text == '' ) {
$imageParameters['alt'] = $nt->getText();
}
-
+
+ # Set both fixed width and min-height.
$thumbhtml = "\n\t\t\t".
- '<div class="thumb" style="padding: ' . $vpad . 'px 0; width: ' .($this->mWidths+30).'px;">'
+ '<div class="thumb" style="width: ' .($this->mWidths + self::THUMB_PADDING).'px;">'
# Auto-margin centering for block-level elements. Needed now that we have video
# handlers since they may emit block-level elements as opposed to simple <img> tags.
# ref http://css-discuss.incutio.com/?page=CenteringBlockElement
- . '<div style="margin-left: auto; margin-right: auto; width: ' .$this->mWidths.'px;">'
+ . '<div style="margin:'.$vpad.'px auto;">'
. $thumb->toHtml( $imageParameters ) . '</div></div>';
// Call parser transform hook
@@ -308,7 +333,7 @@ class ImageGallery
$textlink = $this->mShowFilename ?
$sk->link(
$nt,
- htmlspecialchars( $wgLang->truncate( $nt->getText(), 20 ) ),
+ htmlspecialchars( $wgLang->truncate( $nt->getText(), $this->mCaptionLength ) ),
array(),
array(),
array( 'known', 'noclasses' )
@@ -319,31 +344,25 @@ class ImageGallery
# in version 4.8.6 generated crackpot html in its absence, see:
# http://bugzilla.wikimedia.org/show_bug.cgi?id=1765 -Ævar
- if ( $i % $this->mPerRow == 0 ) {
- $s .= "\n\t<tr>";
- }
+ # Weird double wrapping in div needed due to FF2 bug
+ # Can be safely removed if FF2 falls completely out of existance
$s .=
- "\n\t\t" . '<td><div class="gallerybox" style="width: '.($this->mWidths+35).'px;">'
+ "\n\t\t" . '<li class="gallerybox" style="width: ' . ( $this->mWidths + self::THUMB_PADDING + self::GB_PADDING ) . 'px">'
+ . '<div style="width: ' . ( $this->mWidths + self::THUMB_PADDING + self::GB_PADDING ) . 'px">'
. $thumbhtml
. "\n\t\t\t" . '<div class="gallerytext">' . "\n"
. $textlink . $text . $nb
. "\n\t\t\t</div>"
- . "\n\t\t</div></td>";
- if ( $i % $this->mPerRow == $this->mPerRow - 1 ) {
- $s .= "\n\t</tr>";
- }
+ . "\n\t\t</div></li>";
++$i;
}
- if( $i % $this->mPerRow != 0 ) {
- $s .= "\n\t</tr>";
- }
- $s .= "\n</table>";
+ $s .= "\n</ul>";
return $s;
}
/**
- * @return int Number of images in the gallery
+ * @return Integer: number of images in the gallery
*/
public function count() {
return count( $this->mImages );
@@ -352,7 +371,7 @@ class ImageGallery
/**
* Set the contextual title
*
- * @param Title $title Contextual title
+ * @param $title Title: contextual title
*/
public function setContextTitle( $title ) {
$this->contextTitle = $title;
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
index f16acc33..c018e647 100644
--- a/includes/ImagePage.php
+++ b/includes/ImagePage.php
@@ -1,6 +1,6 @@
<?php
-if( !defined( 'MEDIAWIKI' ) )
+if ( !defined( 'MEDIAWIKI' ) )
die( 1 );
/**
@@ -22,7 +22,11 @@ class ImagePage extends Article {
$this->dupes = null;
$this->repo = null;
}
-
+
+ /**
+ * @param $file File:
+ * @return void
+ */
public function setFile( $file ) {
$this->displayImg = $file;
$this->img = $file;
@@ -30,20 +34,20 @@ class ImagePage extends Article {
}
protected function loadFile() {
- if( $this->fileLoaded ) {
+ if ( $this->fileLoaded ) {
return true;
}
$this->fileLoaded = true;
$this->displayImg = $this->img = false;
wfRunHooks( 'ImagePageFindFile', array( $this, &$this->img, &$this->displayImg ) );
- if( !$this->img ) {
+ if ( !$this->img ) {
$this->img = wfFindFile( $this->mTitle );
- if( !$this->img ) {
+ if ( !$this->img ) {
$this->img = wfLocalFile( $this->mTitle );
}
}
- if( !$this->displayImg ) {
+ if ( !$this->displayImg ) {
$this->displayImg = $this->img;
}
$this->repo = $this->img->getRepo();
@@ -61,13 +65,22 @@ class ImagePage extends Article {
public function view() {
global $wgOut, $wgShowEXIF, $wgRequest, $wgUser;
+
+ $diff = $wgRequest->getVal( 'diff' );
+ $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
+
+ if ( $this->mTitle->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) ) {
+ return parent::view();
+ }
+
$this->loadFile();
- if( $this->mTitle->getNamespace() == NS_FILE && $this->img->getRedirected() ) {
- if( $this->mTitle->getDBkey() == $this->img->getName() ) {
+ if ( $this->mTitle->getNamespace() == NS_FILE && $this->img->getRedirected() ) {
+ if ( $this->mTitle->getDBkey() == $this->img->getName() || isset( $diff ) ) {
// mTitle is the same as the redirect target so ask Article
// to perform the redirect for us.
- return Article::view();
+ $wgRequest->setVal( 'diffonly', 'true' );
+ return parent::view();
} else {
// mTitle is not the same as the redirect target so it is
// probably the redirect page itself. Fake the redirect symbol
@@ -79,15 +92,9 @@ class ImagePage extends Article {
}
}
- $diff = $wgRequest->getVal( 'diff' );
- $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
-
- if( $this->mTitle->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) )
- return Article::view();
-
$this->showRedirectedFromHeader();
- if( $wgShowEXIF && $this->displayImg->exists() ) {
+ if ( $wgShowEXIF && $this->displayImg->exists() ) {
// FIXME: bad interface, see note on MediaHandler::formatMetadata().
$formattedMetadata = $this->displayImg->formatMetadata();
$showmeta = $formattedMetadata !== false;
@@ -95,15 +102,15 @@ class ImagePage extends Article {
$showmeta = false;
}
- if( !$diff && $this->displayImg->exists() )
- $wgOut->addHTML( $this->showTOC($showmeta) );
+ if ( !$diff && $this->displayImg->exists() )
+ $wgOut->addHTML( $this->showTOC( $showmeta ) );
- if( !$diff )
+ if ( !$diff )
$this->openShowImage();
# No need to display noarticletext, we use our own message, output in openShowImage()
- if( $this->getID() ) {
- Article::view();
+ if ( $this->getID() ) {
+ parent::view();
} else {
# Just need to set the right headers
$wgOut->setArticleFlag( true );
@@ -112,9 +119,9 @@ class ImagePage extends Article {
}
# Show shared description, if needed
- if( $this->mExtraDescription ) {
+ if ( $this->mExtraDescription ) {
$fol = wfMsgNoTrans( 'shareddescriptionfollows' );
- if( $fol != '-' && !wfEmptyMsg( 'shareddescriptionfollows', $fol ) ) {
+ if ( $fol != '-' && !wfEmptyMsg( 'shareddescriptionfollows', $fol ) ) {
$wgOut->addWikiText( $fol );
}
$wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" );
@@ -123,7 +130,7 @@ class ImagePage extends Article {
$this->closeShowImage();
$this->imageHistory();
// TODO: Cleanup the following
-
+
$wgOut->addHTML( Xml::element( 'h2',
array( 'id' => 'filelinks' ),
wfMsg( 'imagelinks' ) ) . "\n" );
@@ -136,49 +143,49 @@ class ImagePage extends Article {
# Allow extensions to add something after the image links
$html = '';
wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) );
- if ( $html)
+ if ( $html )
$wgOut->addHTML( $html );
- if( $showmeta ) {
- global $wgStylePath, $wgStyleVersion;
- $expand = htmlspecialchars( Xml::escapeJsString( wfMsg( 'metadata-expand' ) ) );
- $collapse = htmlspecialchars( Xml::escapeJsString( wfMsg( 'metadata-collapse' ) ) );
+ if ( $showmeta ) {
$wgOut->addHTML( Xml::element( 'h2', array( 'id' => 'metadata' ), wfMsg( 'metadata' ) ) . "\n" );
$wgOut->addWikiText( $this->makeMetadataTable( $formattedMetadata ) );
- $wgOut->addScriptFile( 'metadata.js' );
- $wgOut->addHTML(
- "<script type=\"text/javascript\">attachMetadataToggle('mw_metadata', '$expand', '$collapse');</script>\n" );
+ $wgOut->addModules( array( 'mediawiki.legacy.metadata' ) );
+ }
+
+ $css = $this->repo->getDescriptionStylesheetUrl();
+ if ( $css ) {
+ $wgOut->addStyle( $css );
}
}
public function getRedirectTarget() {
$this->loadFile();
- if( $this->img->isLocal() ) {
+ if ( $this->img->isLocal() ) {
return parent::getRedirectTarget();
}
// Foreign image page
$from = $this->img->getRedirected();
$to = $this->img->getName();
- if( $from == $to ) {
- return null;
+ if ( $from == $to ) {
+ return null;
}
return $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
}
public function followRedirect() {
$this->loadFile();
- if( $this->img->isLocal() ) {
+ if ( $this->img->isLocal() ) {
return parent::followRedirect();
}
$from = $this->img->getRedirected();
$to = $this->img->getName();
- if( $from == $to ) {
- return false;
+ if ( $from == $to ) {
+ return false;
}
- return Title::makeTitle( NS_FILE, $to );
+ return Title::makeTitle( NS_FILE, $to );
}
public function isRedirect( $text = false ) {
$this->loadFile();
- if( $this->img->isLocal() )
+ if ( $this->img->isLocal() )
return parent::isRedirect( $text );
return (bool)$this->img->getRedirected();
@@ -201,21 +208,21 @@ class ImagePage extends Article {
public function getDuplicates() {
$this->loadFile();
- if( !is_null($this->dupes) ) {
+ if ( !is_null( $this->dupes ) ) {
return $this->dupes;
}
- if( !( $hash = $this->img->getSha1() ) ) {
+ if ( !( $hash = $this->img->getSha1() ) ) {
return $this->dupes = array();
}
$dupes = RepoGroup::singleton()->findBySha1( $hash );
// Remove duplicates with self and non matching file sizes
- $self = $this->img->getRepoName().':'.$this->img->getName();
+ $self = $this->img->getRepoName() . ':' . $this->img->getName();
$size = $this->img->getSize();
foreach ( $dupes as $index => $file ) {
- $key = $file->getRepoName().':'.$file->getName();
- if( $key == $self )
+ $key = $file->getRepoName() . ':' . $file->getName();
+ if ( $key == $self )
unset( $dupes[$index] );
- if( $file->getSize() != $size )
+ if ( $file->getSize() != $size )
unset( $dupes[$index] );
}
return $this->dupes = $dupes;
@@ -226,14 +233,14 @@ class ImagePage extends Article {
/**
* Create the TOC
*
- * @param bool $metadata Whether or not to show the metadata link
- * @return string
+ * @param $metadata Boolean: whether or not to show the metadata link
+ * @return String
*/
protected function showTOC( $metadata ) {
$r = array(
'<li><a href="#file">' . wfMsgHtml( 'file-anchor-link' ) . '</a></li>',
'<li><a href="#filehistory">' . wfMsgHtml( 'filehist' ) . '</a></li>',
- '<li><a href="#filelinks">' . wfMsgHtml( 'imagelinks' ) . '</a></li>',
+ '<li><a href="#filelinks">' . wfMsgHtml( 'imagelinks' ) . '</a></li>',
);
if ( $metadata ) {
$r[] = '<li><a href="#metadata">' . wfMsgHtml( 'metadata' ) . '</a></li>';
@@ -249,8 +256,8 @@ class ImagePage extends Article {
*
* FIXME: bad interface, see note on MediaHandler::formatMetadata().
*
- * @param array $exif The array containing the EXIF data
- * @return string
+ * @param $metadata Array: the array containing the EXIF data
+ * @return String
*/
protected function makeMetadataTable( $metadata ) {
$r = "<div class=\"mw-imagepage-section-metadata\">";
@@ -260,7 +267,7 @@ class ImagePage extends Article {
foreach ( $stuff as $v ) {
# FIXME, why is this using escapeId for a class?!
$class = Sanitizer::escapeId( $v['id'] );
- if( $type == 'collapsed' ) {
+ if ( $type == 'collapsed' ) {
$class .= ' collapsable';
}
$r .= "<tr class=\"$class\">\n";
@@ -280,10 +287,10 @@ class ImagePage extends Article {
*/
public function getContent() {
$this->loadFile();
- if( $this->img && !$this->img->isLocal() && 0 == $this->getID() ) {
+ if ( $this->img && !$this->img->isLocal() && 0 == $this->getID() ) {
return '';
}
- return Article::getContent();
+ return parent::getContent();
}
protected function openShowImage() {
@@ -292,15 +299,13 @@ class ImagePage extends Article {
$this->loadFile();
- $full_url = $this->displayImg->getURL();
- $linkAttribs = false;
- $sizeSel = intval( $wgUser->getOption( 'imagesize') );
- if( !isset( $wgImageLimits[$sizeSel] ) ) {
+ $sizeSel = intval( $wgUser->getOption( 'imagesize' ) );
+ if ( !isset( $wgImageLimits[$sizeSel] ) ) {
$sizeSel = User::getDefaultOption( 'imagesize' );
// The user offset might still be incorrect, specially if
// $wgImageLimits got changed (see bug #8858).
- if( !isset( $wgImageLimits[$sizeSel] ) ) {
+ if ( !isset( $wgImageLimits[$sizeSel] ) ) {
// Default to the first offset in $wgImageLimits
$sizeSel = 0;
}
@@ -311,41 +316,39 @@ class ImagePage extends Article {
$sk = $wgUser->getSkin();
$dirmark = $wgContLang->getDirMark();
- if( $this->displayImg->exists() ) {
+ if ( $this->displayImg->exists() ) {
# image
$page = $wgRequest->getIntOrNull( 'page' );
- if( is_null( $page ) ) {
+ if ( is_null( $page ) ) {
$params = array();
$page = 1;
} else {
$params = array( 'page' => $page );
}
- $width_orig = $this->displayImg->getWidth();
+ $width_orig = $this->displayImg->getWidth( $page );
$width = $width_orig;
- $height_orig = $this->displayImg->getHeight();
+ $height_orig = $this->displayImg->getHeight( $page );
$height = $height_orig;
- $mime = $this->displayImg->getMimeType();
- $showLink = false;
- $linkAttribs = array( 'href' => $full_url );
- $longDesc = $this->displayImg->getLongDesc();
+
+ $longDesc = wfMsg( 'parentheses', $this->displayImg->getLongDesc() );
wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$wgOut ) );
- if( $this->displayImg->allowInlineDisplay() ) {
+ if ( $this->displayImg->allowInlineDisplay() ) {
# image
# "Download high res version" link below the image
- #$msgsize = wfMsgHtml('file-info-size', $width_orig, $height_orig, $sk->formatSize( $this->displayImg->getSize() ), $mime );
+ # $msgsize = wfMsgHtml('file-info-size', $width_orig, $height_orig, $sk->formatSize( $this->displayImg->getSize() ), $mime );
# We'll show a thumbnail of this image
- if( $width > $maxWidth || $height > $maxHeight ) {
+ if ( $width > $maxWidth || $height > $maxHeight ) {
# Calculate the thumbnail size.
# First case, the limiting factor is the width, not the height.
- if( $width / $height >= $maxWidth / $maxHeight ) {
- $height = round( $height * $maxWidth / $width);
+ if ( $width / $height >= $maxWidth / $maxHeight ) {
+ $height = round( $height * $maxWidth / $width );
$width = $maxWidth;
# Note that $height <= $maxHeight now.
} else {
- $newwidth = floor( $width * $maxHeight / $height);
+ $newwidth = floor( $width * $maxHeight / $height );
$height = round( $height * $newwidth / $width );
$width = $newwidth;
# Note that $height <= $maxHeight now, but might not be identical
@@ -358,28 +361,24 @@ class ImagePage extends Article {
);
} else {
# Image is small enough to show full size on image page
- $msgbig = htmlspecialchars( $this->displayImg->getName() );
$msgsmall = wfMsgExt( 'file-nohires', array( 'parseinline' ) );
}
$params['width'] = $width;
$thumbnail = $this->displayImg->transform( $params );
- $anchorclose = "<br />";
- if( $this->displayImg->mustRender() ) {
- $showLink = true;
- } else {
- $anchorclose .=
- $msgsmall .
- '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . "$dirmark " . $longDesc;
+ $showLink = true;
+ $anchorclose = '';
+ if ( !$this->displayImg->mustRender() ) {
+ $anchorclose = "<br />" . $msgsmall;
}
$isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
- if( $isMulti ) {
+ if ( $isMulti ) {
$wgOut->addHTML( '<table class="multipageimage"><tr><td>' );
}
- if( $thumbnail ) {
+ if ( $thumbnail ) {
$options = array(
'alt' => $this->displayImg->getTitle()->getPrefixedText(),
'file-link' => true,
@@ -389,10 +388,10 @@ class ImagePage extends Article {
$anchorclose . "</div>\n" );
}
- if( $isMulti ) {
+ if ( $isMulti ) {
$count = $this->displayImg->pageCount();
- if( $page > 1 ) {
+ if ( $page > 1 ) {
$label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
$link = $sk->link(
$this->mTitle,
@@ -407,7 +406,7 @@ class ImagePage extends Article {
$thumb1 = '';
}
- if( $page < $count ) {
+ if ( $page < $count ) {
$label = wfMsg( 'imgmultipagenext' );
$link = $sk->link(
$this->mTitle,
@@ -430,9 +429,8 @@ class ImagePage extends Article {
'onchange' => 'document.pageselector.submit();',
);
- $option = array();
- for ( $i=1; $i <= $count; $i++ ) {
- $options[] = Xml::option( $wgLang->formatNum($i), $i, $i == $page );
+ for ( $i = 1; $i <= $count; $i++ ) {
+ $options[] = Xml::option( $wgLang->formatNum( $i ), $i, $i == $page );
}
$select = Xml::tags( 'select',
array( 'id' => 'pageselector', 'name' => 'page' ),
@@ -441,7 +439,7 @@ class ImagePage extends Article {
$wgOut->addHTML(
'</td><td><div class="multipageimagenavbox">' .
Xml::openElement( 'form', $formParams ) .
- Xml::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
wfMsgExt( 'imgmultigoto', array( 'parseinline', 'replaceafter' ), $select ) .
Xml::submitButton( wfMsg( 'imgmultigo' ) ) .
Xml::closeElement( 'form' ) .
@@ -449,9 +447,9 @@ class ImagePage extends Article {
);
}
} else {
- #if direct link is allowed but it's not a renderable image, show an icon.
- if( $this->displayImg->isSafeFile() ) {
- $icon= $this->displayImg->iconThumb();
+ # if direct link is allowed but it's not a renderable image, show an icon.
+ if ( $this->displayImg->isSafeFile() ) {
+ $icon = $this->displayImg->iconThumb();
$wgOut->addHTML( '<div class="fullImageLink" id="file">' .
$icon->toHtml( array( 'file-link' => true ) ) .
@@ -462,32 +460,31 @@ class ImagePage extends Article {
}
- if($showLink) {
+ if ( $showLink ) {
$filename = wfEscapeWikiText( $this->displayImg->getName() );
- $medialink = "[[Media:$filename|$filename]]";
+ $linktext = $filename;
+ if ( isset( $msgbig ) ) {
+ $linktext = wfEscapeWikiText( $msgbig );
+ }
+ $medialink = "[[Media:$filename|$linktext]]";
- if( !$this->displayImg->isSafeFile() ) {
+ if ( !$this->displayImg->isSafeFile() ) {
$warning = wfMsgNoTrans( 'mediawarning' );
$wgOut->addWikiText( <<<EOT
-<div class="fullMedia">
-<span class="dangerousLink">{$medialink}</span>$dirmark
-<span class="fileInfo">$longDesc</span>
-</div>
+<div class="fullMedia"><span class="dangerousLink">{$medialink}</span>$dirmark <span class="fileInfo">$longDesc</span></div>
<div class="mediaWarning">$warning</div>
EOT
);
} else {
$wgOut->addWikiText( <<<EOT
-<div class="fullMedia">
-{$medialink}{$dirmark}
-<span class="fileInfo">$longDesc</span>
+<div class="fullMedia">{$medialink}{$dirmark} <span class="fileInfo">$longDesc</span>
</div>
EOT
);
}
}
- if( !$this->displayImg->isLocal() ) {
+ if ( !$this->displayImg->isLocal() ) {
$this->printSharedImageText();
}
} else {
@@ -506,6 +503,11 @@ EOT
}
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
+ if ( !$this->getID() ) {
+ // If there is no image, no shared image, and no description page,
+ // output a 404, to be consistent with articles.
+ $wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
+ }
}
}
@@ -520,11 +522,15 @@ EOT
$descUrl = $this->img->getDescriptionUrl();
$descText = $this->img->getDescriptionText();
+ /* Add canonical to head if there is no local page for this shared file */
+ if( $descUrl && $this->getID() == 0 ) {
+ $wgOut->addLink( array( 'rel' => 'canonical', 'href' => $descUrl ) );
+ }
+
$wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
$repo = $this->img->getRepo()->getDisplayName();
- $msg = '';
- if( $descUrl && $descText && wfMsgNoTrans( 'sharedupload-desc-here' ) !== '-' ) {
+ if ( $descUrl && $descText && wfMsgNoTrans( 'sharedupload-desc-here' ) !== '-' ) {
$wgOut->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) );
} elseif ( $descUrl && wfMsgNoTrans( 'sharedupload-desc-there' ) !== '-' ) {
$wgOut->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) );
@@ -532,7 +538,7 @@ EOT
$wgOut->wrapWikiMsg( $wrap, array( 'sharedupload', $repo ), ''/*BACKCOMPAT*/ );
}
- if( $descText ) {
+ if ( $descText ) {
$this->mExtraDescription = $descText;
}
}
@@ -553,10 +559,10 @@ EOT
protected function uploadLinksBox() {
global $wgUser, $wgOut, $wgEnableUploads, $wgUseExternalEditor;
- if( !$wgEnableUploads ) { return; }
+ if ( !$wgEnableUploads ) { return; }
$this->loadFile();
- if( !$this->img->isLocal() )
+ if ( !$this->img->isLocal() )
return;
$sk = $wgUser->getSkin();
@@ -564,7 +570,7 @@ EOT
$wgOut->addHTML( "<br /><ul>\n" );
# "Upload a new version of this file" link
- if( UploadBase::userCanReUpload($wgUser,$this->img->name) ) {
+ if ( UploadBase::userCanReUpload( $wgUser, $this->img->name ) ) {
$ulink = $sk->makeExternalLink( $this->getUploadUrl(), wfMsg( 'uploadnewversion-linktext' ) );
$wgOut->addHTML( "<li id=\"mw-imagepage-reupload-link\"><div class=\"plainlinks\">{$ulink}</div></li>\n" );
}
@@ -588,7 +594,7 @@ EOT
$wgOut->addHTML( "</ul>\n" );
}
- protected function closeShowImage() {} # For overloading
+ protected function closeShowImage() { } # For overloading
/**
* If the page we've just displayed is in the "Image" namespace,
@@ -606,7 +612,7 @@ EOT
# Exist check because we don't want to show this on pages where an image
# doesn't exist along with the noimage message, that would suck. -ævar
- if( $this->img->exists() ) {
+ if ( $this->img->exists() ) {
$this->uploadLinksBox();
}
}
@@ -623,18 +629,16 @@ EOT
array( 'page_namespace', 'page_title' ),
array( 'il_to' => $this->mTitle->getDBkey(), 'il_from = page_id' ),
__METHOD__,
- array( 'LIMIT' => $limit + 1)
+ array( 'LIMIT' => $limit + 1 )
);
$count = $dbr->numRows( $res );
- if( $count == 0 ) {
- $wgOut->addHTML( "<div id='mw-imagepage-nolinkstoimage'>\n" );
- $wgOut->addWikiMsg( 'nolinkstoimage' );
- $wgOut->addHTML( "</div>\n" );
+ if ( $count == 0 ) {
+ $wgOut->wrapWikiMsg( Html::rawElement( 'div', array ( 'id' => 'mw-imagepage-nolinkstoimage' ), "\n$1\n" ), 'nolinkstoimage' );
return;
}
$wgOut->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
- if( $count <= $limit - 1 ) {
+ if ( $count <= $limit - 1 ) {
$wgOut->addWikiMsg( 'linkstoimage', $count );
} else {
// More links than the limit. Add a link to [[Special:Whatlinkshere]]
@@ -644,37 +648,47 @@ EOT
);
}
- $wgOut->addHTML( "<ul class='mw-imagepage-linkstoimage'>\n" );
+ $wgOut->addHTML( Html::openElement( 'ul', array( 'class' => 'mw-imagepage-linkstoimage' ) ) . "\n" );
$sk = $wgUser->getSkin();
$count = 0;
- while ( $s = $res->fetchObject() ) {
+ $elements = array();
+ foreach ( $res as $s ) {
$count++;
- if( $count <= $limit ) {
+ if ( $count <= $limit ) {
// We have not yet reached the extra one that tells us there is more to fetch
- $link = $sk->link(
- Title::makeTitle( $s->page_namespace, $s->page_title ),
- null,
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
- $wgOut->addHTML( "<li>{$link}</li>\n" );
+ $elements[] = $s;
}
}
- $wgOut->addHTML( "</ul>\n" );
+
+ // Sort the list by namespace:title
+ usort ( $elements, array( $this, 'compare' ) );
+
+ // Create links for every element
+ foreach( $elements as $element ) {
+ $link = $sk->linkKnown( Title::makeTitle( $element->page_namespace, $element->page_title ) );
+ $wgOut->addHTML( Html::rawElement(
+ 'li',
+ array( 'id' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
+ $link
+ ) . "\n"
+ );
+
+ };
+ $wgOut->addHTML( Html::closeElement( 'ul' ) . "\n" );
$res->free();
// Add a links to [[Special:Whatlinkshere]]
- if( $count > $limit )
+ if ( $count > $limit ) {
$wgOut->addWikiMsg( 'morelinkstoimage', $this->mTitle->getPrefixedDBkey() );
- $wgOut->addHTML( "</div>\n" );
+ }
+ $wgOut->addHTML( Html::closeElement( 'div' ) . "\n" );
}
protected function imageRedirects() {
global $wgUser, $wgOut, $wgLang;
$redirects = $this->getTitle()->getRedirectsHere( NS_FILE );
- if( count( $redirects ) == 0 ) return;
+ if ( count( $redirects ) == 0 ) return;
$wgOut->addHTML( "<div id='mw-imagepage-section-redirectstofile'>\n" );
$wgOut->addWikiMsg( 'redirectstofile',
@@ -703,7 +717,7 @@ EOT
$this->loadFile();
$dupes = $this->getDuplicates();
- if( count( $dupes ) == 0 ) return;
+ if ( count( $dupes ) == 0 ) return;
$wgOut->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
$wgOut->addWikiMsg( 'duplicatesoffile',
@@ -714,7 +728,7 @@ EOT
$sk = $wgUser->getSkin();
foreach ( $dupes as $file ) {
$fromSrc = '';
- if( $file->isLocal() ) {
+ if ( $file->isLocal() ) {
$link = $sk->link(
$file->getTitle(),
null,
@@ -737,16 +751,16 @@ EOT
*/
public function delete() {
global $wgUploadMaintenance;
- if( $wgUploadMaintenance && $this->mTitle && $this->mTitle->getNamespace() == NS_FILE ) {
+ if ( $wgUploadMaintenance && $this->mTitle && $this->mTitle->getNamespace() == NS_FILE ) {
global $wgOut;
- $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n", array( 'filedelete-maintenance' ) );
+ $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'filedelete-maintenance' ) );
return;
}
$this->loadFile();
- if( !$this->img->exists() || !$this->img->isLocal() || $this->img->getRedirected() ) {
+ if ( !$this->img->exists() || !$this->img->isLocal() || $this->img->getRedirected() ) {
// Standard article deletion
- Article::delete();
+ parent::delete();
return;
}
$deleter = new FileDeleteForm( $this->img );
@@ -767,7 +781,7 @@ EOT
*/
public function doPurge() {
$this->loadFile();
- if( $this->img->exists() ) {
+ if ( $this->img->exists() ) {
wfDebug( "ImagePage::doPurge purging " . $this->img->getName() . "\n" );
$update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
$update->doUpdate();
@@ -794,6 +808,22 @@ EOT
$wgOut->addWikiText( $description );
}
+
+ /**
+ * Callback for usort() to do link sorts by (namespace, title)
+ * Function copied from Title::compare()
+ *
+ * @param $a object page to compare with
+ * @param $b object page to compare with
+ * @return Integer: result of string comparison, or namespace comparison
+ */
+ protected function compare( $a, $b ) {
+ if ( $a->page_namespace == $b->page_namespace ) {
+ return strcmp( $a->page_title, $b->page_title );
+ } else {
+ return $a->page_namespace - $b->page_namespace;
+ }
+ }
}
/**
@@ -836,7 +866,7 @@ class ImageHistoryList {
. $navLinks . "\n"
. Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n"
. '<tr><td></td>'
- . ( $this->current->isLocal() && ($wgUser->isAllowed('delete') || $wgUser->isAllowed('deletedhistory') ) ? '<td></td>' : '' )
+ . ( $this->current->isLocal() && ( $wgUser->isAllowed( 'delete' ) || $wgUser->isAllowed( 'deletedhistory' ) ) ? '<td></td>' : '' )
. '<th>' . wfMsgHtml( 'filehist-datetime' ) . '</th>'
. ( $this->showThumb ? '<th>' . wfMsgHtml( 'filehist-thumb' ) . '</th>' : '' )
. '<th>' . wfMsgHtml( 'filehist-dimensions' ) . '</th>'
@@ -850,24 +880,24 @@ class ImageHistoryList {
}
public function imageHistoryLine( $iscur, $file ) {
- global $wgUser, $wgLang, $wgContLang, $wgTitle;
+ global $wgUser, $wgLang;
- $timestamp = wfTimestamp(TS_MW, $file->getTimestamp());
+ $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
$img = $iscur ? $file->getName() : $file->getArchiveName();
- $user = $file->getUser('id');
- $usertext = $file->getUser('text');
+ $user = $file->getUser( 'id' );
+ $usertext = $file->getUser( 'text' );
$description = $file->getDescription();
$local = $this->current->isLocal();
- $row = $css = $selected = '';
+ $row = $selected = '';
// Deletion link
- if( $local && ($wgUser->isAllowed('delete') || $wgUser->isAllowed('deletedhistory') ) ) {
+ if ( $local && ( $wgUser->isAllowed( 'delete' ) || $wgUser->isAllowed( 'deletedhistory' ) ) ) {
$row .= '<td>';
# Link to remove from history
- if( $wgUser->isAllowed( 'delete' ) ) {
+ if ( $wgUser->isAllowed( 'delete' ) ) {
$q = array( 'action' => 'delete' );
- if( !$iscur )
+ if ( !$iscur )
$q['oldimage'] = $img;
$row .= $this->skin->link(
$this->title,
@@ -877,22 +907,22 @@ class ImageHistoryList {
}
# Link to hide content. Don't show useless link to people who cannot hide revisions.
$canHide = $wgUser->isAllowed( 'deleterevision' );
- if( $canHide || ($wgUser->isAllowed('deletedhistory') && $file->getVisibility()) ) {
- if( $wgUser->isAllowed('delete') ) {
+ if ( $canHide || ( $wgUser->isAllowed( 'deletedhistory' ) && $file->getVisibility() ) ) {
+ if ( $wgUser->isAllowed( 'delete' ) ) {
$row .= '<br />';
}
// If file is top revision or locked from this user, don't link
- if( $iscur || !$file->userCan(File::DELETED_RESTRICTED) ) {
+ if ( $iscur || !$file->userCan( File::DELETED_RESTRICTED ) ) {
$del = $this->skin->revDeleteLinkDisabled( $canHide );
} else {
list( $ts, $name ) = explode( '!', $img, 2 );
$query = array(
'type' => 'oldimage',
- 'target' => $wgTitle->getPrefixedText(),
+ 'target' => $this->title->getPrefixedText(),
'ids' => $ts,
);
$del = $this->skin->revDeleteLink( $query,
- $file->isDeleted(File::DELETED_RESTRICTED), $canHide );
+ $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
}
$row .= $del;
}
@@ -901,11 +931,11 @@ class ImageHistoryList {
// Reversion link/current indicator
$row .= '<td>';
- if( $iscur ) {
+ if ( $iscur ) {
$row .= wfMsgHtml( 'filehist-current' );
- } elseif( $local && $wgUser->isLoggedIn() && $this->title->userCan( 'edit' ) ) {
- if( $file->isDeleted(File::DELETED_FILE) ) {
- $row .= wfMsgHtml('filehist-revert');
+ } elseif ( $local && $wgUser->isLoggedIn() && $this->title->userCan( 'edit' ) ) {
+ if ( $file->isDeleted( File::DELETED_FILE ) ) {
+ $row .= wfMsgHtml( 'filehist-revert' );
} else {
$row .= $this->skin->link(
$this->title,
@@ -923,14 +953,14 @@ class ImageHistoryList {
$row .= '</td>';
// Date/time and image link
- if( $file->getTimestamp() === $this->img->getTimestamp() ) {
+ if ( $file->getTimestamp() === $this->img->getTimestamp() ) {
$selected = "class='filehistory-selected'";
}
$row .= "<td $selected style='white-space: nowrap;'>";
- if( !$file->userCan(File::DELETED_FILE) ) {
+ if ( !$file->userCan( File::DELETED_FILE ) ) {
# Don't link to unviewable files
$row .= '<span class="history-deleted">' . $wgLang->timeAndDate( $timestamp, true ) . '</span>';
- } elseif( $file->isDeleted(File::DELETED_FILE) ) {
+ } elseif ( $file->isDeleted( File::DELETED_FILE ) ) {
$this->preventClickjacking();
$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
# Make a link to review the image
@@ -939,13 +969,13 @@ class ImageHistoryList {
$wgLang->timeAndDate( $timestamp, true ),
array(),
array(
- 'target' => $wgTitle->getPrefixedText(),
+ 'target' => $this->title->getPrefixedText(),
'file' => $img,
'token' => $wgUser->editToken( $img )
),
array( 'known', 'noclasses' )
);
- $row .= '<span class="history-deleted">'.$url.'</span>';
+ $row .= '<span class="history-deleted">' . $url . '</span>';
} else {
$url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
$row .= Xml::element( 'a', array( 'href' => $url ), $wgLang->timeAndDate( $timestamp, true ) );
@@ -965,27 +995,28 @@ class ImageHistoryList {
// Uploading user
$row .= '<td>';
- if( $local ) {
- // Hide deleted usernames
- if( $file->isDeleted(File::DELETED_USER) ) {
- $row .= '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+ // Hide deleted usernames
+ if ( $file->isDeleted( File::DELETED_USER ) ) {
+ $row .= '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+ } else {
+ if ( $local ) {
+ $row .= $this->skin->userLink( $user, $usertext ) . ' <span style="white-space: nowrap;">' .
+ $this->skin->userToolLinks( $user, $usertext ) . '</span>';
} else {
- $row .= $this->skin->userLink( $user, $usertext ) . " <span style='white-space: nowrap;'>" .
- $this->skin->userToolLinks( $user, $usertext ) . "</span>";
+ $row .= htmlspecialchars( $usertext );
}
- } else {
- $row .= htmlspecialchars( $usertext );
}
$row .= '</td><td>';
// Don't show deleted descriptions
- if( $file->isDeleted(File::DELETED_COMMENT) ) {
- $row .= '<span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span>';
+ if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
+ $row .= '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
} else {
$row .= $this->skin->commentBlock( $description, $this->title );
}
$row .= '</td>';
+ $rowClass = null;
wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) );
$classAttr = $rowClass ? " class='$rowClass'" : "";
@@ -995,12 +1026,12 @@ class ImageHistoryList {
protected function getThumbForLine( $file ) {
global $wgLang;
- if( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE ) && !$file->isDeleted( File::DELETED_FILE ) ) {
+ if ( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE ) && !$file->isDeleted( File::DELETED_FILE ) ) {
$params = array(
'width' => '120',
'height' => '120',
);
- $timestamp = wfTimestamp(TS_MW, $file->getTimestamp());
+ $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
$thumbnail = $file->transform( $params );
$options = array(
@@ -1034,7 +1065,7 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
function __construct( $imagePage ) {
parent::__construct();
$this->mImagePage = $imagePage;
- $this->mTitle = clone( $imagePage->getTitle() );
+ $this->mTitle = clone ( $imagePage->getTitle() );
$this->mTitle->setFragment( '#filehistory' );
$this->mImg = null;
$this->mHist = array();
@@ -1060,17 +1091,17 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
function getBody() {
$s = '';
$this->doQuery();
- if( count($this->mHist) ) {
+ if ( count( $this->mHist ) ) {
$list = new ImageHistoryList( $this->mImagePage );
# Generate prev/next links
$navLink = $this->getNavigationBar();
- $s = $list->beginImageHistoryList($navLink);
+ $s = $list->beginImageHistoryList( $navLink );
// Skip rows there just for paging links
- for( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
+ for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
$file = $this->mHist[$i];
$s .= $list->imageHistoryLine( !$file->isOld(), $file );
}
- $s .= $list->endImageHistoryList($navLink);
+ $s .= $list->endImageHistoryList( $navLink );
if ( $list->getPreventClickjacking() ) {
$this->preventClickjacking();
@@ -1080,52 +1111,52 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
}
function doQuery() {
- if( $this->mQueryDone ) return;
+ if ( $this->mQueryDone ) return;
$this->mImg = $this->mImagePage->getFile(); // ensure loading
- if( !$this->mImg->exists() ) {
+ if ( !$this->mImg->exists() ) {
return;
}
$queryLimit = $this->mLimit + 1; // limit plus extra row
- if( $this->mIsBackwards ) {
+ if ( $this->mIsBackwards ) {
// Fetch the file history
- $this->mHist = $this->mImg->getHistory($queryLimit,null,$this->mOffset,false);
+ $this->mHist = $this->mImg->getHistory( $queryLimit, null, $this->mOffset, false );
// The current rev may not meet the offset/limit
- $numRows = count($this->mHist);
- if( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) {
- $this->mHist = array_merge( array($this->mImg), $this->mHist );
+ $numRows = count( $this->mHist );
+ if ( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) {
+ $this->mHist = array_merge( array( $this->mImg ), $this->mHist );
}
} else {
// The current rev may not meet the offset
- if( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) {
+ if ( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) {
$this->mHist[] = $this->mImg;
}
// Old image versions (fetch extra row for nav links)
- $oiLimit = count($this->mHist) ? $this->mLimit : $this->mLimit+1;
+ $oiLimit = count( $this->mHist ) ? $this->mLimit : $this->mLimit + 1;
// Fetch the file history
$this->mHist = array_merge( $this->mHist,
- $this->mImg->getHistory($oiLimit,$this->mOffset,null,false) );
+ $this->mImg->getHistory( $oiLimit, $this->mOffset, null, false ) );
}
- $numRows = count($this->mHist); // Total number of query results
- if( $numRows ) {
+ $numRows = count( $this->mHist ); // Total number of query results
+ if ( $numRows ) {
# Index value of top item in the list
$firstIndex = $this->mIsBackwards ?
- $this->mHist[$numRows-1]->getTimestamp() : $this->mHist[0]->getTimestamp();
+ $this->mHist[$numRows - 1]->getTimestamp() : $this->mHist[0]->getTimestamp();
# Discard the extra result row if there is one
- if( $numRows > $this->mLimit && $numRows > 1 ) {
- if( $this->mIsBackwards ) {
+ if ( $numRows > $this->mLimit && $numRows > 1 ) {
+ if ( $this->mIsBackwards ) {
# Index value of item past the index
$this->mPastTheEndIndex = $this->mHist[0]->getTimestamp();
# Index value of bottom item in the list
$lastIndex = $this->mHist[1]->getTimestamp();
# Display range
- $this->mRange = array( 1, $numRows-1 );
+ $this->mRange = array( 1, $numRows - 1 );
} else {
# Index value of item past the index
- $this->mPastTheEndIndex = $this->mHist[$numRows-1]->getTimestamp();
+ $this->mPastTheEndIndex = $this->mHist[$numRows - 1]->getTimestamp();
# Index value of bottom item in the list
- $lastIndex = $this->mHist[$numRows-2]->getTimestamp();
+ $lastIndex = $this->mHist[$numRows - 2]->getTimestamp();
# Display range
- $this->mRange = array( 0, $numRows-2 );
+ $this->mRange = array( 0, $numRows - 2 );
}
} else {
# Setting indexes to an empty string means that they will be
@@ -1135,16 +1166,16 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
$this->mPastTheEndIndex = '';
# Index value of bottom item in the list
$lastIndex = $this->mIsBackwards ?
- $this->mHist[0]->getTimestamp() : $this->mHist[$numRows-1]->getTimestamp();
+ $this->mHist[0]->getTimestamp() : $this->mHist[$numRows - 1]->getTimestamp();
# Display range
- $this->mRange = array( 0, $numRows-1 );
+ $this->mRange = array( 0, $numRows - 1 );
}
} else {
$firstIndex = '';
$lastIndex = '';
$this->mPastTheEndIndex = '';
}
- if( $this->mIsBackwards ) {
+ if ( $this->mIsBackwards ) {
$this->mIsFirst = ( $numRows < $queryLimit );
$this->mIsLast = ( $this->mOffset == '' );
$this->mLastShown = $firstIndex;
diff --git a/includes/Import.php b/includes/Import.php
index 45908a66..c76a6834 100644
--- a/includes/Import.php
+++ b/includes/Import.php
@@ -1,7 +1,8 @@
<?php
/**
* MediaWiki page data importer
- * Copyright (C) 2003,2005 Brion Vibber <brion@pobox.com>
+ *
+ * Copyright © 2003,2005 Brion Vibber <brion@pobox.com>
* http://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
@@ -24,7 +25,756 @@
*/
/**
+ * XML file reader for the page data importer
*
+ * implements Special:Import
+ * @ingroup SpecialPage
+ */
+class WikiImporter {
+ private $reader = null;
+ private $mLogItemCallback, $mUploadCallback, $mRevisionCallback, $mPageCallback;
+ private $mSiteInfoCallback, $mTargetNamespace, $mPageOutCallback;
+ private $mDebug;
+
+ /**
+ * Creates an ImportXMLReader drawing from the source provided
+ */
+ function __construct( $source ) {
+ $this->reader = new XMLReader();
+
+ stream_wrapper_register( 'uploadsource', 'UploadSourceAdapter' );
+ $id = UploadSourceAdapter::registerSource( $source );
+ $this->reader->open( "uploadsource://$id" );
+
+ // Default callbacks
+ $this->setRevisionCallback( array( $this, "importRevision" ) );
+ $this->setUploadCallback( array( $this, 'importUpload' ) );
+ $this->setLogItemCallback( array( $this, 'importLogItem' ) );
+ $this->setPageOutCallback( array( $this, 'finishImportPage' ) );
+ }
+
+ private function throwXmlError( $err ) {
+ $this->debug( "FAILURE: $err" );
+ wfDebug( "WikiImporter XML error: $err\n" );
+ }
+
+ private function debug( $data ) {
+ if( $this->mDebug ) {
+ wfDebug( "IMPORT: $data\n" );
+ }
+ }
+
+ private function warn( $data ) {
+ wfDebug( "IMPORT: $data\n" );
+ }
+
+ private function notice( $data ) {
+ global $wgCommandLineMode;
+ if( $wgCommandLineMode ) {
+ print "$data\n";
+ } else {
+ global $wgOut;
+ $wgOut->addHTML( "<li>" . htmlspecialchars( $data ) . "</li>\n" );
+ }
+ }
+
+ /**
+ * Set debug mode...
+ */
+ function setDebug( $debug ) {
+ $this->mDebug = $debug;
+ }
+
+ /**
+ * Sets the action to perform as each new page in the stream is reached.
+ * @param $callback callback
+ * @return callback
+ */
+ public function setPageCallback( $callback ) {
+ $previous = $this->mPageCallback;
+ $this->mPageCallback = $callback;
+ return $previous;
+ }
+
+ /**
+ * Sets the action to perform as each page in the stream is completed.
+ * Callback accepts the page title (as a Title object), a second object
+ * with the original title form (in case it's been overridden into a
+ * local namespace), and a count of revisions.
+ *
+ * @param $callback callback
+ * @return callback
+ */
+ public function setPageOutCallback( $callback ) {
+ $previous = $this->mPageOutCallback;
+ $this->mPageOutCallback = $callback;
+ return $previous;
+ }
+
+ /**
+ * Sets the action to perform as each page revision is reached.
+ * @param $callback callback
+ * @return callback
+ */
+ public function setRevisionCallback( $callback ) {
+ $previous = $this->mRevisionCallback;
+ $this->mRevisionCallback = $callback;
+ return $previous;
+ }
+
+ /**
+ * Sets the action to perform as each file upload version is reached.
+ * @param $callback callback
+ * @return callback
+ */
+ public function setUploadCallback( $callback ) {
+ $previous = $this->mUploadCallback;
+ $this->mUploadCallback = $callback;
+ return $previous;
+ }
+
+ /**
+ * Sets the action to perform as each log item reached.
+ * @param $callback callback
+ * @return callback
+ */
+ public function setLogItemCallback( $callback ) {
+ $previous = $this->mLogItemCallback;
+ $this->mLogItemCallback = $callback;
+ return $previous;
+ }
+
+ /**
+ * Sets the action to perform when site info is encountered
+ * @param $callback callback
+ * @return callback
+ */
+ public function setSiteInfoCallback( $callback ) {
+ $previous = $this->mSiteInfoCallback;
+ $this->mSiteInfoCallback = $callback;
+ return $previous;
+ }
+
+ /**
+ * Set a target namespace to override the defaults
+ */
+ public function setTargetNamespace( $namespace ) {
+ if( is_null( $namespace ) ) {
+ // Don't override namespaces
+ $this->mTargetNamespace = null;
+ } elseif( $namespace >= 0 ) {
+ // FIXME: Check for validity
+ $this->mTargetNamespace = intval( $namespace );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Default per-revision callback, performs the import.
+ * @param $revision WikiRevision
+ */
+ public function importRevision( $revision ) {
+ $dbw = wfGetDB( DB_MASTER );
+ return $dbw->deadlockLoop( array( $revision, 'importOldRevision' ) );
+ }
+
+ /**
+ * Default per-revision callback, performs the import.
+ * @param $rev WikiRevision
+ */
+ public function importLogItem( $rev ) {
+ $dbw = wfGetDB( DB_MASTER );
+ return $dbw->deadlockLoop( array( $rev, 'importLogItem' ) );
+ }
+
+ /**
+ * Dummy for now...
+ */
+ public function importUpload( $revision ) {
+ //$dbw = wfGetDB( DB_MASTER );
+ //return $dbw->deadlockLoop( array( $revision, 'importUpload' ) );
+ return false;
+ }
+
+ /**
+ * Mostly for hook use
+ */
+ public function finishImportPage( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) {
+ $args = func_get_args();
+ return wfRunHooks( 'AfterImportPage', $args );
+ }
+
+ /**
+ * Alternate per-revision callback, for debugging.
+ * @param $revision WikiRevision
+ */
+ public function debugRevisionHandler( &$revision ) {
+ $this->debug( "Got revision:" );
+ if( is_object( $revision->title ) ) {
+ $this->debug( "-- Title: " . $revision->title->getPrefixedText() );
+ } else {
+ $this->debug( "-- Title: <invalid>" );
+ }
+ $this->debug( "-- User: " . $revision->user_text );
+ $this->debug( "-- Timestamp: " . $revision->timestamp );
+ $this->debug( "-- Comment: " . $revision->comment );
+ $this->debug( "-- Text: " . $revision->text );
+ }
+
+ /**
+ * Notify the callback function when a new <page> is reached.
+ * @param $title Title
+ */
+ function pageCallback( $title ) {
+ if( isset( $this->mPageCallback ) ) {
+ call_user_func( $this->mPageCallback, $title );
+ }
+ }
+
+ /**
+ * Notify the callback function when a </page> is closed.
+ * @param $title Title
+ * @param $origTitle Title
+ * @param $revCount Integer
+ * @param $sucCount Int: number of revisions for which callback returned true
+ * @param $pageInfo Array: associative array of page information
+ */
+ private function pageOutCallback( $title, $origTitle, $revCount, $sucCount, $pageInfo ) {
+ if( isset( $this->mPageOutCallback ) ) {
+ $args = func_get_args();
+ call_user_func_array( $this->mPageOutCallback, $args );
+ }
+ }
+
+ /**
+ * Notify the callback function of a revision
+ * @param $revision A WikiRevision object
+ */
+ private function revisionCallback( $revision ) {
+ if ( isset( $this->mRevisionCallback ) ) {
+ return call_user_func_array( $this->mRevisionCallback,
+ array( $revision, $this ) );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Notify the callback function of a new log item
+ * @param $revision A WikiRevision object
+ */
+ private function logItemCallback( $revision ) {
+ if ( isset( $this->mLogItemCallback ) ) {
+ return call_user_func_array( $this->mLogItemCallback,
+ array( $revision, $this ) );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Shouldn't something like this be built-in to XMLReader?
+ * Fetches text contents of the current element, assuming
+ * no sub-elements or such scary things.
+ * @return string
+ * @access private
+ */
+ private function nodeContents() {
+ if( $this->reader->isEmptyElement ) {
+ return "";
+ }
+ $buffer = "";
+ while( $this->reader->read() ) {
+ switch( $this->reader->nodeType ) {
+ case XmlReader::TEXT:
+ case XmlReader::SIGNIFICANT_WHITESPACE:
+ $buffer .= $this->reader->value;
+ break;
+ case XmlReader::END_ELEMENT:
+ return $buffer;
+ }
+ }
+
+ $this->reader->close();
+ return '';
+ }
+
+ # --------------
+
+ /** Left in for debugging */
+ private function dumpElement() {
+ static $lookup = null;
+ if (!$lookup) {
+ $xmlReaderConstants = array(
+ "NONE",
+ "ELEMENT",
+ "ATTRIBUTE",
+ "TEXT",
+ "CDATA",
+ "ENTITY_REF",
+ "ENTITY",
+ "PI",
+ "COMMENT",
+ "DOC",
+ "DOC_TYPE",
+ "DOC_FRAGMENT",
+ "NOTATION",
+ "WHITESPACE",
+ "SIGNIFICANT_WHITESPACE",
+ "END_ELEMENT",
+ "END_ENTITY",
+ "XML_DECLARATION",
+ );
+ $lookup = array();
+
+ foreach( $xmlReaderConstants as $name ) {
+ $lookup[constant("XmlReader::$name")] = $name;
+ }
+ }
+
+ print( var_dump(
+ $lookup[$this->reader->nodeType],
+ $this->reader->name,
+ $this->reader->value
+ )."\n\n" );
+ }
+
+ /**
+ * Primary entry point
+ */
+ public function doImport() {
+ $this->reader->read();
+
+ if ( $this->reader->name != 'mediawiki' ) {
+ throw new MWException( "Expected <mediawiki> tag, got ".
+ $this->reader->name );
+ }
+ $this->debug( "<mediawiki> tag is correct." );
+
+ $this->debug( "Starting primary dump processing loop." );
+
+ $keepReading = $this->reader->read();
+ $skip = false;
+ while ( $keepReading ) {
+ $tag = $this->reader->name;
+ $type = $this->reader->nodeType;
+
+ if ( !wfRunHooks( 'ImportHandleToplevelXMLTag', $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();
+ }
+ }
+
+ return true;
+ }
+
+ 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;
+ }
+ throw new MWException( "SiteInfo tag is not yet handled, do not set mSiteInfoCallback" );
+ }
+
+ private function handleLogItem() {
+ $this->debug( "Enter log item handler." );
+ $logInfo = array();
+
+ // Fields that can just be stuffed in the pageInfo object
+ $normalFields = array( 'id', 'comment', 'type', 'action', 'timestamp',
+ 'logtitle', 'params' );
+
+ while ( $this->reader->read() ) {
+ if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ $this->reader->name == 'logitem') {
+ break;
+ }
+
+ $tag = $this->reader->name;
+
+ if ( !wfRunHooks( 'ImportHandleLogItemXMLTag',
+ $this, $logInfo ) ) {
+ // Do nothing
+ } elseif ( in_array( $tag, $normalFields ) ) {
+ $logInfo[$tag] = $this->nodeContents();
+ } elseif ( $tag == 'contributor' ) {
+ $logInfo['contributor'] = $this->handleContributor();
+ } elseif ( $tag != '#text' ) {
+ $this->warn( "Unhandled log-item XML tag $tag" );
+ }
+ }
+
+ $this->processLogItem( $logInfo );
+ }
+
+ private function processLogItem( $logInfo ) {
+ $revision = new WikiRevision;
+
+ $revision->setID( $logInfo['id'] );
+ $revision->setType( $logInfo['type'] );
+ $revision->setAction( $logInfo['action'] );
+ $revision->setTimestamp( $logInfo['timestamp'] );
+ $revision->setParams( $logInfo['params'] );
+ $revision->setTitle( Title::newFromText( $logInfo['logtitle'] ) );
+
+ if ( isset( $logInfo['comment'] ) ) {
+ $revision->setComment( $logInfo['comment'] );
+ }
+
+ if ( isset( $logInfo['contributor']['ip'] ) ) {
+ $revision->setUserIP( $logInfo['contributor']['ip'] );
+ }
+ if ( isset( $logInfo['contributor']['username'] ) ) {
+ $revision->setUserName( $logInfo['contributor']['username'] );
+ }
+
+ return $this->logItemCallback( $revision );
+ }
+
+ private function handlePage() {
+ // Handle page data.
+ $this->debug( "Enter page handler." );
+ $pageInfo = array( 'revisionCount' => 0, 'successfulRevisionCount' => 0 );
+
+ // Fields that can just be stuffed in the pageInfo object
+ $normalFields = array( 'title', 'id', 'redirect', 'restrictions' );
+
+ $skip = false;
+ $badTitle = false;
+
+ while ( $skip ? $this->reader->next() : $this->reader->read() ) {
+ if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ $this->reader->name == 'page') {
+ break;
+ }
+
+ $tag = $this->reader->name;
+
+ if ( $badTitle ) {
+ // The title is invalid, bail out of this page
+ $skip = true;
+ } elseif ( !wfRunHooks( 'ImportHandlePageXMLTag', array( $this,
+ &$pageInfo ) ) ) {
+ // Do nothing
+ } elseif ( in_array( $tag, $normalFields ) ) {
+ $pageInfo[$tag] = $this->nodeContents();
+ if ( $tag == 'title' ) {
+ $title = $this->processTitle( $pageInfo['title'] );
+
+ if ( !$title ) {
+ $badTitle = true;
+ $skip = true;
+ }
+
+ $this->pageCallback( $title );
+ list( $pageInfo['_title'], $origTitle ) = $title;
+ }
+ } 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,
+ $pageInfo['revisionCount'],
+ $pageInfo['successfulRevisionCount'],
+ $pageInfo );
+ }
+
+ private function handleRevision( &$pageInfo ) {
+ $this->debug( "Enter revision handler" );
+ $revisionInfo = array();
+
+ $normalFields = array( 'id', 'timestamp', 'comment', 'minor', 'text' );
+
+ $skip = false;
+
+ while ( $skip ? $this->reader->next() : $this->reader->read() ) {
+ if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ $this->reader->name == 'revision') {
+ break;
+ }
+
+ $tag = $this->reader->name;
+
+ if ( !wfRunHooks( 'ImportHandleRevisionXMLTag', $this,
+ $pageInfo, $revisionInfo ) ) {
+ // Do nothing
+ } elseif ( in_array( $tag, $normalFields ) ) {
+ $revisionInfo[$tag] = $this->nodeContents();
+ } elseif ( $tag == 'contributor' ) {
+ $revisionInfo['contributor'] = $this->handleContributor();
+ } elseif ( $tag != '#text' ) {
+ $this->warn( "Unhandled revision XML tag $tag" );
+ $skip = true;
+ }
+ }
+
+ $pageInfo['revisionCount']++;
+ if ( $this->processRevision( $pageInfo, $revisionInfo ) ) {
+ $pageInfo['successfulRevisionCount']++;
+ }
+ }
+
+ private function processRevision( $pageInfo, $revisionInfo ) {
+ $revision = new WikiRevision;
+
+ $revision->setID( $revisionInfo['id'] );
+ $revision->setText( $revisionInfo['text'] );
+ $revision->setTitle( $pageInfo['_title'] );
+ $revision->setTimestamp( $revisionInfo['timestamp'] );
+
+ if ( isset( $revisionInfo['comment'] ) ) {
+ $revision->setComment( $revisionInfo['comment'] );
+ }
+
+ if ( isset( $revisionInfo['minor'] ) )
+ $revision->setMinor( true );
+
+ if ( isset( $revisionInfo['contributor']['ip'] ) ) {
+ $revision->setUserIP( $revisionInfo['contributor']['ip'] );
+ }
+ if ( isset( $revisionInfo['contributor']['username'] ) ) {
+ $revision->setUserName( $revisionInfo['contributor']['username'] );
+ }
+
+ return $this->revisionCallback( $revision );
+ }
+
+ private function handleUpload( &$pageInfo ) {
+ $this->debug( "Enter upload handler" );
+ $uploadInfo = array();
+
+ $normalFields = array( 'timestamp', 'comment', 'filename', 'text',
+ 'src', 'size' );
+
+ $skip = false;
+
+ while ( $skip ? $this->reader->next() : $this->reader->read() ) {
+ if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ $this->reader->name == 'upload') {
+ break;
+ }
+
+ $tag = $this->reader->name;
+
+ if ( !wfRunHooks( 'ImportHandleUploadXMLTag', $this,
+ $pageInfo ) ) {
+ // Do nothing
+ } elseif ( in_array( $tag, $normalFields ) ) {
+ $uploadInfo[$tag] = $this->nodeContents();
+ } elseif ( $tag == 'contributor' ) {
+ $uploadInfo['contributor'] = $this->handleContributor();
+ } elseif ( $tag != '#text' ) {
+ $this->warn( "Unhandled upload XML tag $tag" );
+ $skip = true;
+ }
+ }
+
+ return $this->processUpload( $pageInfo, $uploadInfo );
+ }
+
+ private function processUpload( $pageInfo, $uploadInfo ) {
+ $revision = new WikiRevision;
+
+ $revision->setTitle( $pageInfo['_title'] );
+ $revision->setID( $uploadInfo['id'] );
+ $revision->setTimestamp( $uploadInfo['timestamp'] );
+ $revision->setText( $uploadInfo['text'] );
+ $revision->setFilename( $uploadInfo['filename'] );
+ $revision->setSrc( $uploadInfo['src'] );
+ $revision->setSize( intval( $uploadInfo['size'] ) );
+ $revision->setComment( $uploadInfo['comment'] );
+
+ if ( isset( $uploadInfo['contributor']['ip'] ) ) {
+ $revision->setUserIP( $uploadInfo['contributor']['ip'] );
+ }
+ if ( isset( $uploadInfo['contributor']['username'] ) ) {
+ $revision->setUserName( $uploadInfo['contributor']['username'] );
+ }
+
+ return $this->uploadCallback( $revision );
+ }
+
+ private function handleContributor() {
+ $fields = array( 'id', 'ip', 'username' );
+ $info = array();
+
+ while ( $this->reader->read() ) {
+ if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ $this->reader->name == 'contributor') {
+ break;
+ }
+
+ $tag = $this->reader->name;
+
+ if ( in_array( $tag, $fields ) ) {
+ $info[$tag] = $this->nodeContents();
+ }
+ }
+
+ return $info;
+ }
+
+ private function processTitle( $text ) {
+ $workTitle = $text;
+ $origTitle = Title::newFromText( $workTitle );
+
+ if( !is_null( $this->mTargetNamespace ) && !is_null( $origTitle ) ) {
+ $title = Title::makeTitle( $this->mTargetNamespace,
+ $origTitle->getDBkey() );
+ } else {
+ $title = Title::newFromText( $workTitle );
+ }
+
+ if( is_null( $title ) ) {
+ // Invalid page title? Ignore the page
+ $this->notice( "Skipping invalid page title '$workTitle'" );
+ return false;
+ } elseif( $title->getInterwiki() != '' ) {
+ $this->notice( "Skipping interwiki page title '$workTitle'" );
+ return false;
+ }
+
+ return array( $title, $origTitle );
+ }
+}
+
+/** This is a horrible hack used to keep source compatibility */
+class UploadSourceAdapter {
+ static $sourceRegistrations = array();
+
+ private $mSource;
+ private $mBuffer;
+ private $mPosition;
+
+ static function registerSource( $source ) {
+ $id = wfGenerateToken();
+
+ self::$sourceRegistrations[$id] = $source;
+
+ return $id;
+ }
+
+ function stream_open( $path, $mode, $options, &$opened_path ) {
+ $url = parse_url($path);
+ $id = $url['host'];
+
+ if ( !isset( self::$sourceRegistrations[$id] ) ) {
+ return false;
+ }
+
+ $this->mSource = self::$sourceRegistrations[$id];
+
+ return true;
+ }
+
+ function stream_read( $count ) {
+ $return = '';
+ $leave = false;
+
+ while ( !$leave && !$this->mSource->atEnd() &&
+ strlen($this->mBuffer) < $count ) {
+ $read = $this->mSource->readChunk();
+
+ if ( !strlen($read) ) {
+ $leave = true;
+ }
+
+ $this->mBuffer .= $read;
+ }
+
+ if ( strlen($this->mBuffer) ) {
+ $return = substr( $this->mBuffer, 0, $count );
+ $this->mBuffer = substr( $this->mBuffer, $count );
+ }
+
+ $this->mPosition += strlen($return);
+
+ return $return;
+ }
+
+ function stream_write( $data ) {
+ return false;
+ }
+
+ function stream_tell() {
+ return $this->mPosition;
+ }
+
+ function stream_eof() {
+ return $this->mSource->atEnd();
+ }
+
+ function url_stat() {
+ $result = array();
+
+ $result['dev'] = $result[0] = 0;
+ $result['ino'] = $result[1] = 0;
+ $result['mode'] = $result[2] = 0;
+ $result['nlink'] = $result[3] = 0;
+ $result['uid'] = $result[4] = 0;
+ $result['gid'] = $result[5] = 0;
+ $result['rdev'] = $result[6] = 0;
+ $result['size'] = $result[7] = 0;
+ $result['atime'] = $result[8] = 0;
+ $result['mtime'] = $result[9] = 0;
+ $result['ctime'] = $result[10] = 0;
+ $result['blksize'] = $result[11] = 0;
+ $result['blocks'] = $result[12] = 0;
+
+ return $result;
+ }
+}
+
+class XMLReader2 extends XMLReader {
+ function nodeContents() {
+ if( $this->isEmptyElement ) {
+ return "";
+ }
+ $buffer = "";
+ while( $this->read() ) {
+ switch( $this->nodeType ) {
+ case XmlReader::TEXT:
+ case XmlReader::SIGNIFICANT_WHITESPACE:
+ $buffer .= $this->value;
+ break;
+ case XmlReader::END_ELEMENT:
+ return $buffer;
+ }
+ }
+ return $this->close();
+ }
+}
+
+/**
+ * @todo document (e.g. one-sentence class description).
* @ingroup SpecialPage
*/
class WikiRevision {
@@ -310,7 +1060,6 @@ class WikiRevision {
$file = wfLocalFile( $this->getTitle() );
if( !$file ) {
- var_dump( $file );
wfDebug( "IMPORT: Bad file. :(\n" );
return false;
}
@@ -371,658 +1120,6 @@ class WikiRevision {
}
/**
- * implements Special:Import
- * @ingroup SpecialPage
- */
-class WikiImporter {
- var $mDebug = false;
- var $mSource = null;
- var $mPageCallback = null;
- var $mPageOutCallback = null;
- var $mRevisionCallback = null;
- var $mLogItemCallback = null;
- var $mUploadCallback = null;
- var $mTargetNamespace = null;
- var $mXmlNamespace = false;
- var $lastfield;
- var $tagStack = array();
-
- function __construct( $source ) {
- $this->setRevisionCallback( array( $this, "importRevision" ) );
- $this->setUploadCallback( array( $this, "importUpload" ) );
- $this->setLogItemCallback( array( $this, "importLogItem" ) );
- $this->mSource = $source;
- }
-
- function throwXmlError( $err ) {
- $this->debug( "FAILURE: $err" );
- wfDebug( "WikiImporter XML error: $err\n" );
- }
-
- function handleXmlNamespace ( $parser, $data, $prefix=false, $uri=false ) {
- if( preg_match( '/www.mediawiki.org/',$prefix ) ) {
- $prefix = str_replace( '/','\/',$prefix );
- $this->mXmlNamespace='/^'.$prefix.':/';
- }
- }
-
- function stripXmlNamespace($name) {
- if( $this->mXmlNamespace ) {
- return(preg_replace($this->mXmlNamespace,'',$name,1));
- }
- else {
- return($name);
- }
- }
-
- # --------------
-
- function doImport() {
- if( empty( $this->mSource ) ) {
- return new WikiErrorMsg( "importnotext" );
- }
-
- $parser = xml_parser_create_ns( "UTF-8" );
-
- # case folding violates XML standard, turn it off
- xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
-
- xml_set_object( $parser, $this );
- xml_set_element_handler( $parser, "in_start", "" );
- xml_set_start_namespace_decl_handler( $parser, "handleXmlNamespace" );
-
- $offset = 0; // for context extraction on error reporting
- do {
- $chunk = $this->mSource->readChunk();
- if( !xml_parse( $parser, $chunk, $this->mSource->atEnd() ) ) {
- wfDebug( "WikiImporter::doImport encountered XML parsing error\n" );
- return new WikiXmlError( $parser, wfMsgHtml( 'import-parse-failure' ), $chunk, $offset );
- }
- $offset += strlen( $chunk );
- } while( $chunk !== false && !$this->mSource->atEnd() );
- xml_parser_free( $parser );
-
- return true;
- }
-
- function debug( $data ) {
- if( $this->mDebug ) {
- wfDebug( "IMPORT: $data\n" );
- }
- }
-
- function notice( $data ) {
- global $wgCommandLineMode;
- if( $wgCommandLineMode ) {
- print "$data\n";
- } else {
- global $wgOut;
- $wgOut->addHTML( "<li>" . htmlspecialchars( $data ) . "</li>\n" );
- }
- }
-
- /**
- * Set debug mode...
- */
- function setDebug( $debug ) {
- $this->mDebug = $debug;
- }
-
- /**
- * Sets the action to perform as each new page in the stream is reached.
- * @param $callback callback
- * @return callback
- */
- function setPageCallback( $callback ) {
- $previous = $this->mPageCallback;
- $this->mPageCallback = $callback;
- return $previous;
- }
-
- /**
- * Sets the action to perform as each page in the stream is completed.
- * Callback accepts the page title (as a Title object), a second object
- * with the original title form (in case it's been overridden into a
- * local namespace), and a count of revisions.
- *
- * @param $callback callback
- * @return callback
- */
- function setPageOutCallback( $callback ) {
- $previous = $this->mPageOutCallback;
- $this->mPageOutCallback = $callback;
- return $previous;
- }
-
- /**
- * Sets the action to perform as each page revision is reached.
- * @param $callback callback
- * @return callback
- */
- function setRevisionCallback( $callback ) {
- $previous = $this->mRevisionCallback;
- $this->mRevisionCallback = $callback;
- return $previous;
- }
-
- /**
- * Sets the action to perform as each file upload version is reached.
- * @param $callback callback
- * @return callback
- */
- function setUploadCallback( $callback ) {
- $previous = $this->mUploadCallback;
- $this->mUploadCallback = $callback;
- return $previous;
- }
-
- /**
- * Sets the action to perform as each log item reached.
- * @param $callback callback
- * @return callback
- */
- function setLogItemCallback( $callback ) {
- $previous = $this->mLogItemCallback;
- $this->mLogItemCallback = $callback;
- return $previous;
- }
-
- /**
- * Set a target namespace to override the defaults
- */
- function setTargetNamespace( $namespace ) {
- if( is_null( $namespace ) ) {
- // Don't override namespaces
- $this->mTargetNamespace = null;
- } elseif( $namespace >= 0 ) {
- // FIXME: Check for validity
- $this->mTargetNamespace = intval( $namespace );
- } else {
- return false;
- }
- }
-
- /**
- * Default per-revision callback, performs the import.
- * @param $revision WikiRevision
- * @private
- */
- function importRevision( $revision ) {
- $dbw = wfGetDB( DB_MASTER );
- return $dbw->deadlockLoop( array( $revision, 'importOldRevision' ) );
- }
-
- /**
- * Default per-revision callback, performs the import.
- * @param $rev WikiRevision
- * @private
- */
- function importLogItem( $rev ) {
- $dbw = wfGetDB( DB_MASTER );
- return $dbw->deadlockLoop( array( $rev, 'importLogItem' ) );
- }
-
- /**
- * Dummy for now...
- */
- function importUpload( $revision ) {
- //$dbw = wfGetDB( DB_MASTER );
- //return $dbw->deadlockLoop( array( $revision, 'importUpload' ) );
- return false;
- }
-
- /**
- * Alternate per-revision callback, for debugging.
- * @param $revision WikiRevision
- * @private
- */
- function debugRevisionHandler( &$revision ) {
- $this->debug( "Got revision:" );
- if( is_object( $revision->title ) ) {
- $this->debug( "-- Title: " . $revision->title->getPrefixedText() );
- } else {
- $this->debug( "-- Title: <invalid>" );
- }
- $this->debug( "-- User: " . $revision->user_text );
- $this->debug( "-- Timestamp: " . $revision->timestamp );
- $this->debug( "-- Comment: " . $revision->comment );
- $this->debug( "-- Text: " . $revision->text );
- }
-
- /**
- * Notify the callback function when a new <page> is reached.
- * @param $title Title
- * @private
- */
- function pageCallback( $title ) {
- if( is_callable( $this->mPageCallback ) ) {
- call_user_func( $this->mPageCallback, $title );
- }
- }
-
- /**
- * Notify the callback function when a </page> is closed.
- * @param $title Title
- * @param $origTitle Title
- * @param $revisionCount int
- * @param $successCount Int: number of revisions for which callback returned true
- * @private
- */
- function pageOutCallback( $title, $origTitle, $revisionCount, $successCount ) {
- if( is_callable( $this->mPageOutCallback ) ) {
- call_user_func( $this->mPageOutCallback, $title, $origTitle,
- $revisionCount, $successCount );
- }
- }
-
- # XML parser callbacks from here out -- beware!
- function donothing( $parser, $x, $y="" ) {
- #$this->debug( "donothing" );
- }
-
- function in_start( $parser, $name, $attribs ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "in_start $name" );
- if( $name != "mediawiki" ) {
- return $this->throwXMLerror( "Expected <mediawiki>, got <$name>" );
- }
- xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" );
- }
-
- function in_mediawiki( $parser, $name, $attribs ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "in_mediawiki $name" );
- if( $name == 'siteinfo' ) {
- xml_set_element_handler( $parser, "in_siteinfo", "out_siteinfo" );
- } elseif( $name == 'page' ) {
- $this->push( $name );
- $this->workRevisionCount = 0;
- $this->workSuccessCount = 0;
- $this->uploadCount = 0;
- $this->uploadSuccessCount = 0;
- xml_set_element_handler( $parser, "in_page", "out_page" );
- } elseif( $name == 'logitem' ) {
- $this->push( $name );
- $this->workRevision = new WikiRevision;
- xml_set_element_handler( $parser, "in_logitem", "out_logitem" );
- } else {
- return $this->throwXMLerror( "Expected <page>, got <$name>" );
- }
- }
- function out_mediawiki( $parser, $name ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "out_mediawiki $name" );
- if( $name != "mediawiki" ) {
- return $this->throwXMLerror( "Expected </mediawiki>, got </$name>" );
- }
- xml_set_element_handler( $parser, "donothing", "donothing" );
- }
-
-
- function in_siteinfo( $parser, $name, $attribs ) {
- // no-ops for now
- $name = $this->stripXmlNamespace($name);
- $this->debug( "in_siteinfo $name" );
- switch( $name ) {
- case "sitename":
- case "base":
- case "generator":
- case "case":
- case "namespaces":
- case "namespace":
- break;
- default:
- return $this->throwXMLerror( "Element <$name> not allowed in <siteinfo>." );
- }
- }
-
- function out_siteinfo( $parser, $name ) {
- $name = $this->stripXmlNamespace($name);
- if( $name == "siteinfo" ) {
- xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" );
- }
- }
-
-
- function in_page( $parser, $name, $attribs ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "in_page $name" );
- switch( $name ) {
- case "id":
- case "title":
- case "redirect":
- case "restrictions":
- $this->appendfield = $name;
- $this->appenddata = "";
- xml_set_element_handler( $parser, "in_nothing", "out_append" );
- xml_set_character_data_handler( $parser, "char_append" );
- break;
- case "revision":
- $this->push( "revision" );
- if( is_object( $this->pageTitle ) ) {
- $this->workRevision = new WikiRevision;
- $this->workRevision->setTitle( $this->pageTitle );
- $this->workRevisionCount++;
- } else {
- // Skipping items due to invalid page title
- $this->workRevision = null;
- }
- xml_set_element_handler( $parser, "in_revision", "out_revision" );
- break;
- case "upload":
- $this->push( "upload" );
- if( is_object( $this->pageTitle ) ) {
- $this->workRevision = new WikiRevision;
- $this->workRevision->setTitle( $this->pageTitle );
- $this->uploadCount++;
- } else {
- // Skipping items due to invalid page title
- $this->workRevision = null;
- }
- xml_set_element_handler( $parser, "in_upload", "out_upload" );
- break;
- default:
- return $this->throwXMLerror( "Element <$name> not allowed in a <page>." );
- }
- }
-
- function out_page( $parser, $name ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "out_page $name" );
- $this->pop();
- if( $name != "page" ) {
- return $this->throwXMLerror( "Expected </page>, got </$name>" );
- }
- xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" );
-
- $this->pageOutCallback( $this->pageTitle, $this->origTitle,
- $this->workRevisionCount, $this->workSuccessCount );
-
- $this->workTitle = null;
- $this->workRevision = null;
- $this->workRevisionCount = 0;
- $this->workSuccessCount = 0;
- $this->pageTitle = null;
- $this->origTitle = null;
- }
-
- function in_nothing( $parser, $name, $attribs ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "in_nothing $name" );
- return $this->throwXMLerror( "No child elements allowed here; got <$name>" );
- }
-
- function char_append( $parser, $data ) {
- $this->debug( "char_append '$data'" );
- $this->appenddata .= $data;
- }
-
- function out_append( $parser, $name ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "out_append $name" );
- if( $name != $this->appendfield ) {
- return $this->throwXMLerror( "Expected </{$this->appendfield}>, got </$name>" );
- }
-
- switch( $this->appendfield ) {
- case "title":
- $this->workTitle = $this->appenddata;
- $this->origTitle = Title::newFromText( $this->workTitle );
- if( !is_null( $this->mTargetNamespace ) && !is_null( $this->origTitle ) ) {
- $this->pageTitle = Title::makeTitle( $this->mTargetNamespace,
- $this->origTitle->getDBkey() );
- } else {
- $this->pageTitle = Title::newFromText( $this->workTitle );
- }
- if( is_null( $this->pageTitle ) ) {
- // Invalid page title? Ignore the page
- $this->notice( "Skipping invalid page title '$this->workTitle'" );
- } elseif( $this->pageTitle->getInterwiki() != '' ) {
- $this->notice( "Skipping interwiki page title '$this->workTitle'" );
- $this->pageTitle = null;
- } else {
- $this->pageCallback( $this->workTitle );
- }
- break;
- case "id":
- if ( $this->parentTag() == 'revision' || $this->parentTag() == 'logitem' ) {
- if( $this->workRevision )
- $this->workRevision->setID( $this->appenddata );
- }
- break;
- case "text":
- if( $this->workRevision )
- $this->workRevision->setText( $this->appenddata );
- break;
- case "username":
- if( $this->workRevision )
- $this->workRevision->setUsername( $this->appenddata );
- break;
- case "ip":
- if( $this->workRevision )
- $this->workRevision->setUserIP( $this->appenddata );
- break;
- case "timestamp":
- if( $this->workRevision )
- $this->workRevision->setTimestamp( $this->appenddata );
- break;
- case "comment":
- if( $this->workRevision )
- $this->workRevision->setComment( $this->appenddata );
- break;
- case "type":
- if( $this->workRevision )
- $this->workRevision->setType( $this->appenddata );
- break;
- case "action":
- if( $this->workRevision )
- $this->workRevision->setAction( $this->appenddata );
- break;
- case "logtitle":
- if( $this->workRevision )
- $this->workRevision->setTitle( Title::newFromText( $this->appenddata ) );
- break;
- case "params":
- if( $this->workRevision )
- $this->workRevision->setParams( $this->appenddata );
- break;
- case "minor":
- if( $this->workRevision )
- $this->workRevision->setMinor( true );
- break;
- case "filename":
- if( $this->workRevision )
- $this->workRevision->setFilename( $this->appenddata );
- break;
- case "src":
- if( $this->workRevision )
- $this->workRevision->setSrc( $this->appenddata );
- break;
- case "size":
- if( $this->workRevision )
- $this->workRevision->setSize( intval( $this->appenddata ) );
- break;
- default:
- $this->debug( "Bad append: {$this->appendfield}" );
- }
- $this->appendfield = "";
- $this->appenddata = "";
-
- $parent = $this->parentTag();
- xml_set_element_handler( $parser, "in_$parent", "out_$parent" );
- xml_set_character_data_handler( $parser, "donothing" );
- }
-
- function in_revision( $parser, $name, $attribs ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "in_revision $name" );
- switch( $name ) {
- case "id":
- case "timestamp":
- case "comment":
- case "minor":
- case "text":
- $this->appendfield = $name;
- xml_set_element_handler( $parser, "in_nothing", "out_append" );
- xml_set_character_data_handler( $parser, "char_append" );
- break;
- case "contributor":
- $this->push( "contributor" );
- xml_set_element_handler( $parser, "in_contributor", "out_contributor" );
- break;
- default:
- return $this->throwXMLerror( "Element <$name> not allowed in a <revision>." );
- }
- }
-
- function out_revision( $parser, $name ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "out_revision $name" );
- $this->pop();
- if( $name != "revision" ) {
- return $this->throwXMLerror( "Expected </revision>, got </$name>" );
- }
- xml_set_element_handler( $parser, "in_page", "out_page" );
-
- if( $this->workRevision ) {
- $ok = call_user_func_array( $this->mRevisionCallback,
- array( $this->workRevision, $this ) );
- if( $ok ) {
- $this->workSuccessCount++;
- }
- }
- }
-
- function in_logitem( $parser, $name, $attribs ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "in_logitem $name" );
- switch( $name ) {
- case "id":
- case "timestamp":
- case "comment":
- case "type":
- case "action":
- case "logtitle":
- case "params":
- $this->appendfield = $name;
- xml_set_element_handler( $parser, "in_nothing", "out_append" );
- xml_set_character_data_handler( $parser, "char_append" );
- break;
- case "contributor":
- $this->push( "contributor" );
- xml_set_element_handler( $parser, "in_contributor", "out_contributor" );
- break;
- default:
- return $this->throwXMLerror( "Element <$name> not allowed in a <revision>." );
- }
- }
-
- function out_logitem( $parser, $name ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "out_logitem $name" );
- $this->pop();
- if( $name != "logitem" ) {
- return $this->throwXMLerror( "Expected </logitem>, got </$name>" );
- }
- xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" );
-
- if( $this->workRevision ) {
- $ok = call_user_func_array( $this->mLogItemCallback,
- array( $this->workRevision, $this ) );
- if( $ok ) {
- $this->workSuccessCount++;
- }
- }
- }
-
- function in_upload( $parser, $name, $attribs ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "in_upload $name" );
- switch( $name ) {
- case "timestamp":
- case "comment":
- case "text":
- case "filename":
- case "src":
- case "size":
- $this->appendfield = $name;
- xml_set_element_handler( $parser, "in_nothing", "out_append" );
- xml_set_character_data_handler( $parser, "char_append" );
- break;
- case "contributor":
- $this->push( "contributor" );
- xml_set_element_handler( $parser, "in_contributor", "out_contributor" );
- break;
- default:
- return $this->throwXMLerror( "Element <$name> not allowed in an <upload>." );
- }
- }
-
- function out_upload( $parser, $name ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "out_revision $name" );
- $this->pop();
- if( $name != "upload" ) {
- return $this->throwXMLerror( "Expected </upload>, got </$name>" );
- }
- xml_set_element_handler( $parser, "in_page", "out_page" );
-
- if( $this->workRevision ) {
- $ok = call_user_func_array( $this->mUploadCallback,
- array( $this->workRevision, $this ) );
- if( $ok ) {
- $this->workUploadSuccessCount++;
- }
- }
- }
-
- function in_contributor( $parser, $name, $attribs ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "in_contributor $name" );
- switch( $name ) {
- case "username":
- case "ip":
- case "id":
- $this->appendfield = $name;
- xml_set_element_handler( $parser, "in_nothing", "out_append" );
- xml_set_character_data_handler( $parser, "char_append" );
- break;
- default:
- $this->throwXMLerror( "Invalid tag <$name> in <contributor>" );
- }
- }
-
- function out_contributor( $parser, $name ) {
- $name = $this->stripXmlNamespace($name);
- $this->debug( "out_contributor $name" );
- $this->pop();
- if( $name != "contributor" ) {
- return $this->throwXMLerror( "Expected </contributor>, got </$name>" );
- }
- $parent = $this->parentTag();
- xml_set_element_handler( $parser, "in_$parent", "out_$parent" );
- }
-
- private function push( $name ) {
- array_push( $this->tagStack, $name );
- $this->debug( "PUSH $name" );
- }
-
- private function pop() {
- $name = array_pop( $this->tagStack );
- $this->debug( "POP $name" );
- return $name;
- }
-
- private function parentTag() {
- $name = $this->tagStack[count( $this->tagStack ) - 1];
- $this->debug( "PARENT $name" );
- return $name;
- }
-
-}
-
-/**
* @todo document (e.g. one-sentence class description).
* @ingroup SpecialPage
*/
@@ -1066,27 +1163,27 @@ class ImportStreamSource {
static function newFromFile( $filename ) {
$file = @fopen( $filename, 'rt' );
if( !$file ) {
- return new WikiErrorMsg( "importcantopen" );
+ return Status::newFatal( "importcantopen" );
}
- return new ImportStreamSource( $file );
+ return Status::newGood( new ImportStreamSource( $file ) );
}
static function newFromUpload( $fieldname = "xmlimport" ) {
$upload =& $_FILES[$fieldname];
if( !isset( $upload ) || !$upload['name'] ) {
- return new WikiErrorMsg( 'importnofile' );
+ return Status::newFatal( 'importnofile' );
}
if( !empty( $upload['error'] ) ) {
switch($upload['error']){
case 1: # The uploaded file exceeds the upload_max_filesize directive in php.ini.
- return new WikiErrorMsg( 'importuploaderrorsize' );
+ return Status::newFatal( 'importuploaderrorsize' );
case 2: # The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.
- return new WikiErrorMsg( 'importuploaderrorsize' );
+ return Status::newFatal( 'importuploaderrorsize' );
case 3: # The uploaded file was only partially uploaded
- return new WikiErrorMsg( 'importuploaderrorpartial' );
- case 6: #Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3.
- return new WikiErrorMsg( 'importuploaderrortemp' );
+ return Status::newFatal( 'importuploaderrorpartial' );
+ case 6: #Missing a temporary folder.
+ return Status::newFatal( 'importuploaderrortemp' );
# case else: # Currently impossible
}
@@ -1095,7 +1192,7 @@ class ImportStreamSource {
if( is_uploaded_file( $fname ) ) {
return ImportStreamSource::newFromFile( $fname );
} else {
- return new WikiErrorMsg( 'importnofile' );
+ return Status::newFatal( 'importnofile' );
}
}
@@ -1111,19 +1208,19 @@ class ImportStreamSource {
fwrite( $file, $data );
fflush( $file );
fseek( $file, 0 );
- return new ImportStreamSource( $file );
+ return Status::newGood( new ImportStreamSource( $file ) );
} else {
- return new WikiErrorMsg( 'importcantopen' );
+ return Status::newFatal( 'importcantopen' );
}
}
public static function newFromInterwiki( $interwiki, $page, $history = false, $templates = false, $pageLinkDepth = 0 ) {
if( $page == '' ) {
- return new WikiErrorMsg( 'import-noarticle' );
+ return Status::newFatal( 'import-noarticle' );
}
$link = Title::newFromText( "$interwiki:Special:Export/$page" );
if( is_null( $link ) || $link->getInterwiki() == '' ) {
- return new WikiErrorMsg( 'importbadinterwiki' );
+ return Status::newFatal( 'importbadinterwiki' );
} else {
$params = array();
if ( $history ) $params['history'] = 1;
diff --git a/includes/Interwiki.php b/includes/Interwiki.php
index 3c71f6ee..5a3b6556 100644
--- a/includes/Interwiki.php
+++ b/includes/Interwiki.php
@@ -7,7 +7,8 @@
/**
* The interwiki class
* All information is loaded on creation when called by Interwiki::fetch( $prefix ).
- * All work is done on slave, because this should *never* change (except during schema updates etc, which arent wiki-related)
+ * All work is done on slave, because this should *never* change (except during
+ * schema updates etc, which aren't wiki-related)
*/
class Interwiki {
@@ -15,11 +16,13 @@ class Interwiki {
protected static $smCache = array();
const CACHE_LIMIT = 100; // 0 means unlimited, any other value is max number of entries.
- protected $mPrefix, $mURL, $mLocal, $mTrans;
+ protected $mPrefix, $mURL, $mAPI, $mWikiID, $mLocal, $mTrans;
- public function __construct( $prefix = null, $url = '', $local = 0, $trans = 0 ) {
+ public function __construct( $prefix = null, $url = '', $api = '', $wikiId = '', $local = 0, $trans = 0 ) {
$this->mPrefix = $prefix;
$this->mURL = $url;
+ $this->mAPI = $api;
+ $this->mWikiID = $wikiId;
$this->mLocal = $local;
$this->mTrans = $trans;
}
@@ -61,7 +64,7 @@ class Interwiki {
}
if( self::CACHE_LIMIT && count( self::$smCache ) >= self::CACHE_LIMIT ) {
reset( self::$smCache );
- unset( self::$smCache[ key( self::$smCache ) ] );
+ unset( self::$smCache[key( self::$smCache )] );
}
self::$smCache[$prefix] = $iw;
return $iw;
@@ -107,7 +110,7 @@ class Interwiki {
$db = CdbReader::open( $wgInterwikiCache );
}
/* Resolve site name */
- if( $wgInterwikiScopes>=3 && !$site ) {
+ if( $wgInterwikiScopes >= 3 && !$site ) {
$site = $db->get( '__sites:' . wfWikiID() );
if ( $site == '' ) {
$site = $wgInterwikiFallbackSite;
@@ -123,8 +126,9 @@ class Interwiki {
if ( $value == '' && $wgInterwikiScopes >= 2 ) {
$value = $db->get( "__global:{$prefix}" );
}
- if ( $value == 'undef' )
+ if ( $value == 'undef' ) {
$value = '';
+ }
return $value;
}
@@ -139,7 +143,7 @@ class Interwiki {
global $wgMemc, $wgInterwikiExpiry;
$key = wfMemcKey( 'interwiki', $prefix );
$mc = $wgMemc->get( $key );
- $iw = false;
+
if( $mc && is_array( $mc ) ) { // is_array is hack for old keys
$iw = Interwiki::loadFromArray( $mc );
if( $iw ) {
@@ -153,7 +157,12 @@ class Interwiki {
__METHOD__ ) );
$iw = Interwiki::loadFromArray( $row );
if ( $iw ) {
- $mc = array( 'iw_url' => $iw->mURL, 'iw_local' => $iw->mLocal, 'iw_trans' => $iw->mTrans );
+ $mc = array(
+ 'iw_url' => $iw->mURL,
+ 'iw_api' => $iw->mAPI,
+ 'iw_local' => $iw->mLocal,
+ 'iw_trans' => $iw->mTrans
+ );
$wgMemc->add( $key, $mc, $wgInterwikiExpiry );
return $iw;
}
@@ -173,6 +182,9 @@ class Interwiki {
$iw->mURL = $mc['iw_url'];
$iw->mLocal = $mc['iw_local'];
$iw->mTrans = $mc['iw_trans'];
+ $iw->mAPI = isset( $mc['iw_api'] ) ? $mc['iw_api'] : '';
+ $iw->mWikiID = isset( $mc['iw_wikiid'] ) ? $mc['iw_wikiid'] : '';
+
return $iw;
}
return false;
@@ -180,7 +192,7 @@ class Interwiki {
/**
* Get the URL for a particular title (or with $1 if no title given)
- *
+ *
* @param $title String: what text to put for the article name
* @return String: the URL
*/
@@ -193,6 +205,24 @@ class Interwiki {
}
/**
+ * Get the API URL for this wiki
+ *
+ * @return String: the URL
+ */
+ public function getAPI() {
+ return $this->mAPI;
+ }
+
+ /**
+ * Get the DB name for this wiki
+ *
+ * @return String: the DB name
+ */
+ public function getWikiID() {
+ return $this->mWikiID;
+ }
+
+ /**
* Is this a local link from a sister project, or is
* it something outside, like Google
*
diff --git a/includes/JSMin.php b/includes/JSMin.php
deleted file mode 100644
index 70db7022..00000000
--- a/includes/JSMin.php
+++ /dev/null
@@ -1,290 +0,0 @@
-<?php
-/**
- * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
- *
- * This is pretty much a direct port of jsmin.c to PHP with just a few
- * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
- * outputs to stdout, this library accepts a string as input and returns another
- * string as output.
- *
- * PHP 5 or higher is required.
- *
- * Permission is hereby granted to use this version of the library under the
- * same terms as jsmin.c, which has the following license:
- *
- * --
- * Copyright (c) 2002 Douglas Crockford (www.crockford.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 shall be used for Good, not Evil.
- *
- * 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.
- * --
- *
- * @package JSMin
- * @author Ryan Grove <ryan@wonko.com>
- * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
- * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
- * @license http://opensource.org/licenses/mit-license.php MIT License
- * @version 1.1.1 (2008-03-02)
- * @link http://code.google.com/p/jsmin-php/
- */
-
-class JSMin {
- const ORD_LF = 10;
- const ORD_SPACE = 32;
-
- protected $a = '';
- protected $b = '';
- protected $input = '';
- protected $inputIndex = 0;
- protected $inputLength = 0;
- protected $lookAhead = null;
- protected $output = '';
-
- // -- Public Static Methods --------------------------------------------------
-
- public static function minify( $js ) {
- $jsmin = new JSMin( $js );
- return $jsmin->min();
- }
-
- // -- Public Instance Methods ------------------------------------------------
-
- public function __construct( $input ) {
- $this->input = str_replace( "\r\n", "\n", $input );
- $this->inputLength = strlen( $this->input );
- }
-
- // -- Protected Instance Methods ---------------------------------------------
-
- protected function action( $d ) {
- switch( $d ) {
- case 1:
- $this->output .= $this->a;
-
- case 2:
- $this->a = $this->b;
-
- if ( $this->a === "'" || $this->a === '"' ) {
- for ( ; ; ) {
- $this->output .= $this->a;
- $this->a = $this->get();
-
- if ( $this->a === $this->b ) {
- break;
- }
-
- if ( ord( $this->a ) <= self::ORD_LF ) {
- throw new JSMinException( 'Unterminated string literal.' );
- }
-
- if ( $this->a === '\\' ) {
- $this->output .= $this->a;
- $this->a = $this->get();
- }
- }
- }
-
- case 3:
- $this->b = $this->next();
-
- if ( $this->b === '/' && (
- $this->a === '(' || $this->a === ',' || $this->a === '=' ||
- $this->a === ':' || $this->a === '[' || $this->a === '!' ||
- $this->a === '&' || $this->a === '|' || $this->a === '?' ) ) {
-
- $this->output .= $this->a . $this->b;
-
- for ( ; ; ) {
- $this->a = $this->get();
-
- if ( $this->a === '/' ) {
- break;
- } elseif ( $this->a === '\\' ) {
- $this->output .= $this->a;
- $this->a = $this->get();
- } elseif ( ord( $this->a ) <= self::ORD_LF ) {
- throw new JSMinException( 'Unterminated regular expression ' .
- 'literal.' );
- }
-
- $this->output .= $this->a;
- }
-
- $this->b = $this->next();
- }
- }
- }
-
- protected function get() {
- $c = $this->lookAhead;
- $this->lookAhead = null;
-
- if ( $c === null ) {
- if ( $this->inputIndex < $this->inputLength ) {
- $c = substr( $this->input, $this->inputIndex, 1 );
- $this->inputIndex += 1;
- } else {
- $c = null;
- }
- }
-
- if ( $c === "\r" ) {
- return "\n";
- }
-
- if ( $c === null || $c === "\n" || ord( $c ) >= self::ORD_SPACE ) {
- return $c;
- }
-
- return ' ';
- }
-
- protected function isAlphaNum( $c ) {
- return ord( $c ) > 126 || $c === '\\' || preg_match( '/^[\w\$]$/', $c ) === 1;
- }
-
- protected function min() {
- $this->a = "\n";
- $this->action( 3 );
-
- while ( $this->a !== null ) {
- switch ( $this->a ) {
- case ' ':
- if ( $this->isAlphaNum( $this->b ) ) {
- $this->action( 1 );
- } else {
- $this->action( 2 );
- }
- break;
-
- case "\n":
- switch ( $this->b ) {
- case '{':
- case '[':
- case '(':
- case '+':
- case '-':
- $this->action( 1 );
- break;
-
- case ' ':
- $this->action( 3 );
- break;
-
- default:
- if ( $this->isAlphaNum( $this->b ) ) {
- $this->action( 1 );
- }
- else {
- $this->action( 2 );
- }
- }
- break;
-
- default:
- switch ( $this->b ) {
- case ' ':
- if ( $this->isAlphaNum( $this->a ) ) {
- $this->action( 1 );
- break;
- }
-
- $this->action( 3 );
- break;
-
- case "\n":
- switch ( $this->a ) {
- case '}':
- case ']':
- case ')':
- case '+':
- case '-':
- case '"':
- case "'":
- $this->action( 1 );
- break;
-
- default:
- if ( $this->isAlphaNum( $this->a ) ) {
- $this->action( 1 );
- }
- else {
- $this->action( 3 );
- }
- }
- break;
-
- default:
- $this->action( 1 );
- break;
- }
- }
- }
-
- return $this->output;
- }
-
- protected function next() {
- $c = $this->get();
-
- if ( $c === '/' ) {
- switch( $this->peek() ) {
- case '/':
- for ( ; ; ) {
- $c = $this->get();
-
- if ( ord( $c ) <= self::ORD_LF ) {
- return $c;
- }
- }
-
- case '*':
- $this->get();
-
- for ( ; ; ) {
- switch( $this->get() ) {
- case '*':
- if ( $this->peek() === '/' ) {
- $this->get();
- return ' ';
- }
- break;
-
- case null:
- throw new JSMinException( 'Unterminated comment.' );
- }
- }
-
- default:
- return $c;
- }
- }
-
- return $c;
- }
-
- protected function peek() {
- $this->lookAhead = $this->get();
- return $this->lookAhead;
- }
-}
-
-// -- Exceptions ---------------------------------------------------------------
-class JSMinException extends Exception {}
diff --git a/includes/Licenses.php b/includes/Licenses.php
index 12a1f938..45944c73 100644
--- a/includes/Licenses.php
+++ b/includes/Licenses.php
@@ -31,7 +31,7 @@ class Licenses extends HTMLFormField {
*/
public function __construct( $params ) {
parent::__construct( $params );
-
+
$this->msg = empty( $params['licenses'] ) ? wfMsgForContent( 'licenses' ) : $params['licenses'];
$this->selected = null;
@@ -69,8 +69,6 @@ class Licenses extends HTMLFormField {
}
protected function trimStars( $str ) {
- $i = $count = 0;
-
$numStars = strspn( $str, '*' );
return array( $numStars, ltrim( substr( $str, $numStars ), ' ' ) );
}
@@ -107,7 +105,7 @@ class Licenses extends HTMLFormField {
protected function outputOption( $text, $value, $attribs = null, $depth = 0 ) {
$attribs['value'] = $value;
if ( $value === $this->selected )
- $attribs['selected'] = 'selected';
+ $attribs['selected'] = 'selected';
$val = str_repeat( /* &nbsp */ "\xc2\xa0", $depth * 2 ) . $text;
return str_repeat( "\t", $depth ) . Xml::element( 'option', $attribs, $val ) . "\n";
}
@@ -133,18 +131,18 @@ class Licenses extends HTMLFormField {
*/
public function getInputHTML( $value ) {
$this->selected = $value;
-
+
$this->html = $this->outputOption( wfMsg( 'nolicense' ), '',
(bool)$this->selected ? null : array( 'selected' => 'selected' ) );
$this->makeHtml( $this->getLicenses() );
-
+
$attribs = array(
'name' => $this->mName,
'id' => $this->mID
);
if ( !empty( $this->mParams['disabled'] ) )
$attibs['disabled'] = 'disabled';
-
+
return Html::rawElement( 'select', $attribs, $this->html );
}
}
@@ -168,7 +166,7 @@ class License {
*
* @param $str String: license name??
*/
- function License( $str ) {
+ function __construct( $str ) {
list( $text, $template ) = explode( '|', strrev( $str ), 2 );
$this->template = strrev( $template );
diff --git a/includes/LinkBatch.php b/includes/LinkBatch.php
index d9a9666d..e689f966 100644
--- a/includes/LinkBatch.php
+++ b/includes/LinkBatch.php
@@ -12,12 +12,26 @@ class LinkBatch {
*/
var $data = array();
+ /**
+ * For debugging which method is using this class.
+ */
+ protected $caller;
+
function __construct( $arr = array() ) {
foreach( $arr as $item ) {
$this->addObj( $item );
}
}
+ /**
+ * Use ->setCaller( __METHOD__ ) to indicate which code is using this
+ * class. Only used in debugging output.
+ * @since 1.17
+ */
+ public function setCaller( $caller ) {
+ $this->caller = $caller;
+ }
+
public function addObj( $title ) {
if ( is_object( $title ) ) {
$this->add( $title->getNamespace(), $title->getDBkey() );
@@ -95,9 +109,9 @@ class LinkBatch {
$ids = array();
$remaining = $this->data;
- while ( $row = $res->fetchObject() ) {
+ foreach ( $res as $row ) {
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect );
+ $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest );
$ids[$title->getPrefixedDBkey()] = $row->page_id;
unset( $remaining[$row->page_namespace][$row->page_title] );
}
@@ -122,19 +136,19 @@ class LinkBatch {
}
wfProfileIn( __METHOD__ );
- // Construct query
- // This is very similar to Parser::replaceLinkHolders
+ // This is similar to LinkHolderArray::replaceInternal
$dbr = wfGetDB( DB_SLAVE );
- $page = $dbr->tableName( 'page' );
- $set = $this->constructSet( 'page', $dbr );
- if ( $set === false ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- $sql = "SELECT page_id, page_namespace, page_title, page_len, page_is_redirect FROM $page WHERE $set";
+ $table = 'page';
+ $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_len',
+ 'page_is_redirect', 'page_latest' );
+ $conds = $this->constructSet( 'page', $dbr );
// Do query
- $res = $dbr->query( $sql, __METHOD__ );
+ $caller = __METHOD__;
+ if ( strval( $this->caller ) !== '' ) {
+ $caller .= " (for {$this->caller})";
+ }
+ $res = $dbr->select( $table, $fields, $conds, $caller );
wfProfileOut( __METHOD__ );
return $res;
}
@@ -142,50 +156,11 @@ class LinkBatch {
/**
* Construct a WHERE clause which will match all the given titles.
*
- * @param string $prefix the appropriate table's field name prefix ('page', 'pl', etc)
- * @return string
- * @public
+ * @param $prefix String: the appropriate table's field name prefix ('page', 'pl', etc)
+ * @param $db DatabaseBase object to use
+ * @return mixed string with SQL where clause fragment, or false if no items.
*/
- public function constructSet( $prefix, &$db ) {
- $first = true;
- $firstTitle = true;
- $sql = '';
- foreach ( $this->data as $ns => $dbkeys ) {
- if ( !count( $dbkeys ) ) {
- continue;
- }
-
- if ( $first ) {
- $first = false;
- } else {
- $sql .= ' OR ';
- }
-
- if (count($dbkeys)==1) { // avoid multiple-reference syntax if simple equality can be used
- $singleKey = array_keys($dbkeys);
- $sql .= "({$prefix}_namespace=$ns AND {$prefix}_title=".
- $db->addQuotes($singleKey[0]).
- ")";
- } else {
- $sql .= "({$prefix}_namespace=$ns AND {$prefix}_title IN (";
-
- $firstTitle = true;
- foreach( $dbkeys as $dbkey => $unused ) {
- if ( $firstTitle ) {
- $firstTitle = false;
- } else {
- $sql .= ',';
- }
- $sql .= $db->addQuotes( $dbkey );
- }
- $sql .= '))';
- }
- }
- if ( $first && $firstTitle ) {
- # No titles added
- return false;
- } else {
- return $sql;
- }
+ public function constructSet( $prefix, $db ) {
+ return $db->makeWhereFrom2d( $this->data, "{$prefix}_namespace", "{$prefix}_title" );
}
}
diff --git a/includes/LinkCache.php b/includes/LinkCache.php
index 8d035763..dce34592 100644
--- a/includes/LinkCache.php
+++ b/includes/LinkCache.php
@@ -48,8 +48,8 @@ class LinkCache {
/**
* Get a field of a title object from cache.
* If this link is not good, it will return NULL.
- * @param Title $title
- * @param string $field ('length','redirect')
+ * @param $title Title
+ * @param $field String: ('length','redirect','revision')
* @return mixed
*/
public function getGoodLinkFieldObj( $title, $field ) {
@@ -67,17 +67,20 @@ class LinkCache {
/**
* Add a link for the title to the link cache
- * @param int $id
- * @param Title $title
- * @param int $len
- * @param int $redir
+ *
+ * @param $id Integer: page's ID
+ * @param $title Title object
+ * @param $len Integer: text's length
+ * @param $redir Integer: whether the page is a redirect
+ * @param $revision Integer: latest revision's ID
*/
- public function addGoodLinkObj( $id, $title, $len = -1, $redir = null ) {
+ public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false ) {
$dbkey = $title->getPrefixedDbKey();
$this->mGoodLinks[$dbkey] = intval( $id );
$this->mGoodLinkFields[$dbkey] = array(
'length' => intval( $len ),
- 'redirect' => intval( $redir ) );
+ 'redirect' => intval( $redir ),
+ 'revision' => intval( $revision ) );
}
public function addBadLinkObj( $title ) {
@@ -109,15 +112,14 @@ class LinkCache {
/**
* Add a title to the link cache, return the page_id or zero if non-existent
+ *
* @param $title String: title to add
- * @param $len int, page size
- * @param $redir bool, is redirect?
- * @return integer
+ * @return Integer
*/
- public function addLink( $title, $len = -1, $redir = null ) {
+ public function addLink( $title ) {
$nt = Title::newFromDBkey( $title );
if( $nt ) {
- return $this->addLinkObj( $nt, $len, $redir );
+ return $this->addLinkObj( $nt );
} else {
return 0;
}
@@ -125,13 +127,12 @@ class LinkCache {
/**
* Add a title to the link cache, return the page_id or zero if non-existent
- * @param $nt Title to add.
- * @param $len int, page size
- * @param $redir bool, is redirect?
- * @return integer
+ *
+ * @param $nt Title object to add
+ * @return Integer
*/
- public function addLinkObj( &$nt, $len = -1, $redirect = null ) {
- global $wgAntiLockFlags, $wgProfiler;
+ public function addLinkObj( $nt ) {
+ global $wgAntiLockFlags;
wfProfileIn( __METHOD__ );
$key = $nt->getPrefixedDBkey();
@@ -164,7 +165,7 @@ class LinkCache {
}
$s = $db->selectRow( 'page',
- array( 'page_id', 'page_len', 'page_is_redirect' ),
+ array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
__METHOD__, $options );
# Set fields...
@@ -172,15 +173,18 @@ class LinkCache {
$id = intval( $s->page_id );
$len = intval( $s->page_len );
$redirect = intval( $s->page_is_redirect );
+ $revision = intval( $s->page_latest );
} else {
+ $id = 0;
$len = -1;
$redirect = 0;
+ $revision = 0;
}
if ( $id == 0 ) {
$this->addBadLinkObj( $nt );
} else {
- $this->addGoodLinkObj( $id, $nt, $len, $redirect );
+ $this->addGoodLinkObj( $id, $nt, $len, $redirect, $revision );
}
wfProfileOut( __METHOD__ );
return $id;
diff --git a/includes/Linker.php b/includes/Linker.php
index fe193011..e2193f97 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -21,7 +21,7 @@ class Linker {
* Get the appropriate HTML attributes to add to the "a" element of an ex-
* ternal link, as created by [wikisyntax].
*
- * @param string $class The contents of the class attribute; if an empty
+ * @param $class String: the contents of the class attribute; if an empty
* string is passed, which is the default value, defaults to 'external'.
*/
function getExternalLinkAttributes( $class = 'external' ) {
@@ -32,10 +32,10 @@ class Linker {
* Get the appropriate HTML attributes to add to the "a" element of an in-
* terwiki link.
*
- * @param string $title The title text for the link, URL-encoded (???) but
+ * @param $title String: the title text for the link, URL-encoded (???) but
* not HTML-escaped
- * @param string $unused Unused
- * @param string $class The contents of the class attribute; if an empty
+ * @param $unused String: unused
+ * @param $class String: the contents of the class attribute; if an empty
* string is passed, which is the default value, defaults to 'external'.
*/
function getInterwikiLinkAttributes( $title, $unused = null, $class = 'external' ) {
@@ -54,12 +54,12 @@ class Linker {
* Get the appropriate HTML attributes to add to the "a" element of an in-
* ternal link.
*
- * @param string $title The title text for the link, URL-encoded (???) but
+ * @param $title String: the title text for the link, URL-encoded (???) but
* not HTML-escaped
- * @param string $unused Unused
- * @param string $class The contents of the class attribute, default none
+ * @param $unused String: unused
+ * @param $class String: the contents of the class attribute, default none
*/
- function getInternalLinkAttributes( $title, $unused = null, $class='' ) {
+ function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
$title = urldecode( $title );
$title = str_replace( '_', ' ', $title );
return $this->getLinkAttributesInternal( $title, $class );
@@ -69,14 +69,14 @@ class Linker {
* Get the appropriate HTML attributes to add to the "a" element of an in-
* ternal link, given the Title object for the page we want to link to.
*
- * @param Title $nt The Title object
- * @param string $unused Unused
- * @param string $class The contents of the class attribute, default none
- * @param mixed $title Optional (unescaped) string to use in the title
+ * @param $nt The Title object
+ * @param $unused String: unused
+ * @param $class String: the contents of the class attribute, default none
+ * @param $title Mixed: optional (unescaped) string to use in the title
* attribute; if false, default to the name of the page we're linking to
*/
function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) {
- if( $title === false ) {
+ if ( $title === false ) {
$title = $nt->getPrefixedText();
}
return $this->getLinkAttributesInternal( $title, $class );
@@ -92,7 +92,7 @@ class Linker {
if ( $class != '' ) {
$r .= " class=\"$class\"";
}
- if ( $title != '') {
+ if ( $title != '' ) {
$r .= " title=\"$title\"";
}
return $r;
@@ -101,9 +101,9 @@ class Linker {
/**
* Return the CSS colour of a known link
*
- * @param Title $t
- * @param integer $threshold user defined threshold
- * @return string CSS class
+ * @param $t Title object
+ * @param $threshold Integer: user defined threshold
+ * @return String: CSS class
*/
function getLinkColour( $t, $threshold ) {
$colour = '';
@@ -112,7 +112,7 @@ class Linker {
$colour = 'mw-redirect';
} elseif ( $threshold > 0 &&
$t->exists() && $t->getLength() < $threshold &&
- MWNamespace::isContent( $t->getNamespace() ) ) {
+ $t->isContentPage() ) {
# Page is a stub
$colour = 'stub';
}
@@ -158,13 +158,14 @@ class Linker {
*/
public function link( $target, $text = null, $customAttribs = array(), $query = array(), $options = array() ) {
wfProfileIn( __METHOD__ );
- if( !$target instanceof Title ) {
+ if ( !$target instanceof Title ) {
+ wfProfileOut( __METHOD__ );
return "<!-- ERROR -->$text";
}
$options = (array)$options;
$ret = null;
- if( !wfRunHooks( 'LinkBegin', array( $this, $target, &$text,
+ if ( !wfRunHooks( 'LinkBegin', array( $this, $target, &$text,
&$customAttribs, &$query, &$options, &$ret ) ) ) {
wfProfileOut( __METHOD__ );
return $ret;
@@ -175,24 +176,24 @@ class Linker {
# 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( $target->isKnown() ) {
- $options []= 'known';
+ if ( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) {
+ if ( $target->isKnown() ) {
+ $options[] = 'known';
} else {
- $options []= 'broken';
+ $options[] = 'broken';
}
}
wfProfileOut( __METHOD__ . '-checkPageExistence' );
$oldquery = array();
- if( in_array( "forcearticlepath", $options ) && $query ){
+ if ( in_array( "forcearticlepath", $options ) && $query ) {
$oldquery = $query;
$query = array();
}
# Note: we want the href attribute first, for prettiness.
$attribs = array( 'href' => $this->linkUrl( $target, $query, $options ) );
- if( in_array( 'forcearticlepath', $options ) && $oldquery ){
+ if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
$attribs['href'] = wfAppendQuery( $attribs['href'], wfArrayToCgi( $oldquery ) );
}
@@ -200,12 +201,12 @@ class Linker {
$attribs,
$this->linkAttribs( $target, $customAttribs, $options )
);
- if( is_null( $text ) ) {
+ if ( is_null( $text ) ) {
$text = $this->linkText( $target );
}
$ret = null;
- if( wfRunHooks( 'LinkEnd', array( $this, $target, $options, &$text, &$attribs, &$ret ) ) ) {
+ if ( wfRunHooks( 'LinkEnd', array( $this, $target, $options, &$text, &$attribs, &$ret ) ) ) {
$ret = Html::rawElement( 'a', $attribs, $text );
}
@@ -216,7 +217,7 @@ class Linker {
/**
* Identical to link(), except $options defaults to 'known'.
*/
- public function linkKnown( $target, $text = null, $customAttribs = array(), $query = array(), $options = array('known','noclasses') ) {
+ public function linkKnown( $target, $text = null, $customAttribs = array(), $query = array(), $options = array( 'known', 'noclasses' ) ) {
return $this->link( $target, $text, $customAttribs, $query, $options );
}
@@ -227,7 +228,7 @@ class Linker {
wfProfileIn( __METHOD__ );
# We don't want to include fragments for broken links, because they
# generally make no sense.
- if( in_array( 'broken', $options ) and $target->mFragment !== '' ) {
+ if ( in_array( 'broken', $options ) and $target->mFragment !== '' ) {
$target = clone $target;
$target->mFragment = '';
}
@@ -235,7 +236,7 @@ class Linker {
# If it's a broken link, add the appropriate query pieces, unless
# there's already an action specified, or unless 'edit' makes no sense
# (i.e., for a nonexistent special page).
- if( in_array( 'broken', $options ) and empty( $query['action'] )
+ if ( in_array( 'broken', $options ) and empty( $query['action'] )
and $target->getNamespace() != NS_SPECIAL ) {
$query['action'] = 'edit';
$query['redlink'] = '1';
@@ -253,40 +254,36 @@ class Linker {
global $wgUser;
$defaults = array();
- if( !in_array( 'noclasses', $options ) ) {
+ if ( !in_array( 'noclasses', $options ) ) {
wfProfileIn( __METHOD__ . '-getClasses' );
# Now build the classes.
$classes = array();
- if( in_array( 'broken', $options ) ) {
+ if ( in_array( 'broken', $options ) ) {
$classes[] = 'new';
}
- if( $target->isExternal() ) {
+ if ( $target->isExternal() ) {
$classes[] = 'extiw';
}
- # Note that redirects never count as stubs here.
- if ( !in_array( 'broken', $options ) && $target->isRedirect() ) {
- $classes[] = 'mw-redirect';
- } elseif( $target->isContentPage() ) {
- # Check for stub.
- $threshold = $wgUser->getOption( 'stubthreshold' );
- if( $threshold > 0 and $target->exists() and $target->getLength() < $threshold ) {
- $classes[] = 'stub';
+ if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387)
+ $colour = $this->getLinkColour( $target, $wgUser->getStubThreshold() );
+ if ( $colour !== '' ) {
+ $classes[] = $colour; # mw-redirect or stub
}
}
- if( $classes != array() ) {
+ if ( $classes != array() ) {
$defaults['class'] = implode( ' ', $classes );
}
wfProfileOut( __METHOD__ . '-getClasses' );
}
# Get a default title attribute.
- if( $target->getPrefixedText() == '' ) {
+ if ( $target->getPrefixedText() == '' ) {
# A link like [[#Foo]]. This used to mean an empty title
# attribute, but that's silly. Just don't output a title.
- } elseif( in_array( 'known', $options ) ) {
+ } elseif ( in_array( 'known', $options ) ) {
$defaults['title'] = $target->getPrefixedText();
} else {
$defaults['title'] = wfMsg( 'red-link-title', $target->getPrefixedText() );
@@ -296,10 +293,10 @@ class Linker {
# over that, deleting all "false" attributes.
$ret = array();
$merged = Sanitizer::mergeAttributes( $defaults, $attribs );
- foreach( $merged as $key => $val ) {
+ 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' and $val !== false ) {
$ret[$key] = $val;
}
}
@@ -312,13 +309,13 @@ class Linker {
*/
private function linkText( $target ) {
# We might be passed a non-Title by make*LinkObj(). Fail gracefully.
- if( !$target instanceof Title ) {
+ if ( !$target instanceof Title ) {
return '';
}
# If the target is just a fragment, with no title, we return the frag-
# ment text. Otherwise, we return the title text itself.
- if( $target->getPrefixedText() === '' and $target->getFragment() !== '' ) {
+ if ( $target->getPrefixedText() === '' and $target->getFragment() !== '' ) {
return htmlspecialchars( $target->getFragment() );
}
return htmlspecialchars( $target->getPrefixedText() );
@@ -328,17 +325,20 @@ class Linker {
* Generate either a normal exists-style link or a stub link, depending
* on the given page size.
*
- * @param $size Integer
- * @param $nt Title object.
- * @param $text String
- * @param $query String
- * @param $trail String
- * @param $prefix String
- * @return string HTML of link
+ * @param $size Integer
+ * @param $nt Title object.
+ * @param $text String
+ * @param $query String
+ * @param $trail String
+ * @param $prefix String
+ * @return string HTML of link
+ * @deprecated
*/
function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
global $wgUser;
- $threshold = intval( $wgUser->getOption( 'stubthreshold' ) );
+ wfDeprecated( __METHOD__ );
+
+ $threshold = $wgUser->getStubThreshold();
$colour = ( $size < $threshold ) ? 'stub' : '';
// FIXME: replace deprecated makeColouredLinkObj by link()
return $this->makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
@@ -360,7 +360,9 @@ class Linker {
function normaliseSpecialPage( Title $title ) {
if ( $title->getNamespace() == NS_SPECIAL ) {
list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
- if ( !$name ) return $title;
+ if ( !$name ) {
+ return $title;
+ }
$ret = SpecialPage::getTitleFor( $name, $subpage );
$ret->mFragment = $title->getFragment();
return $ret;
@@ -392,9 +394,9 @@ class Linker {
$alt = $this->fnamePart( $url );
}
$img = '';
- $success = wfRunHooks('LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
- if(!$success) {
- wfDebug("Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true);
+ $success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
+ if ( !$success ) {
+ wfDebug( "Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true );
return $img;
}
return Html::element( 'img',
@@ -407,10 +409,9 @@ class Linker {
* Given parameters derived from [[Image:Foo|options...]], generate the
* HTML that that syntax inserts in the page.
*
- * @param Title $title Title object
- * @param File $file File object, or false if it doesn't exist
- *
- * @param array $frameParams Associative array of parameters external to the media handler.
+ * @param $title Title object
+ * @param $file File object, or false if it doesn't exist
+ * @param $frameParams Array: associative array of parameters external to the media handler.
* Boolean parameters are indicated by presence or absence, the value is arbitrary and
* will often be false.
* thumbnail If present, downscale and frame
@@ -427,24 +428,25 @@ class Linker {
* caption HTML for image caption.
* link-url URL to link to
* link-title Title object to link to
+ * link-target Value for the target attribue, only with link-url
* no-link Boolean, suppress description link
*
- * @param array $handlerParams Associative array of media handler parameters, to be passed
+ * @param $handlerParams Array: associative array of media handler parameters, to be passed
* to transform(). Typical keys are "width" and "page".
- * @param string $time, timestamp of the file, set as false for current
- * @param string $query, query params for desc url
- * @return string HTML for an image, with links, wrappers, etc.
+ * @param $time String: timestamp of the file, set as false for current
+ * @param $query String: query params for desc url
+ * @param $widthOption: Used by the parser to remember the user preference thumbnailsize
+ * @return String: HTML for an image, with links, wrappers, etc.
*/
- function makeImageLink2( Title $title, $file, $frameParams = array(), $handlerParams = array(), $time = false, $query = "" ) {
+ function makeImageLink2( Title $title, $file, $frameParams = array(), $handlerParams = array(), $time = false, $query = "", $widthOption = null ) {
$res = null;
- if( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$this, &$title,
+ if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$this, &$title,
&$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
return $res;
}
- global $wgContLang, $wgUser, $wgThumbLimits, $wgThumbUpright;
if ( $file && !$file->allowInlineDisplay() ) {
- wfDebug( __METHOD__.': '.$title->getPrefixedDBkey()." does not allow inline display\n" );
+ wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display\n" );
return $this->link( $title );
}
@@ -468,29 +470,32 @@ class Linker {
if ( $file && !isset( $hp['width'] ) ) {
$hp['width'] = $file->getWidth( $page );
- if( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
- $wopt = $wgUser->getOption( 'thumbsize' );
-
- if( !isset( $wgThumbLimits[$wopt] ) ) {
- $wopt = User::getDefaultOption( 'thumbsize' );
+ if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
+ global $wgThumbLimits, $wgThumbUpright;
+ if ( !isset( $widthOption ) || !isset( $wgThumbLimits[$widthOption] ) ) {
+ $widthOption = User::getDefaultOption( 'thumbsize' );
}
// Reduce width for upright images when parameter 'upright' is used
if ( isset( $fp['upright'] ) && $fp['upright'] == 0 ) {
$fp['upright'] = $wgThumbUpright;
}
- // Use width which is smaller: real image width or user preference width
// For caching health: If width scaled down due to upright parameter, round to full __0 pixel to avoid the creation of a lot of odd thumbs
$prefWidth = isset( $fp['upright'] ) ?
- round( $wgThumbLimits[$wopt] * $fp['upright'], -1 ) :
- $wgThumbLimits[$wopt];
- if ( $hp['width'] <= 0 || $prefWidth < $hp['width'] ) {
+ round( $wgThumbLimits[$widthOption] * $fp['upright'], -1 ) :
+ $wgThumbLimits[$widthOption];
+
+ // Use width which is smaller: real image width or user preference width
+ // Unless image is scalable vector.
+ if ( !isset( $hp['height'] ) && ( $hp['width'] <= 0 ||
+ $prefWidth < $hp['width'] || $file->isVectorized() ) ) {
$hp['width'] = $prefWidth;
}
}
}
if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) ) {
+ global $wgContLang;
# Create a thumbnail. Alignment depends on language
# writing direction, # right aligned for left-to-right-
# languages ("Western languages"), left-aligned
@@ -501,7 +506,7 @@ class Linker {
if ( $fp['align'] == '' ) {
$fp['align'] = $wgContLang->alignEnd();
}
- return $prefix.$this->makeThumbLink2( $title, $file, $fp, $hp, $time, $query ).$postfix;
+ return $prefix . $this->makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix;
}
if ( $file && isset( $fp['frameless'] ) ) {
@@ -513,7 +518,7 @@ class Linker {
}
}
- if ( $file && $hp['width'] ) {
+ if ( $file && isset( $hp['width'] ) ) {
# Create a resized image, without the additional thumbnail features
$thumb = $file->transform( $hp );
} else {
@@ -521,38 +526,59 @@ class Linker {
}
if ( !$thumb ) {
- $s = $this->makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time==true );
+ $s = $this->makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
} else {
$params = array(
'alt' => $fp['alt'],
'title' => $fp['title'],
'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false ,
'img-class' => isset( $fp['border'] ) ? 'thumbborder' : false );
- if ( !empty( $fp['link-url'] ) ) {
- $params['custom-url-link'] = $fp['link-url'];
- } elseif ( !empty( $fp['link-title'] ) ) {
- $params['custom-title-link'] = $fp['link-title'];
- } elseif ( !empty( $fp['no-link'] ) ) {
- // No link
- } else {
- $params['desc-link'] = true;
- $params['desc-query'] = $query;
- }
+ $params = $this->getImageLinkMTOParams( $fp, $query ) + $params;
$s = $thumb->toHtml( $params );
}
if ( $fp['align'] != '' ) {
$s = "<div class=\"float{$fp['align']}\">{$s}</div>";
}
- return str_replace("\n", ' ',$prefix.$s.$postfix);
+ return str_replace( "\n", ' ', $prefix . $s . $postfix );
}
/**
- * Make HTML for a thumbnail including image, border and caption
- * @param Title $title
- * @param File $file File object or false if it doesn't exist
+ * Get the link parameters for MediaTransformOutput::toHtml() from given
+ * frame parameters supplied by the Parser.
+ * @param $frameParams The frame parameters
+ * @param $query An optional query string to add to description page links
*/
- function makeThumbLinkObj( Title $title, $file, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manualthumb = "" ) {
+ function getImageLinkMTOParams( $frameParams, $query = '' ) {
+ $mtoParams = array();
+ if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
+ $mtoParams['custom-url-link'] = $frameParams['link-url'];
+ if ( isset( $frameParams['link-target'] ) ) {
+ $mtoParams['custom-target-link'] = $frameParams['link-target'];
+ }
+ } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
+ $mtoParams['custom-title-link'] = $this->normaliseSpecialPage( $frameParams['link-title'] );
+ } elseif ( !empty( $frameParams['no-link'] ) ) {
+ // No link
+ } else {
+ $mtoParams['desc-link'] = true;
+ $mtoParams['desc-query'] = $query;
+ }
+ return $mtoParams;
+ }
+
+ /**
+ * Make HTML for a thumbnail including image, border and caption
+ * @param $title Title object
+ * @param $file File object or false if it doesn't exist
+ * @param $label String
+ * @param $alt String
+ * @param $align String
+ * @param $params Array
+ * @param $framed Boolean
+ * @param $manualthumb String
+ */
+ function makeThumbLinkObj( Title $title, $file, $label = '', $alt, $align = 'right', $params = array(), $framed = false , $manualthumb = "" ) {
$frameParams = array(
'alt' => $alt,
'caption' => $label,
@@ -564,7 +590,7 @@ class Linker {
}
function makeThumbLink2( Title $title, $file, $frameParams = array(), $handlerParams = array(), $time = false, $query = "" ) {
- global $wgStylePath, $wgContLang;
+ global $wgStylePath;
$exists = $file && $file->exists();
# Shortcuts
@@ -589,17 +615,17 @@ class Linker {
if ( isset( $fp['manualthumb'] ) ) {
# Use manually specified thumbnail
$manual_title = Title::makeTitleSafe( NS_FILE, $fp['manualthumb'] );
- if( $manual_title ) {
+ if ( $manual_title ) {
$manual_img = wfFindFile( $manual_title );
if ( $manual_img ) {
- $thumb = $manual_img->getUnscaledThumb();
+ $thumb = $manual_img->getUnscaledThumb( $hp );
} else {
$exists = false;
}
}
} elseif ( isset( $fp['framed'] ) ) {
// Use image dimensions, don't scale
- $thumb = $file->getUnscaledThumb( $page );
+ $thumb = $file->getUnscaledThumb( $hp );
} else {
# Do not present an image bigger than the source, for bitmap-style images
# This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
@@ -621,104 +647,102 @@ class Linker {
# So we don't need to pass it here in $query. However, the URL for the
# zoom icon still needs it, so we make a unique query for it. See bug 14771
$url = $title->getLocalURL( $query );
- if( $page ) {
+ if ( $page ) {
$url = wfAppendQuery( $url, 'page=' . urlencode( $page ) );
}
- $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
-
$s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
- if( !$exists ) {
- $s .= $this->makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time==true );
- $zoomicon = '';
+ if ( !$exists ) {
+ $s .= $this->makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
+ $zoomIcon = '';
} elseif ( !$thumb ) {
$s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
- $zoomicon = '';
+ $zoomIcon = '';
} else {
- $s .= $thumb->toHtml( array(
+ $params = array(
'alt' => $fp['alt'],
'title' => $fp['title'],
- 'img-class' => 'thumbimage',
- 'desc-link' => true,
- 'desc-query' => $query ) );
+ 'img-class' => 'thumbimage' );
+ $params = $this->getImageLinkMTOParams( $fp, $query ) + $params;
+ $s .= $thumb->toHtml( $params );
if ( isset( $fp['framed'] ) ) {
- $zoomicon="";
+ $zoomIcon = "";
} else {
- $zoomicon = '<div class="magnify">'.
- '<a href="'.$url.'" class="internal" title="'.$more.'">'.
- '<img src="'.$wgStylePath.'/common/images/magnify-clip.png" ' .
- 'width="15" height="11" alt="" /></a></div>';
+ $zoomIcon = '<div class="magnify">' .
+ '<a href="' . htmlspecialchars( $url ) . '" class="internal" ' .
+ 'title="' . htmlspecialchars( wfMsg( 'thumbnail-more' ) ) . '">' .
+ '<img src="' . htmlspecialchars( $wgStylePath ) . '/common/images/magnify-clip.png" ' .
+ 'width="15" height="11" alt="" /></a></div>';
}
}
- $s .= ' <div class="thumbcaption">'.$zoomicon.$fp['caption']."</div></div></div>";
- return str_replace("\n", ' ', $s);
+ $s .= ' <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>";
+ return str_replace( "\n", ' ', $s );
}
/**
* Make a "broken" link to an image
*
- * @param Title $title Image title
- * @param string $text Link label
- * @param string $query Query string
- * @param string $trail Link trail
- * @param string $prefix Link prefix
- * @param bool $time, a file of a certain timestamp was requested
- * @return string
+ * @param $title Title object
+ * @param $text String: link label
+ * @param $query String: query string
+ * @param $trail String: link trail
+ * @param $prefix String: link prefix
+ * @param $time Boolean: a file of a certain timestamp was requested
+ * @return String
*/
public function makeBrokenImageLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '', $time = false ) {
- global $wgEnableUploads, $wgUploadNavigationUrl;
- if( $title instanceof Title ) {
+ global $wgEnableUploads, $wgUploadMissingFileUrl;
+ if ( $title instanceof Title ) {
wfProfileIn( __METHOD__ );
$currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
- if( ( $wgUploadNavigationUrl || $wgEnableUploads ) && !$currentExists ) {
- if( $text == '' )
- $text = htmlspecialchars( $title->getPrefixedText() );
+ list( $inside, $trail ) = self::splitTrail( $trail );
+ if ( $text == '' )
+ $text = htmlspecialchars( $title->getPrefixedText() );
+
+ if ( ( $wgUploadMissingFileUrl || $wgEnableUploads ) && !$currentExists ) {
$redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
- if( $redir ) {
+
+ if ( $redir ) {
wfProfileOut( __METHOD__ );
- return $this->makeKnownLinkObj( $title, $text, $query, $trail, $prefix );
+ return $this->linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
}
-
- $href = $this->getUploadUrl( $title, $query );
-
- list( $inside, $trail ) = self::splitTrail( $trail );
+ $href = $this->getUploadUrl( $title, $query );
wfProfileOut( __METHOD__ );
- return Html::element( 'a', array(
- 'href' => $href,
- 'class' => 'new',
- 'title' => $title->getPrefixedText()
- ), $prefix . $text . $inside ) . $trail;
+ return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
+ htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
+ htmlspecialchars( $prefix . $text . $inside, ENT_NOQUOTES ) . '</a>' . $trail;
} else {
wfProfileOut( __METHOD__ );
- return $this->makeKnownLinkObj( $title, $text, $query, $trail, $prefix );
+ return $this->linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
}
} else {
+ wfProfileOut( __METHOD__ );
return "<!-- ERROR -->{$prefix}{$text}{$trail}";
}
}
-
+
/**
* Get the URL to upload a certain file
- *
- * @param $destFile Title Title of the file to upload
- * @param $query string Urlencoded query string to prepend
- * @return string Urlencoded URL
+ *
+ * @param $destFile Title object of the file to upload
+ * @param $query String: urlencoded query string to prepend
+ * @return String: urlencoded URL
*/
protected function getUploadUrl( $destFile, $query = '' ) {
- global $wgUploadNavigationUrl;
+ global $wgUploadMissingFileUrl;
$q = 'wpDestFile=' . $destFile->getPartialUrl();
- if( $query != '' )
+ if ( $query != '' )
$q .= '&' . $query;
- if( $wgUploadNavigationUrl ) {
- return wfAppendQuery( $wgUploadNavigationUrl, $q );
+ if ( $wgUploadMissingFileUrl ) {
+ return wfAppendQuery( $wgUploadMissingFileUrl, $q );
} else {
$upload = SpecialPage::getTitleFor( 'Upload' );
return $upload->getLocalUrl( $q );
- }
+ }
}
/**
@@ -727,26 +751,25 @@ class Linker {
* @param $title Title object.
* @param $text String: pre-sanitized HTML
* @param $time string: time image was created
- * @return string HTML
+ * @return String: HTML
*
- * @public
* @todo Handle invalid or missing images better.
*/
- function makeMediaLinkObj( $title, $text = '', $time = false ) {
- if( is_null( $title ) ) {
- ### HOTFIX. Instead of breaking, return empty string.
+ public function makeMediaLinkObj( $title, $text = '', $time = false ) {
+ if ( is_null( $title ) ) {
+ # # # HOTFIX. Instead of breaking, return empty string.
return $text;
} else {
$img = wfFindFile( $title, array( 'time' => $time ) );
- if( $img ) {
+ if ( $img ) {
$url = $img->getURL();
$class = 'internal';
} else {
$url = $this->getUploadUrl( $title );
$class = 'new';
}
- $alt = htmlspecialchars( $title->getText() );
- if( $text == '' ) {
+ $alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
+ if ( $text == '' ) {
$text = $alt;
}
$u = htmlspecialchars( $url );
@@ -760,21 +783,18 @@ class Linker {
* Usage example: $skin->specialLink( 'recentchanges' )
*/
function specialLink( $name, $key = '' ) {
- global $wgContLang;
-
if ( $key == '' ) { $key = strtolower( $name ); }
- $pn = $wgContLang->ucfirst( $name );
- return $this->makeKnownLink( $wgContLang->specialPage( $pn ),
- wfMsg( $key ) );
+
+ return $this->linkKnown( SpecialPage::getTitleFor( $name ) , wfMsg( $key ) );
}
/**
* Make an external link
- * @param String $url URL to link to
- * @param String $text text of link
- * @param boolean $escape Do we escape the link text?
- * @param String $linktype Type of external link. Gets added to the classes
- * @param array $attribs Array of extra attributes to <a>
+ * @param $url String: URL to link to
+ * @param $text String: text of link
+ * @param $escape Boolean: do we escape the link text?
+ * @param $linktype String: type of external link. Gets added to the classes
+ * @param $attribs Array of extra attributes to <a>
*
* @todo FIXME: This is a really crappy implementation. $linktype and
* 'external' are mashed into the class attrib for the link (which is made
@@ -785,35 +805,39 @@ class Linker {
* hook play with them, *then* expand it all at once.
*/
function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
- if ( isset( $attribs[ 'class' ] ) ) $class = $attribs[ 'class' ]; # yet another hack :(
- else $class = 'external ' . $linktype;
+ if ( isset( $attribs['class'] ) ) {
+ # yet another hack :(
+ $class = $attribs['class'];
+ } else {
+ $class = "external $linktype";
+ }
$attribsText = $this->getExternalLinkAttributes( $class );
$url = htmlspecialchars( $url );
- if( $escape ) {
+ if ( $escape ) {
$text = htmlspecialchars( $text );
}
$link = '';
- $success = wfRunHooks('LinkerMakeExternalLink', array( &$url, &$text, &$link, &$attribs, $linktype ) );
- if(!$success) {
- wfDebug("Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true);
+ $success = wfRunHooks( 'LinkerMakeExternalLink', array( &$url, &$text, &$link, &$attribs, $linktype ) );
+ if ( !$success ) {
+ wfDebug( "Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true );
return $link;
}
if ( $attribs ) {
$attribsText .= Html::expandAttributes( $attribs );
}
- return '<a href="'.$url.'"'.$attribsText.'>'.$text.'</a>';
+ return '<a href="' . $url . '"' . $attribsText . '>' . $text . '</a>';
}
/**
* Make user link (or user contributions for unregistered users)
* @param $userId Integer: user id in database.
* @param $userText String: user name in database
- * @return string HTML fragment
+ * @return String: HTML fragment
* @private
*/
function userLink( $userId, $userText ) {
- if( $userId == 0 ) {
+ if ( $userId == 0 ) {
$page = SpecialPage::getTitleFor( 'Contributions', $userText );
} else {
$page = Title::makeTitle( NS_USER, $userText );
@@ -824,28 +848,29 @@ class Linker {
/**
* Generate standard user tool links (talk, contributions, block link, etc.)
*
- * @param int $userId User identifier
- * @param string $userText User name or IP address
- * @param bool $redContribsWhenNoEdits Should the contributions link be red if the user has no edits?
- * @param int $flags Customisation flags (e.g. self::TOOL_LINKS_NOBLOCK)
- * @param int $edits, user edit count (optional, for performance)
- * @return string
- */
- public function userToolLinks( $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits=null ) {
+ * @param $userId Integer: user identifier
+ * @param $userText String: user name or IP address
+ * @param $redContribsWhenNoEdits Boolean: should the contributions link be
+ * red if the user has no edits?
+ * @param $flags Integer: customisation flags (e.g. self::TOOL_LINKS_NOBLOCK)
+ * @param $edits Integer: user edit count (optional, for performance)
+ * @return String: HTML fragment
+ */
+ public function userToolLinks( $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null ) {
global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans, $wgLang;
$talkable = !( $wgDisableAnonTalk && 0 == $userId );
$blockable = ( $wgSysopUserBans || 0 == $userId ) && !$flags & self::TOOL_LINKS_NOBLOCK;
$items = array();
- if( $talkable ) {
+ if ( $talkable ) {
$items[] = $this->userTalkLink( $userId, $userText );
}
- if( $userId ) {
+ if ( $userId ) {
// check if the user has an edit
$attribs = array();
- if( $redContribsWhenNoEdits ) {
- $count = !is_null($edits) ? $edits : User::edits( $userId );
- if( $count == 0 ) {
+ if ( $redContribsWhenNoEdits ) {
+ $count = !is_null( $edits ) ? $edits : User::edits( $userId );
+ if ( $count == 0 ) {
$attribs['class'] = 'new';
}
}
@@ -853,11 +878,11 @@ class Linker {
$items[] = $this->link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs );
}
- if( $blockable && $wgUser->isAllowed( 'block' ) ) {
+ if ( $blockable && $wgUser->isAllowed( 'block' ) ) {
$items[] = $this->blockLink( $userId, $userText );
}
- if( $items ) {
+ if ( $items ) {
return ' <span class="mw-usertoollinks">(' . $wgLang->pipeList( $items ) . ')</span>';
} else {
return '';
@@ -866,11 +891,11 @@ class Linker {
/**
* Alias for userToolLinks( $userId, $userText, true );
- * @param int $userId User identifier
- * @param string $userText User name or IP address
- * @param int $edits, user edit count (optional, for performance)
+ * @param $userId Integer: user identifier
+ * @param $userText String: user name or IP address
+ * @param $edits Integer: user edit count (optional, for performance)
*/
- public function userToolLinksRedContribs( $userId, $userText, $edits=null ) {
+ public function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
return $this->userToolLinks( $userId, $userText, true, 0, $edits );
}
@@ -878,7 +903,7 @@ class Linker {
/**
* @param $userId Integer: user id in database.
* @param $userText String: user name in database.
- * @return string HTML fragment with user talk link
+ * @return String: HTML fragment with user talk link
* @private
*/
function userTalkLink( $userId, $userText ) {
@@ -890,7 +915,7 @@ class Linker {
/**
* @param $userId Integer: userid
* @param $userText String: user name in database.
- * @return string HTML fragment with block link
+ * @return String: HTML fragment with block link
* @private
*/
function blockLink( $userId, $userText ) {
@@ -902,19 +927,19 @@ class Linker {
/**
* Generate a user link if the current user is allowed to view it
* @param $rev Revision object.
- * @param $isPublic, bool, show only if all users can see it
- * @return string HTML
+ * @param $isPublic Boolean: show only if all users can see it
+ * @return String: HTML fragment
*/
function revUserLink( $rev, $isPublic = false ) {
- if( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
+ if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
$link = wfMsgHtml( 'rev-deleted-user' );
- } else if( $rev->userCan( Revision::DELETED_USER ) ) {
+ } else if ( $rev->userCan( Revision::DELETED_USER ) ) {
$link = $this->userLink( $rev->getUser( Revision::FOR_THIS_USER ),
$rev->getUserText( Revision::FOR_THIS_USER ) );
} else {
$link = wfMsgHtml( 'rev-deleted-user' );
}
- if( $rev->isDeleted( Revision::DELETED_USER ) ) {
+ if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
return '<span class="history-deleted">' . $link . '</span>';
}
return $link;
@@ -923,13 +948,13 @@ class Linker {
/**
* Generate a user tool link cluster if the current user is allowed to view it
* @param $rev Revision object.
- * @param $isPublic, bool, show only if all users can see it
+ * @param $isPublic Boolean: show only if all users can see it
* @return string HTML
*/
function revUserTools( $rev, $isPublic = false ) {
- if( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
+ if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
$link = wfMsgHtml( 'rev-deleted-user' );
- } else if( $rev->userCan( Revision::DELETED_USER ) ) {
+ } else if ( $rev->userCan( Revision::DELETED_USER ) ) {
$userId = $rev->getUser( Revision::FOR_THIS_USER );
$userText = $rev->getUserText( Revision::FOR_THIS_USER );
$link = $this->userLink( $userId, $userText ) .
@@ -937,7 +962,7 @@ class Linker {
} else {
$link = wfMsgHtml( 'rev-deleted-user' );
}
- if( $rev->isDeleted( Revision::DELETED_USER ) ) {
+ if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
return ' <span class="history-deleted">' . $link . '</span>';
}
return $link;
@@ -955,11 +980,11 @@ class Linker {
* Since you can't set a default parameter for a reference, I've turned it
* temporarily to a value pass. Should be adjusted further. --brion
*
- * @param string $comment
- * @param mixed $title Title object (to generate link to the section in autocomment) or null
- * @param bool $local Whether section links should refer to local page
+ * @param $comment String
+ * @param $title Mixed: Title object (to generate link to the section in autocomment) or null
+ * @param $local Boolean: whether section links should refer to local page
*/
- function formatComment($comment, $title = null, $local = false) {
+ function formatComment( $comment, $title = null, $local = false ) {
wfProfileIn( __METHOD__ );
# Sanitize text a bit:
@@ -982,11 +1007,10 @@ class Linker {
* add a separator where needed and format the comment itself with CSS
* Called by Linker::formatComment.
*
- * @param string $comment Comment text
- * @param object $title An optional title object used to links to sections
- * @return string $comment formatted comment
- *
- * @todo Document the $local parameter.
+ * @param $comment String: comment text
+ * @param $title An optional title object used to links to sections
+ * @param $local Boolean: whether section links should refer to local page
+ * @return String: formatted comment
*/
private function formatAutocomments( $comment, $title = null, $local = false ) {
// Bah!
@@ -1012,14 +1036,13 @@ class Linker {
if ( $title ) {
$section = $auto;
- # Generate a valid anchor name from the section title.
- # Hackish, but should generally work - we strip wiki
- # syntax, including the magic [[: that is used to
- # "link rather than show" in case of images and
- # interlanguage links.
+ # Remove links that a user may have manually put in the autosummary
+ # This could be improved by copying as much of Parser::stripSectionName as desired.
$section = str_replace( '[[:', '', $section );
$section = str_replace( '[[', '', $section );
$section = str_replace( ']]', '', $section );
+
+ $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784
if ( $local ) {
$sectionTitle = Title::newFromText( '#' . $section );
} else {
@@ -1035,11 +1058,11 @@ class Linker {
}
}
$auto = "$link$auto";
- if( $pre ) {
+ if ( $pre ) {
# written summary $presep autocomment (summary /* section */)
$auto = wfMsgExt( 'autocomment-prefix', array( 'escapenoentities', 'content' ) ) . $auto;
}
- if( $post ) {
+ if ( $post ) {
# autocomment $postsep written summary (/* section */ summary)
$auto .= wfMsgExt( 'colon-separator', array( 'escapenoentities', 'content' ) );
}
@@ -1053,8 +1076,10 @@ class Linker {
* is ignored
*
* @todo Fixme: doesn't handle sub-links as in image thumb texts like the main parser
- * @param string $comment Text to format links in
- * @return string
+ * @param $comment String: text to format links in
+ * @param $title An optional title object used to links to sections
+ * @param $local Boolean: whether section links should refer to local page
+ * @return String
*/
public function formatLinksInComment( $comment, $title = null, $local = false ) {
$this->commentContextTitle = $title;
@@ -1077,33 +1102,33 @@ class Linker {
$comment = $match[0];
# fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
- if( strpos( $match[1], '%' ) !== false ) {
- $match[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), urldecode($match[1]) );
+ if ( strpos( $match[1], '%' ) !== false ) {
+ $match[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), urldecode( $match[1] ) );
}
# Handle link renaming [[foo|text]] will show link as "text"
- if( $match[3] != "" ) {
+ if ( $match[3] != "" ) {
$text = $match[3];
} else {
$text = $match[1];
}
$submatch = array();
$thelink = null;
- if( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
+ if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
# Media link; trail not supported.
$linkRegexp = '/\[\[(.*?)\]\]/';
$title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
$thelink = $this->makeMediaLinkObj( $title, $text );
} else {
# Other kind of link
- if( preg_match( $wgContLang->linkTrail(), $match[4], $submatch ) ) {
+ if ( preg_match( $wgContLang->linkTrail(), $match[4], $submatch ) ) {
$trail = $submatch[1];
} else {
$trail = "";
}
$linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
- if (isset($match[1][0]) && $match[1][0] == ':')
- $match[1] = substr($match[1], 1);
+ if ( isset( $match[1][0] ) && $match[1][0] == ':' )
+ $match[1] = substr( $match[1], 1 );
list( $inside, $trail ) = Linker::splitTrail( $trail );
$linkText = $text;
@@ -1111,9 +1136,11 @@ class Linker {
$match[1], $linkText );
$target = Title::newFromText( $linkTarget );
- if( $target ) {
- if( $target->getText() == '' && !$this->commentLocal && $this->commentContextTitle ) {
- $newTarget = clone( $this->commentContextTitle );
+ if ( $target ) {
+ if ( $target->getText() == '' && $target->getInterwiki() === ''
+ && !$this->commentLocal && $this->commentContextTitle )
+ {
+ $newTarget = clone ( $this->commentContextTitle );
$newTarget->setFragment( '#' . $target->getFragment() );
$target = $newTarget;
}
@@ -1123,7 +1150,7 @@ class Linker {
) . $trail;
}
}
- if( $thelink ) {
+ if ( $thelink ) {
// If the link is still valid, go ahead and replace it in!
$comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
}
@@ -1145,9 +1172,9 @@ class Linker {
# Some namespaces don't allow subpages,
# so only perform processing if subpages are allowed
- if( $contextTitle && MWNamespace::hasSubpages( $contextTitle->getNamespace() ) ) {
+ if ( $contextTitle && MWNamespace::hasSubpages( $contextTitle->getNamespace() ) ) {
$hash = strpos( $target, '#' );
- if( $hash !== false ) {
+ if ( $hash !== false ) {
$suffix = substr( $target, $hash );
$target = substr( $target, 0, $hash );
} else {
@@ -1156,41 +1183,41 @@ class Linker {
# bug 7425
$target = trim( $target );
# Look at the first character
- if( $target != '' && $target{0} === '/' ) {
+ if ( $target != '' && $target { 0 } === '/' ) {
# / at end means we don't want the slash to be shown
$m = array();
$trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
- if( $trailingSlashes ) {
- $noslash = $target = substr( $target, 1, -strlen($m[0][0]) );
+ if ( $trailingSlashes ) {
+ $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
} else {
$noslash = substr( $target, 1 );
}
- $ret = $contextTitle->getPrefixedText(). '/' . trim($noslash) . $suffix;
- if( $text === '' ) {
+ $ret = $contextTitle->getPrefixedText() . '/' . trim( $noslash ) . $suffix;
+ if ( $text === '' ) {
$text = $target . $suffix;
} # this might be changed for ugliness reasons
} else {
# check for .. subpage backlinks
$dotdotcount = 0;
$nodotdot = $target;
- while( strncmp( $nodotdot, "../", 3 ) == 0 ) {
+ while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
++$dotdotcount;
$nodotdot = substr( $nodotdot, 3 );
}
- if($dotdotcount > 0) {
+ if ( $dotdotcount > 0 ) {
$exploded = explode( '/', $contextTitle->GetPrefixedText() );
- if( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
+ if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
$ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
# / at the end means don't show full path
- if( substr( $nodotdot, -1, 1 ) === '/' ) {
+ if ( substr( $nodotdot, -1, 1 ) === '/' ) {
$nodotdot = substr( $nodotdot, 0, -1 );
- if( $text === '' ) {
+ if ( $text === '' ) {
$text = $nodotdot . $suffix;
}
}
$nodotdot = trim( $nodotdot );
- if( $nodotdot != '' ) {
+ if ( $nodotdot != '' ) {
$ret .= '/' . $nodotdot;
}
$ret .= $suffix;
@@ -1207,9 +1234,9 @@ class Linker {
* Wrap a comment in standard punctuation and formatting if
* it's non-empty, otherwise return empty string.
*
- * @param string $comment
- * @param mixed $title Title object (to generate link to section in autocomment) or null
- * @param bool $local Whether section links should refer to local page
+ * @param $comment String
+ * @param $title Mixed: Title object (to generate link to section in autocomment) or null
+ * @param $local Boolean: whether section links should refer to local page
*
* @return string
*/
@@ -1217,7 +1244,7 @@ class Linker {
// '*' used to be the comment inserted by the software way back
// in antiquity in case none was provided, here for backwards
// compatability, acc. to brion -ævar
- if( $comment == '' || $comment == '*' ) {
+ if ( $comment == '' || $comment == '*' ) {
return '';
} else {
$formatted = $this->formatComment( $comment, $title, $local );
@@ -1229,22 +1256,22 @@ class Linker {
* Wrap and format the given revision's comment block, if the current
* user is allowed to view it.
*
- * @param Revision $rev
- * @param bool $local Whether section links should refer to local page
- * @param $isPublic, show only if all users can see it
- * @return string HTML
+ * @param $rev Revision object
+ * @param $local Boolean: whether section links should refer to local page
+ * @param $isPublic Boolean: show only if all users can see it
+ * @return String: HTML fragment
*/
function revComment( Revision $rev, $local = false, $isPublic = false ) {
- if( $rev->getRawComment() == "" ) return "";
- if( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
+ if ( $rev->getRawComment() == "" ) return "";
+ if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
$block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
- } else if( $rev->userCan( Revision::DELETED_COMMENT ) ) {
+ } else if ( $rev->userCan( Revision::DELETED_COMMENT ) ) {
$block = $this->commentBlock( $rev->getComment( Revision::FOR_THIS_USER ),
$rev->getTitle(), $local );
} else {
$block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
}
- if( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
+ if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
return " <span class=\"history-deleted\">$block</span>";
}
return $block;
@@ -1272,8 +1299,8 @@ class Linker {
/**
* Finish one or more sublevels on the Table of Contents
*/
- function tocUnindent($level) {
- return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level>0 ? $level : 0 );
+ function tocUnindent( $level ) {
+ return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
}
/**
@@ -1296,36 +1323,30 @@ class Linker {
*/
function tocLineEnd() {
return "</li>\n";
- }
+ }
/**
* Wraps the TOC in a table and provides the hide/collapse javascript.
- * @param string $toc html of the Table Of Contents
- * @return string Full html of the TOC
+ *
+ * @param $toc String: html of the Table Of Contents
+ * @param $lang mixed: Language code for the toc title
+ * @return String: full html of the TOC
*/
- function tocList($toc) {
- $title = wfMsgHtml('toc') ;
+ function tocList( $toc, $lang = false ) {
+ $title = wfMsgExt( 'toc', array( 'language' => $lang, 'escape' ) );
return
'<table id="toc" class="toc"><tr><td>'
. '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
. $toc
- # no trailing newline, script should not be wrapped in a
- # paragraph
- . "</ul>\n</td></tr></table>"
- . Html::inlineScript(
- 'if (window.showTocToggle) {'
- . ' var tocShowText = "' . Xml::escapeJsString( wfMsg('showtoc') ) . '";'
- . ' var tocHideText = "' . Xml::escapeJsString( wfMsg('hidetoc') ) . '";'
- . ' showTocToggle();'
- . ' } ' )
- . "\n";
+ . "</ul>\n</td></tr></table>\n";
}
/**
* Generate a table of contents from a section tree
* Currently unused.
+ *
* @param $tree Return value of ParserOutput::getSections()
- * @return string HTML
+ * @return String: HTML fragment
*/
public function generateTOC( $tree ) {
$toc = '';
@@ -1358,16 +1379,19 @@ class Linker {
* to be included in the link, like "&section=$section"
* @param $tooltip string The tooltip to use for the link: will be escaped
* and wrapped in the 'editsectionhint' message
+ * @param $lang string Language code
* @return string HTML to use for edit link
*/
- public function doEditSectionLink( Title $nt, $section, $tooltip = null ) {
+ public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
// HTML generated here should probably have userlangattributes
// added to it for LTR text on RTL pages
$attribs = array();
- if( !is_null( $tooltip ) ) {
- $attribs['title'] = wfMsg( 'editsectionhint', $tooltip );
+ if ( !is_null( $tooltip ) ) {
+ # Bug 25462: undo double-escaping.
+ $tooltip = Sanitizer::decodeCharReferences( $tooltip );
+ $attribs['title'] = wfMsgReal( 'editsectionhint', array( $tooltip ), true, $lang );
}
- $link = $this->link( $nt, wfMsg('editsection'),
+ $link = $this->link( $nt, wfMsgExt( 'editsection', array( 'language' => $lang ) ),
$attribs,
array( 'action' => 'edit', 'section' => $section ),
array( 'noclasses', 'known' )
@@ -1376,43 +1400,44 @@ class Linker {
# Run the old hook. This takes up half of the function . . . hopefully
# we can rid of it someday.
$attribs = '';
- if( $tooltip ) {
- $attribs = wfMsgHtml( 'editsectionhint', htmlspecialchars( $tooltip ) );
+ if ( $tooltip ) {
+ $attribs = htmlspecialchars( wfMsgReal( 'editsectionhint', array( $tooltip ), true, $lang ) );
$attribs = " title=\"$attribs\"";
}
$result = null;
- wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $link, &$result ) );
- if( !is_null( $result ) ) {
+ wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $link, &$result, $lang ) );
+ if ( !is_null( $result ) ) {
# For reverse compatibility, add the brackets *after* the hook is
# run, and even add them to hook-provided text. (This is the main
# reason that the EditSectionLink hook is deprecated in favor of
# DoEditSectionLink: it can't change the brackets or the span.)
- $result = wfMsgHtml( 'editsection-brackets', $result );
+ $result = wfMsgExt( 'editsection-brackets', array( 'escape', 'replaceafter', 'language' => $lang ), $result );
return "<span class=\"editsection\">$result</span>";
}
# Add the brackets and the span, and *then* run the nice new hook, with
# clean and non-redundant arguments.
- $result = wfMsgHtml( 'editsection-brackets', $link );
+ $result = wfMsgExt( 'editsection-brackets', array( 'escape', 'replaceafter', 'language' => $lang ), $link );
$result = "<span class=\"editsection\">$result</span>";
- wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result ) );
+ wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result, $lang ) );
return $result;
}
/**
* Create a headline for content
*
- * @param int $level The level of the headline (1-6)
- * @param string $attribs Any attributes for the headline, starting with a space and ending with '>'
- * This *must* be at least '>' for no attribs
- * @param string $anchor The anchor to give the headline (the bit after the #)
- * @param string $text The text of the header
- * @param string $link HTML to add for the section edit link
- * @param mixed $legacyAnchor A second, optional anchor to give for
+ * @param $level Integer: the level of the headline (1-6)
+ * @param $attribs String: any attributes for the headline, starting with
+ * a space and ending with '>'
+ * This *must* be at least '>' for no attribs
+ * @param $anchor String: the anchor to give the headline (the bit after the #)
+ * @param $text String: the text of the header
+ * @param $link String: HTML to add for the section edit link
+ * @param $legacyAnchor Mixed: a second, optional anchor to give for
* backward compatibility (false to omit)
*
- * @return string HTML headline
+ * @return String: HTML headline
*/
public function makeHeadline( $level, $attribs, $anchor, $text, $link, $legacyAnchor = false ) {
$ret = "<h$level$attribs"
@@ -1420,7 +1445,7 @@ class Linker {
. " <span class=\"mw-headline\" id=\"$anchor\">$text</span>"
. "</h$level>";
if ( $legacyAnchor !== false ) {
- $ret = "<a id=\"$legacyAnchor\"></a>$ret";
+ $ret = "<div id=\"$legacyAnchor\"></div>$ret";
}
return $ret;
}
@@ -1428,8 +1453,6 @@ class Linker {
/**
* Split a link trail, return the "inside" portion and the remainder of the trail
* as a two-element array
- *
- * @static
*/
static function splitTrail( $trail ) {
static $regex = false;
@@ -1438,7 +1461,7 @@ class Linker {
$regex = $wgContLang->linkTrail();
}
$inside = '';
- if ( $trail != '' ) {
+ if ( $trail !== '' ) {
$m = array();
if ( preg_match( $regex, $trail, $m ) ) {
$inside = $m[1];
@@ -1459,7 +1482,7 @@ class Linker {
* changes, so this allows sysops to combat a busy vandal without bothering
* other users.
*
- * @param Revision $rev
+ * @param $rev Revision object
*/
function generateRollback( $rev ) {
return '<span class="mw-rollback-link">['
@@ -1470,8 +1493,8 @@ class Linker {
/**
* Build a raw rollback link, useful for collections of "tool" links
*
- * @param Revision $rev
- * @return string
+ * @param $rev Revision object
+ * @return String: HTML fragment
*/
public function buildRollbackLink( $rev ) {
global $wgRequest, $wgUser;
@@ -1480,7 +1503,7 @@ class Linker {
'action' => 'rollback',
'from' => $rev->getUserText()
);
- if( $wgRequest->getBool( 'bot' ) ) {
+ if ( $wgRequest->getBool( 'bot' ) ) {
$query['bot'] = '1';
$query['hidediff'] = '1'; // bug 15999
}
@@ -1494,11 +1517,11 @@ class Linker {
/**
* Returns HTML for the "templates used on this page" list.
*
- * @param array $templates Array of templates from Article::getUsedTemplate
+ * @param $templates Array of templates from Article::getUsedTemplate
* or similar
- * @param bool $preview Whether this is for a preview
- * @param bool $section Whether this is for a section edit
- * @return string HTML output
+ * @param $preview Boolean: whether this is for a preview
+ * @param $section Boolean: whether this is for a section edit
+ * @return String: HTML output
*/
public function formatTemplates( $templates, $preview = false, $section = false ) {
wfProfileIn( __METHOD__ );
@@ -1507,7 +1530,7 @@ class Linker {
if ( count( $templates ) > 0 ) {
# Do a batch existence check
$batch = new LinkBatch;
- foreach( $templates as $title ) {
+ foreach ( $templates as $title ) {
$batch->addObj( $title );
}
$batch->execute();
@@ -1533,7 +1556,7 @@ class Linker {
} else {
$protected = '';
}
- if( $titleObj->quickUserCan( 'edit' ) ) {
+ if ( $titleObj->quickUserCan( 'edit' ) ) {
$editLink = $this->link(
$titleObj,
wfMsg( 'editlink' ),
@@ -1559,9 +1582,9 @@ class Linker {
/**
* Returns HTML for the "hidden categories on this page" list.
*
- * @param array $hiddencats Array of hidden categories from Article::getHiddenCategories
+ * @param $hiddencats Array of hidden categories from Article::getHiddenCategories
* or similar
- * @return string HTML output
+ * @return String: HTML output
*/
public function formatHiddenCategories( $hiddencats ) {
global $wgLang;
@@ -1588,7 +1611,7 @@ class Linker {
* unit (B, KB, MB or GB) according to the magnitude in question
*
* @param $size Size to format
- * @return string
+ * @return String
*/
public function formatSize( $size ) {
global $wgLang;
@@ -1601,27 +1624,30 @@ class Linker {
* isn't always, because sometimes the accesskey needs to go on a different
* element than the id, for reverse-compatibility, etc.)
*
- * @param string $name Id of the element, minus prefixes.
- * @param mixed $options null or the string 'withaccess' to add an access-
+ * @param $name String: id of the element, minus prefixes.
+ * @param $options Mixed: null or the string 'withaccess' to add an access-
* key hint
- * @return string Contents of the title attribute (which you must HTML-
+ * @return String: contents of the title attribute (which you must HTML-
* escape), or false for no title attribute
*/
public function titleAttrib( $name, $options = null ) {
wfProfileIn( __METHOD__ );
- $tooltip = wfMsg( "tooltip-$name" );
- # Compatibility: formerly some tooltips had [alt-.] hardcoded
- $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
-
- # Message equal to '-' means suppress it.
- if ( wfEmptyMsg( "tooltip-$name", $tooltip ) || $tooltip == '-' ) {
+ if ( wfEmptyMsg( "tooltip-$name" ) ) {
$tooltip = false;
+ } else {
+ $tooltip = wfMsg( "tooltip-$name" );
+ # Compatibility: formerly some tooltips had [alt-.] hardcoded
+ $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
+ # Message equal to '-' means suppress it.
+ if ( $tooltip == '-' ) {
+ $tooltip = false;
+ }
}
if ( $options == 'withaccess' ) {
$accesskey = $this->accesskey( $name );
- if( $accesskey !== false ) {
+ if ( $accesskey !== false ) {
if ( $tooltip === false || $tooltip === '' ) {
$tooltip = "[$accesskey]";
} else {
@@ -1640,35 +1666,42 @@ class Linker {
* the id but isn't always, because sometimes the accesskey needs to go on
* a different element than the id, for reverse-compatibility, etc.)
*
- * @param string $name Id of the element, minus prefixes.
- * @return string Contents of the accesskey attribute (which you must HTML-
+ * @param $name String: id of the element, minus prefixes.
+ * @return String: contents of the accesskey attribute (which you must HTML-
* escape), or false for no accesskey attribute
*/
public function accesskey( $name ) {
+ if ( isset( $this->accesskeycache[$name] ) ) {
+ return $this->accesskeycache[$name];
+ }
wfProfileIn( __METHOD__ );
- $accesskey = wfMsg( "accesskey-$name" );
+ $message = wfMessage( "accesskey-$name" );
- # FIXME: Per standard MW behavior, a value of '-' means to suppress the
- # attribute, but this is broken for accesskey: that might be a useful
- # value.
- if( $accesskey != '' && $accesskey != '-' && !wfEmptyMsg( "accesskey-$name", $accesskey ) ) {
- wfProfileOut( __METHOD__ );
- return $accesskey;
+ if ( !$message->exists() ) {
+ $accesskey = false;
+ } else {
+ $accesskey = $message->plain();
+ if ( $accesskey === '' || $accesskey === '-' ) {
+ # FIXME: Per standard MW behavior, a value of '-' means to suppress the
+ # attribute, but this is broken for accesskey: that might be a useful
+ # value.
+ $accesskey = false;
+ }
}
wfProfileOut( __METHOD__ );
- return false;
+ return $this->accesskeycache[$name] = $accesskey;
}
/**
* Creates a (show/hide) link for deleting revisions/log entries
*
- * @param array $query Query parameters to be passed to link()
- * @param bool $restricted Set to true to use a <strong> instead of a <span>
- * @param bool $delete Set to true to use (show/hide) rather than (show)
+ * @param $query Array: query parameters to be passed to link()
+ * @param $restricted Boolean: set to true to use a <strong> instead of a <span>
+ * @param $delete Boolean: set to true to use (show/hide) rather than (show)
*
- * @return string HTML <a> link to Special:Revisiondelete, wrapped in a
+ * @return String: HTML <a> link to Special:Revisiondelete, wrapped in a
* span to allow for customization of appearance with CSS
*/
public function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
@@ -1682,7 +1715,7 @@ class Linker {
/**
* Creates a dead (show/hide) link for deleting revisions/log entries
*
- * @param bool $delete Set to true to use (show/hide) rather than (show)
+ * @param $delete Boolean: set to true to use (show/hide) rather than (show)
*
* @return string HTML text wrapped in a span to allow for customization
* of appearance with CSS
@@ -1718,11 +1751,11 @@ class Linker {
*/
function makeLink( $title, $text = '', $query = '', $trail = '' ) {
wfProfileIn( __METHOD__ );
- $nt = Title::newFromText( $title );
+ $nt = Title::newFromText( $title );
if ( $nt instanceof Title ) {
$result = $this->makeLinkObj( $nt, $text, $query, $trail );
} else {
- wfDebug( 'Invalid title passed to Linker::makeLink(): "'.$title."\"\n" );
+ wfDebug( 'Invalid title passed to Linker::makeLink(): "' . $title . "\"\n" );
$result = $text == "" ? $title : $text;
}
@@ -1742,13 +1775,15 @@ class Linker {
* @param $trail String: optional trail. Alphabetic characters at the start of this string will
* be included in the link text. Other characters will be appended after
* the end of the link.
+ * @param $prefix String: Optional prefix
+ * @param $aprops String: extra attributes to the a-element
*/
- function makeKnownLink( $title, $text = '', $query = '', $trail = '', $prefix = '',$aprops = '') {
+ function makeKnownLink( $title, $text = '', $query = '', $trail = '', $prefix = '', $aprops = '' ) {
$nt = Title::newFromText( $title );
if ( $nt instanceof Title ) {
return $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix , $aprops );
} else {
- wfDebug( 'Invalid title passed to Linker::makeKnownLink(): "'.$title."\"\n" );
+ wfDebug( 'Invalid title passed to Linker::makeKnownLink(): "' . $title . "\"\n" );
return $text == '' ? $title : $text;
}
}
@@ -1759,19 +1794,19 @@ class Linker {
* This function is a shortcut to makeBrokenLinkObj(Title::newFromText($title),...). Do not call
* it if you already have a title object handy. See makeBrokenLinkObj for further documentation.
*
- * @param string $title The text of the title
- * @param string $text Link text
- * @param string $query Optional query part
- * @param string $trail Optional trail. Alphabetic characters at the start of this string will
- * be included in the link text. Other characters will be appended after
- * the end of the link.
+ * @param $title String: The text of the title
+ * @param $text String: Link text
+ * @param $query String: Optional query part
+ * @param $trail String: Optional trail. Alphabetic characters at the start of this string will
+ * be included in the link text. Other characters will be appended after
+ * the end of the link.
*/
function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
$nt = Title::newFromText( $title );
if ( $nt instanceof Title ) {
return $this->makeBrokenLinkObj( $nt, $text, $query, $trail );
} else {
- wfDebug( 'Invalid title passed to Linker::makeBrokenLink(): "'.$title."\"\n" );
+ wfDebug( 'Invalid title passed to Linker::makeBrokenLink(): "' . $title . "\"\n" );
return $text == '' ? $title : $text;
}
}
@@ -1795,7 +1830,7 @@ class Linker {
if ( $nt instanceof Title ) {
return $this->makeStubLinkObj( $nt, $text, $query, $trail );
} else {
- wfDebug( 'Invalid title passed to Linker::makeStubLink(): "'.$title."\"\n" );
+ wfDebug( 'Invalid title passed to Linker::makeStubLink(): "' . $title . "\"\n" );
return $text == '' ? $title : $text;
}
}
@@ -1816,12 +1851,12 @@ class Linker {
* the end of the link.
* @param $prefix String: optional prefix. As trail, only before instead of after.
*/
- function makeLinkObj( $nt, $text= '', $query = '', $trail = '', $prefix = '' ) {
+ function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
wfProfileIn( __METHOD__ );
$query = wfCgiToArray( $query );
list( $inside, $trail ) = Linker::splitTrail( $trail );
- if( $text === '' ) {
+ if ( $text === '' ) {
$text = $this->linkText( $nt );
}
@@ -1838,7 +1873,7 @@ class Linker {
* it doesn't have to do a database query. It's also valid for interwiki titles and special
* pages.
*
- * @param $nt Title object of target page
+ * @param $title Title object of target page
* @param $text String: text to replace the title
* @param $query String: link target
* @param $trail String: text after link
@@ -1872,21 +1907,21 @@ class Linker {
*
* Make a red link to the edit page of a given title.
*
- * @param $nt Title object of the target page
+ * @param $title Title object of the target page
* @param $text String: Link text
* @param $query String: Optional query part
* @param $trail String: Optional trail. Alphabetic characters at the start of this string will
* be included in the link text. Other characters will be appended after
* the end of the link.
+ * @param $prefix String: Optional prefix
*/
function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
wfProfileIn( __METHOD__ );
list( $inside, $trail ) = Linker::splitTrail( $trail );
- if( $text === '' ) {
+ if ( $text === '' ) {
$text = $this->linkText( $title );
}
- $nt = $this->normaliseSpecialPage( $title );
$ret = $this->link( $title, "$prefix$text$inside", array(),
wfCgiToArray( $query ), 'broken' ) . $trail;
@@ -1906,9 +1941,10 @@ class Linker {
* @param $trail String: optional trail. Alphabetic characters at the start of this string will
* be included in the link text. Other characters will be appended after
* the end of the link.
+ * @param $prefix String: Optional prefix
*/
function makeStubLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
- wfDeprecated( __METHOD__ );
+ // wfDeprecated( __METHOD__ );
return $this->makeColouredLinkObj( $nt, 'stub', $text, $query, $trail, $prefix );
}
@@ -1924,9 +1960,11 @@ class Linker {
* @param $trail String: optional trail. Alphabetic characters at the start of this string will
* be included in the link text. Other characters will be appended after
* the end of the link.
+ * @param $prefix String: Optional prefix
*/
function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
- if($colour != ''){
+ // wfDeprecated( __METHOD__ );
+ if ( $colour != '' ) {
$style = $this->getInternalLinkAttributesObj( $nt, $text, $colour );
} else $style = '';
return $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
@@ -1942,17 +1980,17 @@ class Linker {
* Creates the HTML source for images
* @deprecated use makeImageLink2
*
- * @param object $title
- * @param string $label label text
- * @param string $alt alt text
- * @param string $align horizontal alignment: none, left, center, right)
- * @param array $handlerParams Parameters to be passed to the media handler
- * @param boolean $framed shows image in original size in a frame
- * @param boolean $thumb shows image as thumbnail in a frame
- * @param string $manualthumb image name for the manual thumbnail
- * @param string $valign vertical alignment: baseline, sub, super, top, text-top, middle, bottom, text-bottom
- * @param string $time, timestamp of the file, set as false for current
- * @return string
+ * @param $title Title object
+ * @param $label String: label text
+ * @param $alt String: alt text
+ * @param $align String: horizontal alignment: none, left, center, right)
+ * @param $handlerParams Array: parameters to be passed to the media handler
+ * @param $framed Boolean: shows image in original size in a frame
+ * @param $thumb Boolean: shows image as thumbnail in a frame
+ * @param $manualthumb String: image name for the manual thumbnail
+ * @param $valign String: vertical alignment: baseline, sub, super, top, text-top, middle, bottom, text-bottom
+ * @param $time String: timestamp of the file, set as false for current
+ * @return String
*/
function makeImageLinkObj( $title, $label, $alt, $align = '', $handlerParams = array(), $framed = false,
$thumb = false, $manualthumb = '', $valign = '', $time = false )
@@ -2005,7 +2043,7 @@ class Linker {
*/
public function editSectionLink( Title $nt, $section, $hint = '' ) {
wfDeprecated( __METHOD__ );
- if( $hint === '' ) {
+ if ( $hint === '' ) {
# No way to pass an actual empty $hint here! The new interface al-
# lows this, so we have to do this for compatibility.
$hint = null;
@@ -2014,7 +2052,7 @@ class Linker {
}
/**
- * Returns the attributes for the tooltip and access key
+ * Returns the attributes for the tooltip and access key.
*/
public function tooltipAndAccesskeyAttribs( $name ) {
global $wgEnableTooltipsAndAccesskeys;
@@ -2042,7 +2080,6 @@ class Linker {
return Xml::expandAttributes( $this->tooltipAndAccesskeyAttribs( $name ) );
}
-
/** @deprecated Returns raw bits of HTML, use titleAttrib() */
public function tooltip( $name, $options = null ) {
global $wgEnableTooltipsAndAccesskeys;
diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php
index ef3374d9..b86ea565 100644
--- a/includes/LinksUpdate.php
+++ b/includes/LinksUpdate.php
@@ -26,11 +26,11 @@ class LinksUpdate {
/**
* Constructor
*
- * @param Title $title Title of the page we're updating
- * @param ParserOutput $parserOutput Output from a full parse of this page
- * @param bool $recursive Queue jobs for recursive updates?
+ * @param $title Title of the page we're updating
+ * @param $parserOutput ParserOutput: output from a full parse of this page
+ * @param $recursive Boolean: queue jobs for recursive updates?
*/
- function LinksUpdate( $title, $parserOutput, $recursive = true ) {
+ function __construct( $title, $parserOutput, $recursive = true ) {
global $wgAntiLockFlags;
if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
@@ -54,6 +54,7 @@ class LinksUpdate {
$this->mExternals = $parserOutput->getExternalLinks();
$this->mCategories = $parserOutput->getCategories();
$this->mProperties = $parserOutput->getProperties();
+ $this->mInterwikis = $parserOutput->getInterwikiLinks();
# Convert the format of the interlanguage links
# I didn't want to change it in the ParserOutput, because that array is passed all
@@ -66,8 +67,18 @@ class LinksUpdate {
$this->mInterlangs[$key] = $title;
}
+ foreach ( $this->mCategories as $cat => &$sortkey ) {
+ # If the sortkey is longer then 255 bytes,
+ # it truncated by DB, and then doesn't get
+ # matched when comparing existing vs current
+ # categories, causing bug 25254.
+ # Also. substr behaves weird when given "".
+ if ( $sortkey !== '' ) {
+ $sortkey = substr( $sortkey, 0, 255 );
+ }
+ }
+
$this->mRecursive = $recursive;
- $this->mTouchTmplLinks = false;
wfRunHooks( 'LinksUpdateConstructed', array( &$this ) );
}
@@ -115,6 +126,11 @@ class LinksUpdate {
$this->incrTableUpdate( 'langlinks', 'll', $this->getInterlangDeletions( $existing ),
$this->getInterlangInsertions( $existing ) );
+ # Inline interwiki links
+ $existing = $this->getExistingInterwikis();
+ $this->incrTableUpdate( 'iwlinks', 'iwl', $this->getInterwikiDeletions( $existing ),
+ $this->getInterwikiInsertions( $existing ) );
+
# Template links
$existing = $this->getExistingTemplates();
$this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ),
@@ -175,6 +191,7 @@ class LinksUpdate {
$this->dumbTableUpdate( 'templatelinks', $this->getTemplateInsertions(), 'tl_from' );
$this->dumbTableUpdate( 'externallinks', $this->getExternalInsertions(), 'el_from' );
$this->dumbTableUpdate( 'langlinks', $this->getInterlangInsertions(),'ll_from' );
+ $this->dumbTableUpdate( 'iwlinks', $this->getInterwikiInsertions(),'iwl_from' );
$this->dumbTableUpdate( 'page_props', $this->getPropertyInsertions(), 'pp_page' );
# Update the cache of all the category pages and image description
@@ -219,8 +236,8 @@ class LinksUpdate {
/**
* Invalidate the cache of a list of pages from a single namespace
*
- * @param integer $namespace
- * @param array $dbkeys
+ * @param $namespace Integer
+ * @param $dbkeys Array
*/
function invalidatePages( $namespace, $dbkeys ) {
if ( !count( $dbkeys ) ) {
@@ -241,7 +258,7 @@ class LinksUpdate {
'page_touched < ' . $this->mDb->addQuotes( $now )
), __METHOD__
);
- while ( $row = $this->mDb->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$ids[] = $row->page_id;
}
if ( !count( $ids ) ) {
@@ -292,18 +309,6 @@ class LinksUpdate {
}
/**
- * Make a WHERE clause from a 2-d NS/dbkey array
- *
- * @param array $arr 2-d array indexed by namespace and DB key
- * @param string $prefix Field name prefix, without the underscore
- */
- function makeWhereFrom2d( &$arr, $prefix ) {
- $lb = new LinkBatch;
- $lb->setArray( $arr );
- return $lb->constructSet( $prefix, $this->mDb );
- }
-
- /**
* Update a table by doing a delete query then an insert query
* @private
*/
@@ -314,8 +319,13 @@ class LinksUpdate {
$fromField = "{$prefix}_from";
}
$where = array( $fromField => $this->mId );
- if ( $table == 'pagelinks' || $table == 'templatelinks' ) {
- $clause = $this->makeWhereFrom2d( $deletions, $prefix );
+ if ( $table == 'pagelinks' || $table == 'templatelinks' || $table == 'iwlinks' ) {
+ if ( $table == 'iwlinks' ) {
+ $baseKey = 'iwl_prefix';
+ } else {
+ $baseKey = "{$prefix}_namespace";
+ }
+ $clause = $this->mDb->makeWhereFrom2d( $deletions, $baseKey, "{$prefix}_title" );
if ( $clause ) {
$where[] = $clause;
} else {
@@ -352,8 +362,6 @@ class LinksUpdate {
function getLinkInsertions( $existing = array() ) {
$arr = array();
foreach( $this->mLinks as $ns => $dbkeys ) {
- # array_diff_key() was introduced in PHP 5.1, there is a compatibility function
- # in GlobalFunctions.php
$diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
@@ -421,22 +429,42 @@ class LinksUpdate {
/**
* Get an array of category insertions
- * @param array $existing Array mapping existing category names to sort keys. If both
+ *
+ * @param $existing Array mapping existing category names to sort keys. If both
* match a link in $this, the link will be omitted from the output
* @private
*/
function getCategoryInsertions( $existing = array() ) {
- global $wgContLang;
+ global $wgContLang, $wgCategoryCollation;
$diffs = array_diff_assoc( $this->mCategories, $existing );
$arr = array();
- foreach ( $diffs as $name => $sortkey ) {
+ foreach ( $diffs as $name => $prefix ) {
$nt = Title::makeTitleSafe( NS_CATEGORY, $name );
$wgContLang->findVariantLink( $name, $nt, true );
+
+ if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
+ $type = 'subcat';
+ } elseif ( $this->mTitle->getNamespace() == NS_FILE ) {
+ $type = 'file';
+ } else {
+ $type = 'page';
+ }
+
+ # Treat custom sortkeys as a prefix, so that if multiple
+ # things are forced to sort as '*' or something, they'll
+ # sort properly in the category rather than in page_id
+ # order or such.
+ $sortkey = Collation::singleton()->getSortKey(
+ $this->mTitle->getCategorySortkey( $prefix ) );
+
$arr[] = array(
'cl_from' => $this->mId,
'cl_to' => $name,
'cl_sortkey' => $sortkey,
- 'cl_timestamp' => $this->mDb->timestamp()
+ 'cl_timestamp' => $this->mDb->timestamp(),
+ 'cl_sortkey_prefix' => $prefix,
+ 'cl_collation' => $wgCategoryCollation,
+ 'cl_type' => $type,
);
}
return $arr;
@@ -444,7 +472,8 @@ class LinksUpdate {
/**
* Get an array of interlanguage link insertions
- * @param array $existing Array mapping existing language codes to titles
+ *
+ * @param $existing Array mapping existing language codes to titles
* @private
*/
function getInterlangInsertions( $existing = array() ) {
@@ -476,6 +505,25 @@ class LinksUpdate {
return $arr;
}
+ /**
+ * Get an array of interwiki insertions for passing to the DB
+ * Skips the titles specified by the 2-D array $existing
+ * @private
+ */
+ function getInterwikiInsertions( $existing = array() ) {
+ $arr = array();
+ foreach( $this->mInterwikis as $prefix => $dbkeys ) {
+ $diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys;
+ foreach ( $diffs as $dbk => $id ) {
+ $arr[] = array(
+ 'iwl_from' => $this->mId,
+ 'iwl_prefix' => $prefix,
+ 'iwl_title' => $dbk
+ );
+ }
+ }
+ return $arr;
+ }
/**
* Given an array of existing links, returns those links which are not in $this
@@ -556,6 +604,23 @@ class LinksUpdate {
}
/**
+ * Given an array of existing interwiki links, returns those links which are not in $this
+ * and thus should be deleted.
+ * @private
+ */
+ function getInterwikiDeletions( $existing ) {
+ $del = array();
+ foreach ( $existing as $prefix => $dbkeys ) {
+ if ( isset( $this->mInterwikis[$prefix] ) ) {
+ $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] );
+ } else {
+ $del[$prefix] = $existing[$prefix];
+ }
+ }
+ return $del;
+ }
+
+ /**
* Get an array of existing links, as a 2-D array
* @private
*/
@@ -563,13 +628,12 @@ class LinksUpdate {
$res = $this->mDb->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ),
array( 'pl_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
- while ( $row = $this->mDb->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( !isset( $arr[$row->pl_namespace] ) ) {
$arr[$row->pl_namespace] = array();
}
$arr[$row->pl_namespace][$row->pl_title] = 1;
}
- $this->mDb->freeResult( $res );
return $arr;
}
@@ -581,13 +645,12 @@ class LinksUpdate {
$res = $this->mDb->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ),
array( 'tl_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
- while ( $row = $this->mDb->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( !isset( $arr[$row->tl_namespace] ) ) {
$arr[$row->tl_namespace] = array();
}
$arr[$row->tl_namespace][$row->tl_title] = 1;
}
- $this->mDb->freeResult( $res );
return $arr;
}
@@ -599,10 +662,9 @@ class LinksUpdate {
$res = $this->mDb->select( 'imagelinks', array( 'il_to' ),
array( 'il_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
- while ( $row = $this->mDb->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$arr[$row->il_to] = 1;
}
- $this->mDb->freeResult( $res );
return $arr;
}
@@ -614,10 +676,9 @@ class LinksUpdate {
$res = $this->mDb->select( 'externallinks', array( 'el_to' ),
array( 'el_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
- while ( $row = $this->mDb->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$arr[$row->el_to] = 1;
}
- $this->mDb->freeResult( $res );
return $arr;
}
@@ -626,13 +687,12 @@ class LinksUpdate {
* @private
*/
function getExistingCategories() {
- $res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey' ),
+ $res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey_prefix' ),
array( 'cl_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
- while ( $row = $this->mDb->fetchObject( $res ) ) {
- $arr[$row->cl_to] = $row->cl_sortkey;
+ foreach ( $res as $row ) {
+ $arr[$row->cl_to] = $row->cl_sortkey_prefix;
}
- $this->mDb->freeResult( $res );
return $arr;
}
@@ -645,13 +705,30 @@ class LinksUpdate {
$res = $this->mDb->select( 'langlinks', array( 'll_lang', 'll_title' ),
array( 'll_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
- while ( $row = $this->mDb->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$arr[$row->ll_lang] = $row->ll_title;
}
return $arr;
}
/**
+ * Get an array of existing inline interwiki links, as a 2-D array
+ * @return array (prefix => array(dbkey => 1))
+ */
+ protected function getExistingInterwikis() {
+ $res = $this->mDb->select( 'iwlinks', array( 'iwl_prefix', 'iwl_title' ),
+ array( 'iwl_from' => $this->mId ), __METHOD__, $this->mOptions );
+ $arr = array();
+ foreach ( $res as $row ) {
+ if ( !isset( $arr[$row->iwl_prefix] ) ) {
+ $arr[$row->iwl_prefix] = array();
+ }
+ $arr[$row->iwl_prefix][$row->iwl_title] = 1;
+ }
+ return $arr;
+ }
+
+ /**
* Get an array of existing categories, with the name in the key and sort key in the value.
* @private
*/
@@ -659,10 +736,9 @@ class LinksUpdate {
$res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ),
array( 'pp_page' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
- while ( $row = $this->mDb->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$arr[$row->pp_propname] = $row->pp_value;
}
- $this->mDb->freeResult( $res );
return $arr;
}
diff --git a/includes/LocalisationCache.php b/includes/LocalisationCache.php
index 12925b68..9ead21f1 100644
--- a/includes/LocalisationCache.php
+++ b/includes/LocalisationCache.php
@@ -101,7 +101,7 @@ class LocalisationCache {
* by a fallback sequence.
*/
static public $mergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
- 'dateFormats', 'defaultUserOptionOverrides', 'magicWords', 'imageFiles',
+ 'dateFormats', 'defaultUserOptionOverrides', 'imageFiles',
'preloadedMessages',
);
@@ -122,6 +122,11 @@ class LocalisationCache {
* key is removed after the first merge.
*/
static public $optionalMergeKeys = array( 'bookstoreList' );
+
+ /**
+ * Keys for items that are formatted like $magicWords
+ */
+ static public $magicWordKeys = array( 'magicWords' );
/**
* Keys for items where the subitems are stored in the backend separately.
@@ -187,7 +192,8 @@ class LocalisationCache {
self::$mergeableMapKeys,
self::$mergeableListKeys,
self::$mergeableAliasListKeys,
- self::$optionalMergeKeys
+ self::$optionalMergeKeys,
+ self::$magicWordKeys
) );
}
return isset( $this->mergeableKeys[$key] );
@@ -435,6 +441,8 @@ class LocalisationCache {
if ( isset( $value['inherit'] ) ) {
unset( $value['inherit'] );
}
+ } elseif ( in_array( $key, self::$magicWordKeys ) ) {
+ $this->mergeMagicWords( $value, $fallbackValue );
}
}
} else {
@@ -442,6 +450,20 @@ class LocalisationCache {
}
}
+ protected function mergeMagicWords( &$value, $fallbackValue ) {
+ foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
+ if ( !isset( $value[$magicName] ) ) {
+ $value[$magicName] = $fallbackInfo;
+ } else {
+ $oldSynonyms = array_slice( $fallbackInfo, 1 );
+ $newSynonyms = array_slice( $value[$magicName], 1 );
+ $synonyms = array_values( array_unique( array_merge(
+ $newSynonyms, $oldSynonyms ) ) );
+ $value[$magicName] = array_merge( array( $fallbackInfo[0] ), $synonyms );
+ }
+ }
+ }
+
/**
* Given an array mapping language code to localisation value, such as is
* found in extension *.i18n.php files, iterate through a fallback sequence
@@ -621,6 +643,13 @@ class LocalisationCache {
}
}
$this->store->finishWrite();
+
+ # Clear out the MessageBlobStore
+ # HACK: If using a null (i.e. disabled) storage backend, we
+ # can't write to the MessageBlobStore either
+ if ( !$this->store instanceof LCStore_Null ) {
+ MessageBlobStore::clear();
+ }
wfProfileOut( __METHOD__ );
}
diff --git a/includes/LogEventsList.php b/includes/LogEventsList.php
index 4bfdc2a3..128500c5 100644
--- a/includes/LogEventsList.php
+++ b/includes/LogEventsList.php
@@ -1,24 +1,31 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>, 2008 Aaron Schulz
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * Contain classes to list log entries
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>, 2008 Aaron Schulz
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
class LogEventsList {
const NO_ACTION_LINK = 1;
+ const NO_EXTRA_USER_LINKS = 2;
private $skin;
private $out;
@@ -40,7 +47,7 @@ class LogEventsList {
if( !isset( $this->message ) ) {
$messages = array( 'revertmerge', 'protect_change', 'unblocklink', 'change-blocklink',
'revertmove', 'undeletelink', 'undeleteviewlink', 'revdel-restore', 'hist', 'diff',
- 'pipe-separator' );
+ 'pipe-separator', 'revdel-restore-deleted', 'revdel-restore-visible' );
foreach( $messages as $msg ) {
$this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
}
@@ -64,6 +71,7 @@ class LogEventsList {
/**
* Show options for the log list
+ *
* @param $types string or Array
* @param $user String
* @param $page String
@@ -87,8 +95,7 @@ class LogEventsList {
$tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
- $html = '';
- $html .= Xml::hidden( 'title', $special );
+ $html = Html::hidden( 'title', $special );
// Basic selectors
$html .= $this->getTypeMenu( $types ) . "\n";
@@ -106,7 +113,7 @@ class LogEventsList {
// Tag filter
if ($tagSelector) {
- $html .= Xml::tags( 'p', null, implode( '&nbsp;', $tagSelector ) );
+ $html .= Xml::tags( 'p', null, implode( '&#160;', $tagSelector ) );
}
// Filter links
@@ -155,7 +162,7 @@ class LogEventsList {
);
$links[$type] = wfMsgHtml( "log-show-hide-{$type}", $link );
- $hiddens .= Xml::hidden( "hide_{$type}_log", $val ) . "\n";
+ $hiddens .= Html::hidden( "hide_{$type}_log", $val ) . "\n";
}
// Build links
return '<small>'.$wgLang->pipeList( $links ) . '</small>' . $hiddens;
@@ -198,6 +205,14 @@ class LogEventsList {
// Note the query type
$queryType = count($queryTypes) == 1 ? $queryTypes[0] : '';
+
+ // Always put "All public logs" on top
+ if ( isset( $typesByName[''] ) ) {
+ $all = $typesByName[''];
+ unset( $typesByName[''] );
+ $typesByName = array( '' => $all ) + $typesByName;
+ }
+
// Third pass generates sorted XHTML content
foreach( $typesByName as $type => $text ) {
$selected = ($type == $queryType);
@@ -271,38 +286,87 @@ class LogEventsList {
* @return String: Formatted HTML list item
*/
public function logLine( $row ) {
- global $wgLang, $wgUser, $wgContLang;
-
+ $classes = array( 'mw-logline-' . $row->log_type );
$title = Title::makeTitle( $row->log_namespace, $row->log_title );
- $classes = array( "mw-logline-{$row->log_type}" );
- $time = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->log_timestamp ), true );
+ // Log time
+ $time = $this->logTimestamp( $row );
// User links
+ $userLink = $this->logUserLinks( $row );
+ // Extract extra parameters
+ $paramArray = LogPage::extractParams( $row->log_params );
+ // Event description
+ $action = $this->logAction( $row, $title, $paramArray );
+ // Log comment
+ $comment = $this->logComment( $row );
+ // Add review/revert links and such...
+ $revert = $this->logActionLinks( $row, $title, $paramArray, $comment );
+
+ // Some user can hide log items and have review links
+ $del = $this->getShowHideLinks( $row );
+ if( $del != '' ) $del .= ' ';
+
+ // Any tags...
+ list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'logevent' );
+ $classes = array_merge( $classes, $newClasses );
+
+ return Xml::tags( 'li', array( "class" => implode( ' ', $classes ) ),
+ $del . "$time $userLink $action $comment $revert $tagDisplay" ) . "\n";
+ }
+
+ private function logTimestamp( $row ) {
+ global $wgLang;
+ $time = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->log_timestamp ), true );
+ return htmlspecialchars( $time );
+ }
+
+ private function logUserLinks( $row ) {
if( self::isDeleted( $row, LogPage::DELETED_USER ) ) {
- $userLink = '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+ $userLinks = '<span class="history-deleted">' .
+ wfMsgHtml( 'rev-deleted-user' ) . '</span>';
} else {
- $userLink = $this->skin->userLink( $row->log_user, $row->user_name ) .
- $this->skin->userToolLinks( $row->log_user, $row->user_name, true, 0, $row->user_editcount );
+ $userLinks = $this->skin->userLink( $row->log_user, $row->user_name );
+ // Talk|Contribs links...
+ if( !( $this->flags & self::NO_EXTRA_USER_LINKS ) ) {
+ $userLinks .= $this->skin->userToolLinks(
+ $row->log_user, $row->user_name, true, 0, $row->user_editcount );
+ }
}
- // Comment
+ return $userLinks;
+ }
+
+ private function logAction( $row, $title, $paramArray ) {
+ if( self::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
+ $action = '<span class="history-deleted">' .
+ wfMsgHtml( 'rev-deleted-event' ) . '</span>';
+ } else {
+ $action = LogPage::actionText(
+ $row->log_type, $row->log_action, $title, $this->skin, $paramArray, true );
+ }
+ return $action;
+ }
+
+ private function logComment( $row ) {
+ global $wgContLang;
if( self::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
- $comment = '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
+ $comment = '<span class="history-deleted">' .
+ wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
} else {
- $comment = $wgContLang->getDirMark() . $this->skin->commentBlock( $row->log_comment );
+ $comment = $wgContLang->getDirMark() .
+ $this->skin->commentBlock( $row->log_comment );
}
- // Extract extra parameters
- $paramArray = LogPage::extractParams( $row->log_params );
- $revert = $del = '';
- // Some user can hide log items and have review links
- if( !( $this->flags & self::NO_ACTION_LINK ) && $wgUser->isAllowed( 'deletedhistory' ) ) {
- // Don't show useless link to people who cannot hide revisions
- if( $row->log_deleted || $wgUser->isAllowed( 'deleterevision' ) ) {
- $del = $this->getShowHideLinks( $row ) . ' ';
- }
+ return $comment;
+ }
+
+ // @TODO: split up!
+ private function logActionLinks( $row, $title, $paramArray, &$comment ) {
+ global $wgUser;
+ if( ( $this->flags & self::NO_ACTION_LINK ) // we don't want to see the action
+ || self::isDeleted( $row, LogPage::DELETED_ACTION ) ) // action is hidden
+ {
+ return '';
}
- // Add review links and such...
- if( ( $this->flags & self::NO_ACTION_LINK ) || ( $row->log_deleted & LogPage::DELETED_ACTION ) ) {
- // Action text is suppressed...
- } else if( self::typeAction( $row, 'move', 'move', 'move' ) && !empty( $paramArray[0] ) ) {
+ $revert = '';
+ if( self::typeAction( $row, 'move', 'move', 'move' ) && !empty( $paramArray[0] ) ) {
$destTitle = Title::newFromText( $paramArray[0] );
if( $destTitle ) {
$revert = '(' . $this->skin->link(
@@ -312,7 +376,7 @@ class LogEventsList {
array(
'wpOldTitle' => $destTitle->getPrefixedDBkey(),
'wpNewTitle' => $title->getPrefixedDBkey(),
- 'wpReason' => wfMsgForContent( 'revertmove' ),
+ 'wpReason' => wfMsgForContent( 'revertmove' ),
'wpMovetalk' => 0
),
array( 'known', 'noclasses' )
@@ -325,7 +389,6 @@ class LogEventsList {
} else {
$viewdeleted = $this->message['undeletelink'];
}
-
$revert = '(' . $this->skin->link(
SpecialPage::getTitleFor( 'Undelete' ),
$viewdeleted,
@@ -377,9 +440,8 @@ class LogEventsList {
$revert .= ')';
// Show unmerge link
} else if( self::typeAction( $row, 'merge', 'merge', 'mergehistory' ) ) {
- $merge = SpecialPage::getTitleFor( 'Mergehistory' );
$revert = '(' . $this->skin->link(
- $merge,
+ SpecialPage::getTitleFor( 'MergeHistory' ),
$this->message['revertmerge'],
array(),
array(
@@ -391,63 +453,13 @@ class LogEventsList {
) . ')';
// If an edit was hidden from a page give a review link to the history
} else if( self::typeAction( $row, array( 'delete', 'suppress' ), 'revision', 'deletedhistory' ) ) {
- if( count($paramArray) >= 2 ) {
- // Different revision types use different URL params...
- $key = $paramArray[0];
- // $paramArray[1] is a CSV of the IDs
- $Ids = explode( ',', $paramArray[1] );
- $query = $paramArray[1];
- $revert = array();
- // Diff link for single rev deletions
- if( count($Ids) == 1 ) {
- // Live revision diffs...
- if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
- $revert[] = $this->skin->link(
- $title,
- $this->message['diff'],
- array(),
- array(
- 'diff' => intval( $Ids[0] ),
- 'unhide' => 1
- ),
- array( 'known', 'noclasses' )
- );
- // Deleted revision diffs...
- } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
- $revert[] = $this->skin->link(
- SpecialPage::getTitleFor( 'Undelete' ),
- $this->message['diff'],
- array(),
- array(
- 'target' => $title->getPrefixedDBKey(),
- 'diff' => 'prev',
- 'timestamp' => $Ids[0]
- ),
- array( 'known', 'noclasses' )
- );
- }
- }
- // View/modify link...
- $revert[] = $this->skin->link(
- SpecialPage::getTitleFor( 'Revisiondelete' ),
- $this->message['revdel-restore'],
- array(),
- array(
- 'target' => $title->getPrefixedText(),
- 'type' => $key,
- 'ids' => $query
- ),
- array( 'known', 'noclasses' )
- );
- // Pipe links
- $revert = wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
- }
+ $revert = RevisionDeleter::getLogLinks( $title, $paramArray,
+ $this->skin, $this->message );
// Hidden log items, give review link
} else if( self::typeAction( $row, array( 'delete', 'suppress' ), 'event', 'deletedhistory' ) ) {
if( count($paramArray) >= 1 ) {
$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
// $paramArray[1] is a CSV of the IDs
- $Ids = explode( ',', $paramArray[0] );
$query = $paramArray[0];
// Link to each hidden object ID, $paramArray[1] is the url param
$revert = '(' . $this->skin->link(
@@ -470,7 +482,7 @@ class LogEventsList {
# Fall back to a blue contributions link
$revert = $this->skin->userToolLinks( 1, $title->getDBkey() );
}
- if( $time < '20080129000000' ) {
+ if( wfTimestamp( TS_MW, $row->log_timestamp ) < '20080129000000' ) {
# Suppress $comment from old entries (before 2008-01-29),
# not needed and can contain incorrect links
$comment = '';
@@ -480,26 +492,10 @@ class LogEventsList {
wfRunHooks( 'LogLine', array( $row->log_type, $row->log_action, $title, $paramArray,
&$comment, &$revert, $row->log_timestamp ) );
}
- // Event description
- if( self::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
- $action = '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>';
- } else {
- $action = LogPage::actionText( $row->log_type, $row->log_action, $title,
- $this->skin, $paramArray, true );
- }
-
- // Any tags...
- list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'logevent' );
- $classes = array_merge( $classes, $newClasses );
-
if( $revert != '' ) {
$revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>';
}
-
- $time = htmlspecialchars( $time );
-
- return Xml::tags( 'li', array( "class" => implode( ' ', $classes ) ),
- $del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert . " $tagDisplay" ) . "\n";
+ return $revert;
}
/**
@@ -508,23 +504,30 @@ class LogEventsList {
*/
private function getShowHideLinks( $row ) {
global $wgUser;
- if( $row->log_type == 'suppress' ) {
- return ''; // No one can hide items from the oversight log
+ if( ( $this->flags & self::NO_ACTION_LINK ) // we don't want to see the links
+ || $row->log_type == 'suppress' ) // no one can hide items from the suppress log
+ {
+ return '';
}
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- // If event was hidden from sysops
- if( !self::userCan( $row, LogPage::DELETED_RESTRICTED ) ) {
- $del = $this->skin->revDeleteLinkDisabled( $canHide );
- } else {
- $target = SpecialPage::getTitleFor( 'Log', $row->log_type );
- $page = Title::makeTitle( $row->log_namespace, $row->log_title );
- $query = array(
- 'target' => $target->getPrefixedDBkey(),
- 'type' => 'logging',
- 'ids' => $row->log_id,
- );
- $del = $this->skin->revDeleteLink( $query,
- self::isDeleted( $row, LogPage::DELETED_RESTRICTED ), $canHide );
+ $del = '';
+ // Don't show useless link to people who cannot hide revisions
+ if( $wgUser->isAllowed( 'deletedhistory' ) ) {
+ if( $row->log_deleted || $wgUser->isAllowed( 'deleterevision' ) ) {
+ $canHide = $wgUser->isAllowed( 'deleterevision' );
+ // If event was hidden from sysops
+ if( !self::userCan( $row, LogPage::DELETED_RESTRICTED ) ) {
+ $del = $this->skin->revDeleteLinkDisabled( $canHide );
+ } else {
+ $target = SpecialPage::getTitleFor( 'Log', $row->log_type );
+ $query = array(
+ 'target' => $target->getPrefixedDBkey(),
+ 'type' => 'logging',
+ 'ids' => $row->log_id,
+ );
+ $del = $this->skin->revDeleteLink( $query,
+ self::isDeleted( $row, LogPage::DELETED_RESTRICTED ), $canHide );
+ }
+ }
}
return $del;
}
@@ -534,7 +537,7 @@ class LogEventsList {
* @param $type Mixed: string/array
* @param $action Mixed: string/array
* @param $right string
- * @return bool
+ * @return Boolean
*/
public static function typeAction( $row, $type, $action, $right='' ) {
$match = is_array($type) ?
@@ -553,6 +556,7 @@ class LogEventsList {
/**
* Determine if the current user is allowed to view a particular
* field of this log row, if it's marked as deleted.
+ *
* @param $row Row
* @param $field Integer
* @return Boolean
@@ -560,10 +564,11 @@ class LogEventsList {
public static function userCan( $row, $field ) {
return self::userCanBitfield( $row->log_deleted, $field );
}
-
+
/**
* Determine if the current user is allowed to view a particular
* field of this log row, if it's marked as deleted.
+ *
* @param $bitfield Integer (current field)
* @param $field Integer
* @return Boolean
@@ -571,7 +576,7 @@ class LogEventsList {
public static function userCanBitfield( $bitfield, $field ) {
if( $bitfield & $field ) {
global $wgUser;
- $permission = '';
+
if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
$permission = 'suppressrevision';
} else {
@@ -595,6 +600,7 @@ class LogEventsList {
/**
* Show log extract. Either with text and a box (set $msgKey) or without (don't set $msgKey)
+ *
* @param $out OutputPage or String-by-reference
* @param $types String or Array
* @param $page String The page title to show log entries for
@@ -609,38 +615,41 @@ class LogEventsList {
* that are processed with wgMsgExt and option 'parse'
* - offset Set to overwrite offset parameter in $wgRequest
* set to '' to unset offset
- * - wrap String: Wrap the message in html (usually something like "<div ...>$1</div>").
+ * - wrap String Wrap the message in html (usually something like "<div ...>$1</div>").
+ * - flags Integer display flags (NO_ACTION_LINK,NO_EXTRA_USER_LINKS)
* @return Integer Number of total log items (not limited by $lim)
*/
- public static function showLogExtract( &$out, $types=array(), $page='', $user='',
- $param = array() ) {
-
+ public static function showLogExtract(
+ &$out, $types=array(), $page='', $user='', $param = array()
+ ) {
+ global $wgUser, $wgOut;
$defaultParameters = array(
'lim' => 25,
'conds' => array(),
'showIfEmpty' => true,
'msgKey' => array(''),
- 'wrap' => "$1"
+ 'wrap' => "$1",
+ 'flags' => 0
);
-
# The + operator appends elements of remaining keys from the right
# handed array to the left handed, whereas duplicated keys are NOT overwritten.
$param += $defaultParameters;
-
- global $wgUser, $wgOut;
# Convert $param array to individual variables
$lim = $param['lim'];
$conds = $param['conds'];
$showIfEmpty = $param['showIfEmpty'];
$msgKey = $param['msgKey'];
$wrap = $param['wrap'];
- if ( !is_array( $msgKey ) )
+ $flags = $param['flags'];
+ if ( !is_array( $msgKey ) ) {
$msgKey = array( $msgKey );
+ }
# Insert list of top 50 (or top $lim) items
- $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
+ $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, $flags );
$pager = new LogPager( $loglist, $types, $user, $page, '', $conds );
- if ( isset( $param['offset'] ) ) # Tell pager to ignore $wgRequest offset
+ if ( isset( $param['offset'] ) ) { # Tell pager to ignore $wgRequest offset
$pager->setOffset( $param['offset'] );
+ }
if( $lim > 0 ) $pager->mLimit = $lim;
$logBody = $pager->getBody();
$s = '';
@@ -681,10 +690,10 @@ class LogEventsList {
array(),
$urlParam
);
-
}
- if ( $logBody && $msgKey[0] )
+ if ( $logBody && $msgKey[0] ) {
$s .= '</div>';
+ }
if ( $wrap!='' ) { // Wrap message in html
$s = str_replace( '$1', $s, $wrap );
@@ -701,9 +710,10 @@ class LogEventsList {
/**
* SQL clause to skip forbidden log types for this user
+ *
* @param $db Database
* @param $audience string, public/user
- * @return mixed (string or false)
+ * @return Mixed: string or false
*/
public static function getExcludeClause( $db, $audience = 'public' ) {
global $wgLogRestrictions, $wgUser;
@@ -734,15 +744,17 @@ class LogPager extends ReverseChronologicalPager {
public $mLogEventsList;
/**
- * constructor
+ * Constructor
+ *
* @param $list LogEventsList
- * @param $types String or Array log types to show
- * @param $user String The user who made the log entries
- * @param $title String The page title the log entries are for
- * @param $pattern String Do a prefix search rather than an exact title match
- * @param $conds Array Extra conditions for the query
- * @param $year Integer The year to start from
- * @param $month Integer The month to start from
+ * @param $types String or Array: log types to show
+ * @param $user String: the user who made the log entries
+ * @param $title String: the page title the log entries are for
+ * @param $pattern String: do a prefix search rather than an exact title match
+ * @param $conds Array: extra conditions for the query
+ * @param $year Integer: the year to start from
+ * @param $month Integer: the month to start from
+ * @param $tagFilter String: tag
*/
public function __construct( $list, $types = array(), $user = '', $title = '', $pattern = '',
$conds = array(), $year = false, $month = false, $tagFilter = '' )
@@ -790,6 +802,7 @@ class LogPager extends ReverseChronologicalPager {
/**
* Set the log reader to return only entries of the given type.
* Type restrictions enforced here
+ *
* @param $types String or array: Log types ('upload', 'delete', etc);
* empty string means no restriction
*/
@@ -822,6 +835,7 @@ class LogPager extends ReverseChronologicalPager {
/**
* Set the log reader to return only entries by the given user.
+ *
* @param $name String: (In)valid user name
*/
private function limitUser( $name ) {
@@ -855,6 +869,7 @@ class LogPager extends ReverseChronologicalPager {
/**
* Set the log reader to return only entries affecting the given page.
* (For the block and rights logs, this is a user page.)
+ *
* @param $page String: Title name as text
* @param $pattern String
*/
@@ -898,7 +913,6 @@ class LogPager extends ReverseChronologicalPager {
}
public function getQueryInfo() {
- global $wgOut;
$tables = array( 'logging', 'user' );
$this->mConds[] = 'user_id = log_user';
$index = array();
@@ -951,7 +965,7 @@ class LogPager extends ReverseChronologicalPager {
# Do a link batch query
if( $this->getNumRows() > 0 ) {
$lb = new LinkBatch;
- while( $row = $this->mResult->fetchObject() ) {
+ foreach ( $this->mResult as $row ) {
$lb->add( $row->log_namespace, $row->log_title );
$lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
$lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
@@ -1009,6 +1023,7 @@ class LogPager extends ReverseChronologicalPager {
*/
class LogReader {
var $pager;
+
/**
* @param $request WebRequest: for internal use use a FauxRequest object to pass arbitrary parameters.
*/
@@ -1073,8 +1088,9 @@ class LogViewer {
* Take over the whole output page in $wgOut with the log display.
*/
public function show() {
+ global $wgOut;
# Set title and add header
- $this->list->showHeader( $pager->getType() );
+ $this->list->showHeader( $this->pager->getType() );
# Show form options
$this->list->showOptions( $this->pager->getType(), $this->pager->getUser(), $this->pager->getPage(),
$this->pager->getPattern(), $this->pager->getYear(), $this->pager->getMonth() );
@@ -1097,6 +1113,7 @@ class LogViewer {
* Output just the list of entries given by the linked LogReader,
* with extraneous UI elements. Use for displaying log fragments in
* another page (eg at Special:Undelete)
+ *
* @param $out OutputPage: where to send output
*/
public function showList( &$out ) {
diff --git a/includes/LogPage.php b/includes/LogPage.php
index 1d8d6c1c..8bd08dc4 100644
--- a/includes/LogPage.php
+++ b/includes/LogPage.php
@@ -1,25 +1,25 @@
<?php
-#
-# Copyright (C) 2002, 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
* Contain log classes
+ *
+ * Copyright © 2002, 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
*/
@@ -43,17 +43,17 @@ class LogPage {
var $updateRecentChanges, $sendToUDP;
/**
- * Constructor
- *
- * @param string $type One of '', 'block', 'protect', 'rights', 'delete',
- * 'upload', 'move'
- * @param bool $rc Whether to update recent changes as well as the logging table
- * @param bool $udp Whether to send to the UDP feed if NOT sent to RC
- */
+ * Constructor
+ *
+ * @param $type String: one of '', 'block', 'protect', 'rights', 'delete',
+ * 'upload', 'move'
+ * @param $rc Boolean: whether to update recent changes as well as the logging table
+ * @param $udp String: pass 'UDP' to send to the UDP feed if NOT sent to RC
+ */
public function __construct( $type, $rc = true, $udp = 'skipUDP' ) {
$this->type = $type;
$this->updateRecentChanges = $rc;
- $this->sendToUDP = ($udp == 'UDP');
+ $this->sendToUDP = ( $udp == 'UDP' );
}
protected function saveContent() {
@@ -77,23 +77,29 @@ class LogPage {
'log_params' => $this->params
);
$dbw->insert( 'logging', $data, __METHOD__ );
- $newId = !is_null($log_id) ? $log_id : $dbw->insertId();
+ $newId = !is_null( $log_id ) ? $log_id : $dbw->insertId();
# And update recentchanges
if( $this->updateRecentChanges ) {
$titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
- RecentChange::notifyLog( $now, $titleObj, $this->doer, $this->getRcComment(), '', $this->type,
- $this->action, $this->target, $this->comment, $this->params, $newId );
- } else if( $this->sendToUDP ) {
+ RecentChange::notifyLog(
+ $now, $titleObj, $this->doer, $this->getRcComment(), '',
+ $this->type, $this->action, $this->target, $this->comment,
+ $this->params, $newId
+ );
+ } elseif( $this->sendToUDP ) {
# Don't send private logs to UDP
- if( isset($wgLogRestrictions[$this->type]) && $wgLogRestrictions[$this->type] !='*' ) {
+ if( isset( $wgLogRestrictions[$this->type] ) && $wgLogRestrictions[$this->type] != '*' ) {
return true;
}
# Notify external application via UDP.
# We send this to IRC but do not want to add it the RC table.
$titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
- $rc = RecentChange::newLogEntry( $now, $titleObj, $this->doer, $this->getRcComment(), '',
- $this->type, $this->action, $this->target, $this->comment, $this->params, $newId );
+ $rc = RecentChange::newLogEntry(
+ $now, $titleObj, $this->doer, $this->getRcComment(), '',
+ $this->type, $this->action, $this->target, $this->comment,
+ $this->params, $newId
+ );
$rc->notifyRC2UDP();
}
return $newId;
@@ -105,10 +111,11 @@ class LogPage {
public function getRcComment() {
$rcComment = $this->actionText;
if( $this->comment != '' ) {
- if ($rcComment == '')
+ if ( $rcComment == '' ) {
$rcComment = $this->comment;
- else
+ } else {
$rcComment .= wfMsgForContent( 'colon-separator' ) . $this->comment;
+ }
}
return $rcComment;
}
@@ -121,7 +128,9 @@ class LogPage {
}
/**
- * @static
+ * Get the list of valid log types
+ *
+ * @return Array of strings
*/
public static function validTypes() {
global $wgLogTypes;
@@ -129,21 +138,25 @@ class LogPage {
}
/**
- * @static
+ * Is $type a valid log type
+ *
+ * @param $type String: log type to check
+ * @return Boolean
*/
public static function isLogType( $type ) {
return in_array( $type, LogPage::validTypes() );
}
/**
- * @static
- * @param string $type logtype
+ * Get the name for the given log type
+ *
+ * @param $type String: logtype
+ * @return String: log name
*/
public static function logName( $type ) {
- global $wgLogNames, $wgMessageCache;
+ global $wgLogNames;
if( isset( $wgLogNames[$type] ) ) {
- $wgMessageCache->loadAllMessages();
return str_replace( '_', ' ', wfMsg( $wgLogNames[$type] ) );
} else {
// Bogus log types? Perhaps an extension was removed.
@@ -152,26 +165,34 @@ class LogPage {
}
/**
+ * Get the log header for the given log type
+ *
* @todo handle missing log types
- * @param string $type logtype
- * @return string Headertext of this logtype
+ * @param $type String: logtype
+ * @return String: headertext of this logtype
*/
public static function logHeader( $type ) {
- global $wgLogHeaders, $wgMessageCache;
- $wgMessageCache->loadAllMessages();
- return wfMsgExt($wgLogHeaders[$type],array('parseinline'));
+ global $wgLogHeaders;
+ return wfMsgExt( $wgLogHeaders[$type], array( 'parseinline' ) );
}
/**
- * @static
+ * Generate text for a log entry
+ *
+ * @param $type String: log type
+ * @param $action String: log action
+ * @param $title Mixed: Title object or null
+ * @param $skin Mixed: Skin object or null. If null, we want to use the wiki
+ * content language, since that will go to the IRC feed.
+ * @param $params Array: parameters
+ * @param $filterWikilinks Boolean: whether to filter wiki links
* @return HTML string
*/
- public static function actionText( $type, $action, $title = null, $skin = null,
- $params = array(), $filterWikilinks = false )
+ public static function actionText( $type, $action, $title = null, $skin = null,
+ $params = array(), $filterWikilinks = false )
{
- global $wgLang, $wgContLang, $wgLogActions, $wgMessageCache;
+ global $wgLang, $wgContLang, $wgLogActions;
- $wgMessageCache->loadAllMessages();
$key = "$type/$action";
# Defer patrol log to PatrolLog class
if( $key == 'patrol/patrol' ) {
@@ -187,16 +208,18 @@ class LogPage {
$rightsnone = wfMsg( 'rightsnone' );
foreach ( $params as &$param ) {
$groupArray = array_map( 'trim', explode( ',', $param ) );
- $groupArray = array_map( array( 'User', 'getGroupName' ), $groupArray );
+ $groupArray = array_map( array( 'User', 'getGroupMember' ), $groupArray );
$param = $wgLang->listToText( $groupArray );
}
} else {
$rightsnone = wfMsgForContent( 'rightsnone' );
}
- if( !isset( $params[0] ) || trim( $params[0] ) == '' )
+ if( !isset( $params[0] ) || trim( $params[0] ) == '' ) {
$params[0] = $rightsnone;
- if( !isset( $params[1] ) || trim( $params[1] ) == '' )
+ }
+ if( !isset( $params[1] ) || trim( $params[1] ) == '' ) {
$params[1] = $rightsnone;
+ }
}
if( count( $params ) == 0 ) {
if ( $skin ) {
@@ -210,15 +233,16 @@ class LogPage {
// User suppression
if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) {
if ( $skin ) {
- $params[1] = '<span title="' . htmlspecialchars( $params[1] ). '">' .
+ $params[1] = '<span class="blockExpiry" dir="ltr" title="' . htmlspecialchars( $params[1] ). '">' .
$wgLang->translateBlockExpiry( $params[1] ) . '</span>';
} else {
$params[1] = $wgContLang->translateBlockExpiry( $params[1] );
}
- $params[2] = isset( $params[2] ) ?
+ $params[2] = isset( $params[2] ) ?
self::formatBlockFlags( $params[2], is_null( $skin ) ) : '';
+
// Page protections
- } else if ( $type == 'protect' && count($params) == 3 ) {
+ } elseif ( $type == 'protect' && count($params) == 3 ) {
// Restrictions and expiries
if( $skin ) {
$details .= htmlspecialchars( " {$params[1]}" );
@@ -228,13 +252,14 @@ class LogPage {
// Cascading flag...
if( $params[2] ) {
if ( $skin ) {
- $details .= ' ['.wfMsg('protect-summary-cascade').']';
+ $details .= ' [' . wfMsg( 'protect-summary-cascade' ) . ']';
} else {
- $details .= ' ['.wfMsgForContent('protect-summary-cascade').']';
+ $details .= ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
}
}
+
// Page moves
- } else if ( $type == 'move' && count( $params ) == 3 ) {
+ } elseif ( $type == 'move' && count( $params ) == 3 ) {
if( $params[2] ) {
if ( $skin ) {
$details .= ' [' . wfMsg( 'move-redirect-suppressed' ) . ']';
@@ -242,19 +267,22 @@ class LogPage {
$details .= ' [' . wfMsgForContent( 'move-redirect-suppressed' ) . ']';
}
}
+
// Revision deletion
- } else if ( preg_match( '/^(delete|suppress)\/revision$/', $key ) && count( $params ) == 5 ) {
+ } elseif ( preg_match( '/^(delete|suppress)\/revision$/', $key ) && count( $params ) == 5 ) {
$count = substr_count( $params[2], ',' ) + 1; // revisions
$ofield = intval( substr( $params[3], 7 ) ); // <ofield=x>
$nfield = intval( substr( $params[4], 7 ) ); // <nfield=x>
- $details .= ': '.RevisionDeleter::getLogMessage( $count, $nfield, $ofield, false );
+ $details .= ': ' . RevisionDeleter::getLogMessage( $count, $nfield, $ofield, false, is_null( $skin ) );
+
// Log deletion
- } else if ( preg_match( '/^(delete|suppress)\/event$/', $key ) && count( $params ) == 4 ) {
+ } elseif ( preg_match( '/^(delete|suppress)\/event$/', $key ) && count( $params ) == 4 ) {
$count = substr_count( $params[1], ',' ) + 1; // log items
$ofield = intval( substr( $params[2], 7 ) ); // <ofield=x>
$nfield = intval( substr( $params[3], 7 ) ); // <nfield=x>
- $details .= ': '.RevisionDeleter::getLogMessage( $count, $nfield, $ofield, true );
+ $details .= ': ' . RevisionDeleter::getLogMessage( $count, $nfield, $ofield, true, is_null( $skin ) );
}
+
if ( $skin ) {
$rv = wfMsgHtml( $wgLogActions[$key], $params ) . $details;
} else {
@@ -272,24 +300,24 @@ class LogPage {
$rv = "$action";
}
}
-
+
// For the perplexed, this feature was added in r7855 by Erik.
- // The feature was added because we liked adding [[$1]] in our log entries
- // but the log entries are parsed as Wikitext on RecentChanges but as HTML
- // on Special:Log. The hack is essentially that [[$1]] represented a link
- // to the title in question. The first parameter to the HTML version (Special:Log)
- // is that link in HTML form, and so this just gets rid of the ugly [[]].
- // However, this is a horrible hack and it doesn't work like you expect if, say,
- // you want to link to something OTHER than the title of the log entry.
- // The real problem, which Erik was trying to fix (and it sort-of works now) is
- // that the same messages are being treated as both wikitext *and* HTML.
+ // The feature was added because we liked adding [[$1]] in our log entries
+ // but the log entries are parsed as Wikitext on RecentChanges but as HTML
+ // on Special:Log. The hack is essentially that [[$1]] represented a link
+ // to the title in question. The first parameter to the HTML version (Special:Log)
+ // is that link in HTML form, and so this just gets rid of the ugly [[]].
+ // However, this is a horrible hack and it doesn't work like you expect if, say,
+ // you want to link to something OTHER than the title of the log entry.
+ // The real problem, which Erik was trying to fix (and it sort-of works now) is
+ // that the same messages are being treated as both wikitext *and* HTML.
if( $filterWikilinks ) {
- $rv = str_replace( "[[", "", $rv );
- $rv = str_replace( "]]", "", $rv );
+ $rv = str_replace( '[[', '', $rv );
+ $rv = str_replace( ']]', '', $rv );
}
return $rv;
}
-
+
protected static function getTitleLink( $type, $skin, $title, &$params ) {
global $wgLang, $wgContLang, $wgUserrightsInterwikiDelimiter;
if( !$skin ) {
@@ -298,7 +326,7 @@ class LogPage {
switch( $type ) {
case 'move':
$titleLink = $skin->link(
- $title,
+ $title,
htmlspecialchars( $title->getPrefixedText() ),
array(),
array( 'redirect' => 'no' )
@@ -331,8 +359,9 @@ class LogPage {
if ( count( $parts ) == 2 ) {
$titleLink = WikiMap::foreignUserLink( $parts[1], $parts[0],
htmlspecialchars( $title->getPrefixedText() ) );
- if ( $titleLink !== false )
+ if ( $titleLink !== false ) {
break;
+ }
}
$titleLink = $skin->link( Title::makeTitle( NS_USER, $text ) );
break;
@@ -354,7 +383,7 @@ class LogPage {
list( $name, $par ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
# Use the language name for log titles, rather than Log/X
if( $name == 'Log' ) {
- $titleLink = '('.$skin->link( $title, LogPage::logName( $par ) ).')';
+ $titleLink = '(' . $skin->link( $title, LogPage::logName( $par ) ) . ')';
} else {
$titleLink = $skin->link( $title );
}
@@ -367,48 +396,60 @@ class LogPage {
/**
* Add a log entry
- * @param string $action one of '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'move_redir'
- * @param object &$target A title object.
- * @param string $comment Description associated
- * @param array $params Parameters passed later to wfMsg.* functions
- * @param User $doer The user doing the action
+ *
+ * @param $action String: one of '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'move_redir'
+ * @param $target Title object
+ * @param $comment String: description associated
+ * @param $params Array: parameters passed later to wfMsg.* functions
+ * @param $doer User object: the user doing the action
*/
public function addEntry( $action, $target, $comment, $params = array(), $doer = null ) {
if ( !is_array( $params ) ) {
$params = array( $params );
}
- if ( $comment === null ) $comment = "";
+ if ( $comment === null ) {
+ $comment = '';
+ }
$this->action = $action;
$this->target = $target;
$this->comment = $comment;
$this->params = LogPage::makeParamBlob( $params );
-
- if ($doer === null) {
+
+ if ( $doer === null ) {
global $wgUser;
$doer = $wgUser;
- } elseif (!is_object( $doer ) ) {
+ } elseif ( !is_object( $doer ) ) {
$doer = User::newFromId( $doer );
}
-
+
$this->doer = $doer;
$this->actionText = LogPage::actionText( $this->type, $action, $target, null, $params );
return $this->saveContent();
}
-
+
/**
* Add relations to log_search table
- * @static
+ *
+ * @param $field String
+ * @param $values Array
+ * @param $logid Integer
+ * @return Boolean
*/
public function addRelations( $field, $values, $logid ) {
- if( !strlen($field) || empty($values) )
+ if( !strlen( $field ) || empty( $values ) ) {
return false; // nothing
+ }
$data = array();
foreach( $values as $value ) {
- $data[] = array('ls_field' => $field,'ls_value' => $value,'ls_log_id' => $logid);
+ $data[] = array(
+ 'ls_field' => $field,
+ 'ls_value' => $value,
+ 'ls_log_id' => $logid
+ );
}
$dbw = wfGetDB( DB_MASTER );
$dbw->insert( 'log_search', $data, __METHOD__, 'IGNORE' );
@@ -417,7 +458,9 @@ class LogPage {
/**
* Create a blob from a parameter array
- * @static
+ *
+ * @param $params Array
+ * @return String
*/
public static function makeParamBlob( $params ) {
return implode( "\n", $params );
@@ -425,7 +468,9 @@ class LogPage {
/**
* Extract a parameter array from a blob
- * @static
+ *
+ * @param $blob String
+ * @return Array
*/
public static function extractParams( $blob ) {
if ( $blob === '' ) {
@@ -442,15 +487,16 @@ class LogPage {
* @param $flags Flags to format
* @param $forContent Whether to localize the message depending of the user
* language
- * @return string
+ * @return String
*/
public static function formatBlockFlags( $flags, $forContent = false ) {
global $wgLang;
$flags = explode( ',', trim( $flags ) );
if( count( $flags ) > 0 ) {
- for( $i = 0; $i < count( $flags ); $i++ )
+ for( $i = 0; $i < count( $flags ); $i++ ) {
$flags[$i] = self::formatBlockFlag( $flags[$i], $forContent );
+ }
return '(' . $wgLang->commaList( $flags ) . ')';
} else {
return '';
@@ -463,16 +509,17 @@ class LogPage {
* @param $flag Flag to translate
* @param $forContent Whether to localize the message depending of the user
* language
- * @return string
+ * @return String
*/
public static function formatBlockFlag( $flag, $forContent = false ) {
static $messages = array();
if( !isset( $messages[$flag] ) ) {
$k = 'block-log-flags-' . $flag;
- if( $forContent )
+ if( $forContent ) {
$msg = wfMsgForContent( $k );
- else
+ } else {
$msg = wfMsg( $k );
+ }
$messages[$flag] = htmlspecialchars( wfEmptyMsg( $k, $msg ) ? $flag : $msg );
}
return $messages[$flag];
diff --git a/includes/MacBinary.php b/includes/MacBinary.php
index b5b11e6c..0c38a641 100644
--- a/includes/MacBinary.php
+++ b/includes/MacBinary.php
@@ -35,7 +35,7 @@ class MacBinary {
* The file must be seekable, such as local filesystem.
* Remote URLs probably won't work.
*
- * @param string $filename
+ * @param $filename String
*/
function open( $filename ) {
$this->valid = false;
@@ -48,7 +48,8 @@ class MacBinary {
/**
* Does this appear to be a valid MacBinary archive?
- * @return bool
+ *
+ * @return Boolean
*/
function isValid() {
return $this->valid;
@@ -56,7 +57,8 @@ class MacBinary {
/**
* Get length of data fork
- * @return int
+ *
+ * @return Integer
*/
function dataForkLength() {
return $this->dataLength;
@@ -64,8 +66,9 @@ class MacBinary {
/**
* Copy the data fork to an external file or resource.
- * @param resource $destination
- * @return bool
+ *
+ * @param $destination Ressource
+ * @return Boolean
*/
function extractData( $destination ) {
if( !$this->isValid() ) {
@@ -173,9 +176,9 @@ class MacBinary {
* with magic array thingy by Jim Van Verth.
* http://search.cpan.org/~eryq/Convert-BinHex-1.119/lib/Convert/BinHex.pm
*
- * @param string $data
- * @param int $seed
- * @return int
+ * @param $data String
+ * @param $seed Integer
+ * @return Integer
* @access private
*/
function calcCRC( $data, $seed = 0 ) {
@@ -226,9 +229,9 @@ class MacBinary {
}
/**
- * @param resource $destination
- * @param int $bytesToCopy
- * @return bool
+ * @param $destination Resource
+ * @param $bytesToCopy Integer
+ * @return Boolean
* @access private
*/
function copyBytesTo( $destination, $bytesToCopy ) {
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index d741832f..31d83332 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -1,6 +1,7 @@
<?php
/**
* File for magic words
+ *
* See docs/magicword.txt
*
* @file
@@ -60,6 +61,7 @@ class MagicWord {
'numberofarticles',
'numberoffiles',
'numberofedits',
+ 'articlepath',
'sitename',
'server',
'servername',
@@ -79,6 +81,7 @@ class MagicWord {
'revisionday',
'revisionday2',
'revisionmonth',
+ 'revisionmonth1',
'revisionyear',
'revisiontimestamp',
'revisionuser',
@@ -175,7 +178,7 @@ class MagicWord {
/**#@-*/
- function __construct($id = 0, $syn = '', $cs = false) {
+ function __construct($id = 0, $syn = array(), $cs = false) {
$this->mId = $id;
$this->mSynonyms = (array)$syn;
$this->mCaseSensitive = $cs;
@@ -269,13 +272,13 @@ class MagicWord {
* @private
*/
function initRegex() {
- #$variableClass = Title::legalChars();
- # This was used for matching "$1" variables, but different uses of the feature will have
- # different restrictions, which should be checked *after* the MagicWord has been matched,
- # not here. - IMSoP
+ // Sort the synonyms by length, descending, so that the longest synonym
+ // matches in precedence to the shortest
+ $synonyms = $this->mSynonyms;
+ usort( $synonyms, array( $this, 'compareStringLength' ) );
$escSyn = array();
- foreach ( $this->mSynonyms as $synonym )
+ foreach ( $synonyms as $synonym )
// In case a magic word contains /, like that's going to happen;)
$escSyn[] = preg_quote( $synonym, '/' );
$this->mBaseRegex = implode( '|', $escSyn );
@@ -289,6 +292,23 @@ class MagicWord {
}
/**
+ * A comparison function that returns -1, 0 or 1 depending on whether the
+ * first string is longer, the same length or shorter than the second
+ * string.
+ */
+ function compareStringLength( $s1, $s2 ) {
+ $l1 = strlen( $s1 );
+ $l2 = strlen( $s2 );
+ if ( $l1 < $l2 ) {
+ return 1;
+ } elseif ( $l1 > $l2 ) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
* Gets a regex representing matching the word
*/
function getRegex() {
@@ -513,7 +533,6 @@ class MagicWordArray {
* Add a magic word by name
*/
public function add( $name ) {
- global $wgContLang;
$this->names[] = $name;
$this->hash = $this->baseRegex = $this->regex = null;
}
@@ -646,7 +665,6 @@ class MagicWordArray {
}
// This shouldn't happen either
throw new MWException( __METHOD__.': parameter not found' );
- return array( false, false );
}
/**
@@ -656,7 +674,6 @@ class MagicWordArray {
* Both elements are false if there was no match.
*/
public function matchVariableStartToEnd( $text ) {
- global $wgContLang;
$regexes = $this->getVariableStartToEndRegex();
foreach ( $regexes as $regex ) {
if ( $regex !== '' ) {
@@ -720,7 +737,7 @@ class MagicWordArray {
continue;
}
if ( preg_match( $regex, $text, $m ) ) {
- list( $id, $param ) = $this->parseMatch( $m );
+ list( $id, ) = $this->parseMatch( $m );
if ( strlen( $m[0] ) >= strlen( $text ) ) {
$text = '';
} else {
diff --git a/includes/Math.php b/includes/Math.php
index 8cf9b8d8..0e136d5f 100644
--- a/includes/Math.php
+++ b/includes/Math.php
@@ -37,7 +37,8 @@ class MathRenderer {
if( $this->mode == MW_MATH_SOURCE ) {
# No need to render or parse anything more!
- return ('$ '.htmlspecialchars( $this->tex ).' $');
+ # New lines are replaced with spaces, which avoids confusing our parser (bugs 23190, 22818)
+ return ('<span class="tex">$ ' . str_replace( "\n", " ", htmlspecialchars( $this->tex ) ) . ' $</span>');
}
if( $this->tex == '' ) {
return; # bug 8372
@@ -55,7 +56,7 @@ class MathRenderer {
}
}
- if( function_exists( 'is_executable' ) && !is_executable( $wgTexvc ) ) {
+ if( !is_executable( $wgTexvc ) ) {
return $this->_error( 'math_notexvc' );
}
$cmd = $wgTexvc . ' ' .
@@ -71,7 +72,7 @@ class MathRenderer {
}
wfDebug( "TeX: $cmd\n" );
- $contents = `$cmd`;
+ $contents = wfShellExec( $cmd );
wfDebug( "TeX output:\n $contents\n---\n" );
if (strlen($contents) == 0) {
@@ -153,7 +154,10 @@ class MathRenderer {
$hashpath = $this->_getHashPath();
if( !file_exists( $hashpath ) ) {
- if( !@wfMkdirParents( $hashpath, 0755 ) ) {
+ wfSuppressWarnings();
+ $ret = wfMkdirParents( $hashpath, 0755 );
+ wfRestoreWarnings();
+ if( !$ret ) {
return $this->_error( 'math_bad_output' );
}
} elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
@@ -231,7 +235,9 @@ class MathRenderer {
if( file_exists( $filename ) ) {
if( filesize( $filename ) == 0 ) {
// Some horrible error corrupted stuff :(
- @unlink( $filename );
+ wfSuppressWarnings();
+ unlink( $filename );
+ wfRestoreWarnings();
} else {
return true;
}
@@ -241,7 +247,10 @@ class MathRenderer {
$hashpath = $this->_getHashPath();
if( !file_exists( $hashpath ) ) {
- if( !@wfMkdirParents( $hashpath, 0755 ) ) {
+ wfSuppressWarnings();
+ $ret = wfMkdirParents( $hashpath, 0755 );
+ wfRestoreWarnings();
+ if( !$ret ) {
return false;
}
} elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
@@ -323,10 +332,10 @@ class MathRenderer {
.'/'. substr($this->hash, 2, 1);
}
- public static function renderMath( $tex, $params=array() ) {
- global $wgUser;
+ public static function renderMath( $tex, $params=array(), ParserOptions $parserOptions = null ) {
$math = new MathRenderer( $tex, $params );
- $math->setOutputMode( $wgUser->getOption('math'));
+ if ( $parserOptions )
+ $math->setOutputMode( $parserOptions->getMath() );
return $math->render();
}
}
diff --git a/includes/MemcachedSessions.php b/includes/MemcachedSessions.php
index e3bcea1b..4f10f99c 100644
--- a/includes/MemcachedSessions.php
+++ b/includes/MemcachedSessions.php
@@ -11,30 +11,42 @@
*/
/**
- * @todo document
+ * Get a cache key for the given session id.
+ *
+ * @param $id String: session id
+ * @return String: cache key
*/
function memsess_key( $id ) {
return wfMemcKey( 'session', $id );
}
/**
- * @todo document
+ * Callback when opening a session.
+ * NOP: $wgMemc should be set up already.
+ *
+ * @param $save_path String: path used to store session files, unused
+ * @param $session_name String: session name
+ * @return Boolean: success
*/
function memsess_open( $save_path, $session_name ) {
- # NOP, $wgMemc should be set up already
return true;
}
/**
- * @todo document
+ * Callback when closing a session.
+ * NOP.
+ *
+ * @return Boolean: success
*/
function memsess_close() {
- # NOP
return true;
}
/**
- * @todo document
+ * Callback when reading session data.
+ *
+ * @param $id String: session id
+ * @return Mixed: session data
*/
function memsess_read( $id ) {
global $wgMemc;
@@ -44,7 +56,11 @@ function memsess_read( $id ) {
}
/**
- * @todo document
+ * Callback when writing session data.
+ *
+ * @param $id String: session id
+ * @param $data Mixed: session data
+ * @return Boolean: success
*/
function memsess_write( $id, $data ) {
global $wgMemc;
@@ -53,19 +69,26 @@ function memsess_write( $id, $data ) {
}
/**
- * @todo document
+ * Callback to destroy a session when calling session_destroy().
+ *
+ * @param $id String: session id
+ * @return Boolean: success
*/
function memsess_destroy( $id ) {
global $wgMemc;
+
$wgMemc->delete( memsess_key( $id ) );
return true;
}
/**
- * @todo document
+ * Callback to execute garbage collection.
+ * NOP: Memcached performs garbage collection.
+ *
+ * @param $maxlifetime Integer: maximum session life time
+ * @return Boolean: success
*/
function memsess_gc( $maxlifetime ) {
- # NOP: Memcached performs garbage collection.
return true;
}
diff --git a/includes/Message.php b/includes/Message.php
new file mode 100644
index 00000000..72232ec9
--- /dev/null
+++ b/includes/Message.php
@@ -0,0 +1,360 @@
+<?php
+/**
+ * This class provides methods for fetching interface messages and
+ * processing them into variety of formats that are needed in MediaWiki.
+ *
+ * It is intented to replace the old wfMsg* functions that over time grew
+ * unusable.
+ *
+ * Examples:
+ * Fetching a message text for interface message
+ * $button = Xml::button( wfMessage( 'submit' )->text() );
+ * </pre>
+ * Messages can have parameters:
+ * wfMessage( 'welcome-to' )->params( $wgSitename )->text();
+ * {{GRAMMAR}} and friends work correctly
+ * wfMessage( 'are-friends', $user, $friend );
+ * wfMessage( 'bad-message' )->rawParams( '<script>...</script>' )->escaped();
+ * </pre>
+ * Sometimes the message text ends up in the database, so content language is needed.
+ * wfMessage( 'file-log', $user, $filename )->inContentLanguage()->text()
+ * </pre>
+ * Checking if message exists:
+ * wfMessage( 'mysterious-message' )->exists()
+ * </pre>
+ * If you want to use a different language:
+ * wfMessage( 'email-header' )->inLanguage( $user->getOption( 'language' ) )->plain()
+ * Note that you cannot parse the text except in the content or interface
+ * languages
+ * </pre>
+ *
+ *
+ * Comparison with old wfMsg* functions:
+ *
+ * Use full parsing.
+ * wfMsgExt( 'key', array( 'parseinline' ), 'apple' );
+ * === wfMessage( 'key', 'apple' )->parse();
+ * </pre>
+ * Parseinline is used because it is more useful when pre-building html.
+ * In normal use it is better to use OutputPage::(add|wrap)WikiMsg.
+ *
+ * Places where html cannot be used. {{-transformation is done.
+ * wfMsgExt( 'key', array( 'parsemag' ), 'apple', 'pear' );
+ * === wfMessage( 'key', 'apple', 'pear' )->text();
+ * </pre>
+ *
+ * Shortcut for escaping the message too, similar to wfMsgHTML, but
+ * parameters are not replaced after escaping by default.
+ * $escaped = wfMessage( 'key' )->rawParams( 'apple' )->escaped();
+ * </pre>
+ *
+ * TODO:
+ * - test, can we have tests?
+ * - sort out the details marked with fixme
+ *
+ * @since 1.17
+ * @author Niklas Laxström
+ */
+class Message {
+ /**
+ * In which language to get this message. True, which is the default,
+ * means the current interface language, false content language.
+ */
+ protected $interface = true;
+
+ /**
+ * In which language to get this message. Overrides the $interface
+ * variable.
+ */
+ protected $language = null;
+
+ /**
+ * The message key.
+ */
+ protected $key;
+
+ /**
+ * List of parameters which will be substituted into the message.
+ */
+ protected $parameters = array();
+
+ /**
+ * Format for the message.
+ * Supported formats are:
+ * * text (transform)
+ * * escaped (transform+htmlspecialchars)
+ * * block-parse
+ * * parse (default)
+ * * plain
+ */
+ protected $format = 'parse';
+
+ /**
+ * Whether database can be used.
+ */
+ protected $useDatabase = true;
+
+ /**
+ * Constructor.
+ * @param $key String: message key
+ * @param $params Array message parameters
+ * @return Message: $this
+ */
+ public function __construct( $key, $params = array() ) {
+ global $wgLang;
+ $this->key = $key;
+ $this->parameters = array_values( $params );
+ $this->language = $wgLang;
+ }
+
+ /**
+ * Factory function that is just wrapper for the real constructor. It is
+ * intented to be used instead of the real constructor, because it allows
+ * chaining method calls, while new objects don't.
+ * @param $key String: message key
+ * @param Varargs: parameters as Strings
+ * @return Message: $this
+ */
+ public static function newFromKey( $key /*...*/ ) {
+ $params = func_get_args();
+ array_shift( $params );
+ return new self( $key, $params );
+ }
+
+ /**
+ * Adds parameters to the parameter list of this message.
+ * @param Varargs: parameters as Strings
+ * @return Message: $this
+ */
+ public function params( /*...*/ ) {
+ $args_values = array_values( func_get_args() );
+ $this->parameters = array_merge( $this->parameters, $args_values );
+ return $this;
+ }
+
+ /**
+ * Add parameters that are substituted after parsing or escaping.
+ * In other words the parsing process cannot access the contents
+ * of this type of parameter, and you need to make sure it is
+ * sanitized beforehand. The parser will see "$n", instead.
+ * @param Varargs: raw parameters as Strings
+ * @return Message: $this
+ */
+ public function rawParams( /*...*/ ) {
+ $params = func_get_args();
+ foreach( $params as $param ) {
+ $this->parameters[] = self::rawParam( $param );
+ }
+ return $this;
+ }
+
+ /**
+ * Request the message in any language that is supported.
+ * As a side effect interface message status is unconditionally
+ * turned off.
+ * @param $lang Mixed: language code or Language object.
+ * @return Message: $this
+ */
+ public function inLanguage( $lang ) {
+ if( $lang instanceof Language ){
+ $this->language = $lang;
+ } elseif ( is_string( $lang ) ) {
+ if( $this->language->getCode() != $lang ) {
+ $this->language = Language::factory( $lang );
+ }
+ } else {
+ $type = gettype( $lang );
+ throw new MWException( __METHOD__ . " must be "
+ . "passed a String or Language object; $type given"
+ );
+ }
+ $this->interface = false;
+ return $this;
+ }
+
+ /**
+ * Request the message in the wiki's content language.
+ * @return Message: $this
+ */
+ public function inContentLanguage() {
+ global $wgContLang;
+ $this->interface = false;
+ $this->language = $wgContLang;
+ return $this;
+ }
+
+ /**
+ * Enable or disable database use.
+ * @param $value Boolean
+ * @return Message: $this
+ */
+ public function useDatabase( $value ) {
+ $this->useDatabase = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Returns the message parsed from wikitext to HTML.
+ * TODO: in PHP >= 5.2.0, we can make this a magic method,
+ * and then we can do, eg:
+ * $foo = Message::get($key);
+ * $string = "<abbr>$foo</abbr>";
+ * But we shouldn't implement that while MediaWiki still supports
+ * PHP < 5.2; or people will start using it...
+ * @return String: HTML
+ */
+ public function toString() {
+ $string = $this->getMessageText();
+
+ # Replace parameters before text parsing
+ $string = $this->replaceParameters( $string, 'before' );
+
+ # Maybe transform using the full parser
+ if( $this->format === 'parse' ) {
+ $string = $this->parseText( $string );
+ $m = array();
+ if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
+ $string = $m[1];
+ }
+ } elseif( $this->format === 'block-parse' ){
+ $string = $this->parseText( $string );
+ } elseif( $this->format === 'text' ){
+ $string = $this->transformText( $string );
+ } elseif( $this->format === 'escaped' ){
+ # FIXME: Sanitizer method here?
+ $string = $this->transformText( $string );
+ $string = htmlspecialchars( $string );
+ }
+
+ # Raw parameter replacement
+ $string = $this->replaceParameters( $string, 'after' );
+
+ return $string;
+ }
+
+ /**
+ * Fully parse the text from wikitext to HTML
+ * @return String parsed HTML
+ */
+ public function parse() {
+ $this->format = 'parse';
+ return $this->toString();
+ }
+
+ /**
+ * Returns the message text. {{-transformation is done.
+ * @return String: Unescaped message text.
+ */
+ public function text() {
+ $this->format = 'text';
+ return $this->toString();
+ }
+
+ /**
+ * Returns the message text as-is, only parameters are subsituted.
+ * @return String: Unescaped untransformed message text.
+ */
+ public function plain() {
+ $this->format = 'plain';
+ return $this->toString();
+ }
+
+ /**
+ * Returns the parsed message text which is always surrounded by a block element.
+ * @return String: HTML
+ */
+ public function parseAsBlock() {
+ $this->format = 'block-parse';
+ return $this->toString();
+ }
+
+ /**
+ * Returns the message text. {{-transformation is done and the result
+ * is escaped excluding any raw parameters.
+ * @return String: Escaped message text.
+ */
+ public function escaped() {
+ $this->format = 'escaped';
+ return $this->toString();
+ }
+
+ /**
+ * Check whether a message key has been defined currently.
+ * @return Bool: true if it is and false if not.
+ */
+ public function exists() {
+ return $this->fetchMessage() !== false;
+ }
+
+ public static function rawParam( $value ) {
+ return array( 'raw' => $value );
+ }
+
+ /**
+ * Substitutes any paramaters into the message text.
+ * @param $message String, the message text
+ * @param $type String: either before or after
+ * @return String
+ */
+ protected function replaceParameters( $message, $type = 'before' ) {
+ $replacementKeys = array();
+ foreach( $this->parameters as $n => $param ) {
+ if ( $type === 'before' && !is_array( $param ) ) {
+ $replacementKeys['$' . ($n + 1)] = $param;
+ } elseif ( $type === 'after' && isset( $param['raw'] ) ) {
+ $replacementKeys['$' . ($n + 1)] = $param['raw'];
+ }
+ }
+ $message = strtr( $message, $replacementKeys );
+ return $message;
+ }
+
+ /**
+ * Wrapper for what ever method we use to parse wikitext.
+ * @param $string String: Wikitext message contents
+ * @return Wikitext parsed into HTML
+ */
+ protected function parseText( $string ) {
+ global $wgOut, $wgLang, $wgContLang;
+ if ( $this->language !== $wgLang && $this->language !== $wgContLang ) {
+ # FIXME: remove this limitation
+ throw new MWException( 'Can only parse in interface or content language' );
+ }
+ return $wgOut->parse( $string, /*linestart*/true, $this->interface );
+ }
+
+ /**
+ * Wrapper for what ever method we use to {{-transform wikitext.
+ * @param $string String: Wikitext message contents
+ * @return Wikitext with {{-constructs replaced with their values.
+ */
+ protected function transformText( $string ) {
+ global $wgMessageCache;
+ return $wgMessageCache->transform( $string, $this->interface, $this->language );
+ }
+
+ /**
+ * Returns the textual value for the message.
+ * @return Message contents or placeholder
+ */
+ protected function getMessageText() {
+ $message = $this->fetchMessage();
+ if ( $message === false ) {
+ return '&lt;' . htmlspecialchars( $this->key ) . '&gt;';
+ } else {
+ return $message;
+ }
+ }
+
+ /**
+ * Wrapper for what ever method we use to get message contents
+ */
+ protected function fetchMessage() {
+ if ( !isset( $this->message ) ) {
+ global $wgMessageCache;
+ $this->message = $wgMessageCache->get( $this->key, $this->useDatabase, $this->language );
+ }
+ return $this->message;
+ }
+
+} \ No newline at end of file
diff --git a/includes/MessageBlobStore.php b/includes/MessageBlobStore.php
new file mode 100644
index 00000000..5e6c8e5e
--- /dev/null
+++ b/includes/MessageBlobStore.php
@@ -0,0 +1,370 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @author Roan Kattouw
+ * @author Trevor Parscal
+ */
+
+/**
+ * This class provides access to the resource message blobs storage used by
+ * the ResourceLoader.
+ *
+ * A message blob is a JSON object containing the interface messages for a
+ * certain resource in a certain language. These message blobs are cached
+ * in the msg_resource table and automatically invalidated when one of their
+ * consistuent messages or the resource itself is changed.
+ */
+class MessageBlobStore {
+
+ /**
+ * Get the message blobs for a set of modules
+ *
+ * @param $resourceLoader ResourceLoader object
+ * @param $modules array Array of module objects keyed by module name
+ * @param $lang string Language code
+ * @return array An array mapping module names to message blobs
+ */
+ public static function get( ResourceLoader $resourceLoader, $modules, $lang ) {
+ wfProfileIn( __METHOD__ );
+ if ( !count( $modules ) ) {
+ wfProfileOut( __METHOD__ );
+ return array();
+ }
+ // Try getting from the DB first
+ $blobs = self::getFromDB( $resourceLoader, array_keys( $modules ), $lang );
+
+ // Generate blobs for any missing modules and store them in the DB
+ $missing = array_diff( array_keys( $modules ), array_keys( $blobs ) );
+ foreach ( $missing as $name ) {
+ $blob = self::insertMessageBlob( $name, $modules[$name], $lang );
+ if ( $blob ) {
+ $blobs[$name] = $blob;
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $blobs;
+ }
+
+ /**
+ * Generate and insert a new message blob. If the blob was already
+ * present, it is not regenerated; instead, the preexisting blob
+ * is fetched and returned.
+ *
+ * @param $name String: module name
+ * @param $module ResourceLoaderModule object
+ * @param $lang String: language code
+ * @return mixed Message blob or false if the module has no messages
+ */
+ public static function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
+ $blob = self::generateMessageBlob( $module, $lang );
+
+ if ( !$blob ) {
+ return false;
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+ $success = $dbw->insert( 'msg_resource', array(
+ 'mr_lang' => $lang,
+ 'mr_resource' => $name,
+ 'mr_blob' => $blob,
+ 'mr_timestamp' => $dbw->timestamp()
+ ),
+ __METHOD__,
+ array( 'IGNORE' )
+ );
+
+ if ( $success ) {
+ if ( $dbw->affectedRows() == 0 ) {
+ // Blob was already present, fetch it
+ $blob = $dbw->selectField( 'msg_resource', 'mr_blob', array(
+ 'mr_resource' => $name,
+ 'mr_lang' => $lang,
+ ),
+ __METHOD__
+ );
+ } else {
+ // Update msg_resource_links
+ $rows = array();
+
+ foreach ( $module->getMessages() as $key ) {
+ $rows[] = array(
+ 'mrl_resource' => $name,
+ 'mrl_message' => $key
+ );
+ }
+ $dbw->insert( 'msg_resource_links', $rows,
+ __METHOD__, array( 'IGNORE' )
+ );
+ }
+ }
+
+ return $blob;
+ }
+
+ /**
+ * Update all message blobs for a given module.
+ *
+ * @param $name String: module name
+ * @param $module ResourceLoaderModule object
+ * @param $lang String: language code (optional)
+ * @return Mixed: if $lang is set, the new message blob for that language is
+ * returned if present. Otherwise, null is returned.
+ */
+ public static function updateModule( $name, ResourceLoaderModule $module, $lang = null ) {
+ $retval = null;
+
+ // Find all existing blobs for this module
+ $dbw = wfGetDB( DB_MASTER );
+ $res = $dbw->select( 'msg_resource',
+ array( 'mr_lang', 'mr_blob' ),
+ array( 'mr_resource' => $name ),
+ __METHOD__
+ );
+
+ // Build the new msg_resource rows
+ $newRows = array();
+ $now = $dbw->timestamp();
+ // Save the last-processed old and new blobs for later
+ $oldBlob = $newBlob = null;
+
+ foreach ( $res as $row ) {
+ $oldBlob = $row->mr_blob;
+ $newBlob = self::generateMessageBlob( $module, $row->mr_lang );
+
+ if ( $row->mr_lang === $lang ) {
+ $retval = $newBlob;
+ }
+ $newRows[] = array(
+ 'mr_resource' => $name,
+ 'mr_lang' => $row->mr_lang,
+ 'mr_blob' => $newBlob,
+ 'mr_timestamp' => $now
+ );
+ }
+
+ $dbw->replace( 'msg_resource',
+ array( array( 'mr_resource', 'mr_lang' ) ),
+ $newRows, __METHOD__
+ );
+
+ // Figure out which messages were added and removed
+ $oldMessages = array_keys( FormatJson::decode( $oldBlob, true ) );
+ $newMessages = array_keys( FormatJson::decode( $newBlob, true ) );
+ $added = array_diff( $newMessages, $oldMessages );
+ $removed = array_diff( $oldMessages, $newMessages );
+
+ // Delete removed messages, insert added ones
+ if ( $removed ) {
+ $dbw->delete( 'msg_resource_links', array(
+ 'mrl_resource' => $name,
+ 'mrl_message' => $removed
+ ), __METHOD__
+ );
+ }
+
+ $newLinksRows = array();
+
+ foreach ( $added as $message ) {
+ $newLinksRows[] = array(
+ 'mrl_resource' => $name,
+ 'mrl_message' => $message
+ );
+ }
+
+ if ( $newLinksRows ) {
+ $dbw->insert( 'msg_resource_links', $newLinksRows, __METHOD__,
+ array( 'IGNORE' ) // just in case
+ );
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Update a single message in all message blobs it occurs in.
+ *
+ * @param $key String: message key
+ */
+ public static function updateMessage( $key ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ // Keep running until the updates queue is empty.
+ // Due to update conflicts, the queue might not be emptied
+ // in one iteration.
+ $updates = null;
+ do {
+ $updates = self::getUpdatesForMessage( $key, $updates );
+
+ foreach ( $updates as $k => $update ) {
+ // Update the row on the condition that it
+ // didn't change since we fetched it by putting
+ // the timestamp in the WHERE clause.
+ $success = $dbw->update( 'msg_resource',
+ array(
+ 'mr_blob' => $update['newBlob'],
+ 'mr_timestamp' => $dbw->timestamp() ),
+ array(
+ 'mr_resource' => $update['resource'],
+ 'mr_lang' => $update['lang'],
+ 'mr_timestamp' => $update['timestamp'] ),
+ __METHOD__
+ );
+
+ // Only requeue conflicted updates.
+ // If update() returned false, don't retry, for
+ // fear of getting into an infinite loop
+ if ( !( $success && $dbw->affectedRows() == 0 ) ) {
+ // Not conflicted
+ unset( $updates[$k] );
+ }
+ }
+ } while ( count( $updates ) );
+
+ // No need to update msg_resource_links because we didn't add
+ // or remove any messages, we just changed their contents.
+ }
+
+ public static function clear() {
+ // TODO: Give this some more thought
+ // TODO: Is TRUNCATE better?
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'msg_resource', '*', __METHOD__ );
+ $dbw->delete( 'msg_resource_links', '*', __METHOD__ );
+ }
+
+ /**
+ * Create an update queue for updateMessage()
+ *
+ * @param $key String: message key
+ * @param $prevUpdates Array: updates queue to refresh or null to build a fresh update queue
+ * @return Array: updates queue
+ */
+ private static function getUpdatesForMessage( $key, $prevUpdates = null ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ if ( is_null( $prevUpdates ) ) {
+ // Fetch all blobs referencing $key
+ $res = $dbw->select(
+ array( 'msg_resource', 'msg_resource_links' ),
+ array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
+ array( 'mrl_message' => $key, 'mr_resource=mrl_resource' ),
+ __METHOD__
+ );
+ } else {
+ // Refetch the blobs referenced by $prevUpdates
+
+ // Reorganize the (resource, lang) pairs in the format
+ // expected by makeWhereFrom2d()
+ $twoD = array();
+
+ foreach ( $prevUpdates as $update ) {
+ $twoD[$update['resource']][$update['lang']] = true;
+ }
+
+ $res = $dbw->select( 'msg_resource',
+ array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
+ $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 'mr_lang' ),
+ __METHOD__
+ );
+ }
+
+ // Build the new updates queue
+ $updates = array();
+
+ foreach ( $res as $row ) {
+ $updates[] = array(
+ 'resource' => $row->mr_resource,
+ 'lang' => $row->mr_lang,
+ 'timestamp' => $row->mr_timestamp,
+ 'newBlob' => self::reencodeBlob( $row->mr_blob, $key, $row->mr_lang )
+ );
+ }
+
+ return $updates;
+ }
+
+ /**
+ * Reencode a message blob with the updated value for a message
+ *
+ * @param $blob String: message blob (JSON object)
+ * @param $key String: message key
+ * @param $lang String: language code
+ * @return Message blob with $key replaced with its new value
+ */
+ private static function reencodeBlob( $blob, $key, $lang ) {
+ $decoded = FormatJson::decode( $blob, true );
+ $decoded[$key] = wfMsgExt( $key, array( 'language' => $lang ) );
+
+ return FormatJson::encode( (object)$decoded );
+ }
+
+ /**
+ * Get the message blobs for a set of modules from the database.
+ * Modules whose blobs are not in the database are silently dropped.
+ *
+ * @param $resourceLoader ResourceLoader object
+ * @param $modules Array of module names
+ * @param $lang String: language code
+ * @return array Array mapping module names to blobs
+ */
+ private static function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
+ global $wgCacheEpoch;
+ $retval = array();
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'msg_resource',
+ array( 'mr_blob', 'mr_resource', 'mr_timestamp' ),
+ array( 'mr_resource' => $modules, 'mr_lang' => $lang ),
+ __METHOD__
+ );
+
+ foreach ( $res as $row ) {
+ $module = $resourceLoader->getModule( $row->mr_resource );
+ if ( !$module ) {
+ // This shouldn't be possible
+ throw new MWException( __METHOD__ . ' passed an invalid module name' );
+ }
+ // Update the module's blobs if the set of messages changed or if the blob is
+ // older than $wgCacheEpoch
+ if ( array_keys( FormatJson::decode( $row->mr_blob, true ) ) !== $module->getMessages() ||
+ wfTimestamp( TS_MW, $row->mr_timestamp ) <= $wgCacheEpoch ) {
+ $retval[$row->mr_resource] = self::updateModule( $row->mr_resource, $module, $lang );
+ } else {
+ $retval[$row->mr_resource] = $row->mr_blob;
+ }
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Generate the message blob for a given module in a given language.
+ *
+ * @param $module ResourceLoaderModule object
+ * @param $lang String: language code
+ * @return String: JSON object
+ */
+ private static function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
+ $messages = array();
+
+ foreach ( $module->getMessages() as $key ) {
+ $messages[$key] = wfMsgExt( $key, array( 'language' => $lang ) );
+ }
+
+ return FormatJson::encode( (object)$messages );
+ }
+}
diff --git a/includes/MessageCache.php b/includes/MessageCache.php
index 2c53430f..cc14ec2b 100644
--- a/includes/MessageCache.php
+++ b/includes/MessageCache.php
@@ -18,23 +18,38 @@ define( 'MSG_CACHE_VERSION', 1 );
* @ingroup Cache
*/
class MessageCache {
- // Holds loaded messages that are defined in MediaWiki namespace.
- var $mCache;
+ /**
+ * Process local cache of loaded messages that are defined in
+ * MediaWiki namespace. First array level is a language code,
+ * second level is message key and the values are either message
+ * content prefixed with space, or !NONEXISTENT for negative
+ * caching.
+ */
+ protected $mCache;
+
+ // Should mean that database cannot be used, but check
+ protected $mDisable;
+
+ /// Lifetime for cache, used by object caching
+ protected $mExpiry;
+
+ /**
+ * Message cache has it's own parser which it uses to transform
+ * messages.
+ */
+ protected $mParserOptions, $mParser;
- var $mUseCache, $mDisable, $mExpiry;
- var $mKeys, $mParserOptions, $mParser;
+ /// Variable for tracking which variables are already loaded
+ protected $mLoadedLanguages = array();
- // Variable for tracking which variables are loaded
- var $mLoadedLanguages = array();
+ function __construct( $memCached, $useDB, $expiry ) {
+ if ( !$memCached ) {
+ $memCached = wfGetCache( CACHE_NONE );
+ }
- function __construct( &$memCached, $useDB, $expiry, /*ignored*/ $memcPrefix ) {
- $this->mUseCache = !is_null( $memCached );
- $this->mMemc = &$memCached;
+ $this->mMemc = $memCached;
$this->mDisable = !$useDB;
$this->mExpiry = $expiry;
- $this->mDisableTransform = false;
- $this->mKeys = false; # initialised on demand
- $this->mParser = null;
}
@@ -139,9 +154,9 @@ class MessageCache {
fwrite($file,"<?php\n//$hash\n\n \$this->mCache = array(");
- foreach ($array as $key => $message) {
+ foreach ( $array as $key => $message ) {
$key = $this->escapeForScript($key);
- $messages = $this->escapeForScript($message);
+ $message = $this->escapeForScript($message);
fwrite($file, "'$key' => '$message',\n");
}
@@ -177,7 +192,7 @@ class MessageCache {
* When succesfully loading from (2) or (3), all higher level caches are
* updated for the newest version.
*
- * Nothing is loaded if member variable mDisabled is true, either manually
+ * Nothing is loaded if member variable mDisable is true, either manually
* set by calling code or if message loading fails (is this possible?).
*
* Returns true if cache is already populated or it was succesfully populated,
@@ -189,10 +204,6 @@ class MessageCache {
function load( $code = false ) {
global $wgUseLocalMessageCache;
- if ( !$this->mUseCache ) {
- return true;
- }
-
if( !is_string( $code ) ) {
# This isn't really nice, so at least make a note about it and try to
# fall back
@@ -331,11 +342,10 @@ class MessageCache {
$bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
# Load titles for all oversized pages in the MediaWiki namespace
- $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ );
- while ( $row = $dbr->fetchObject( $res ) ) {
+ $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
+ foreach ( $res as $row ) {
$cache[$row->page_title] = '!TOO BIG';
}
- $dbr->freeResult( $res );
# Conditions to load the remaining pages with their contents
$smallConds = $conds;
@@ -345,12 +355,11 @@ class MessageCache {
$res = $dbr->select( array( 'page', 'revision', 'text' ),
array( 'page_title', 'old_text', 'old_flags' ),
- $smallConds, __METHOD__. "($code)" );
+ $smallConds, __METHOD__ . "($code)-small" );
- for ( $row = $dbr->fetchObject( $res ); $row; $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$cache[$row->page_title] = ' ' . Revision::getRevisionText( $row );
}
- $dbr->freeResult( $res );
$cache['VERSION'] = MSG_CACHE_VERSION;
wfProfileOut( __METHOD__ );
@@ -367,8 +376,12 @@ class MessageCache {
global $wgMaxMsgCacheEntrySize;
wfProfileIn( __METHOD__ );
+ if ( $this->mDisable ) {
+ wfProfileOut( __METHOD__ );
+ return;
+ }
- list( , $code ) = $this->figureMessage( $title );
+ list( $msg, $code ) = $this->figureMessage( $title );
$cacheKey = wfMemcKey( 'messages', $code );
$this->load($code);
@@ -410,6 +423,10 @@ class MessageCache {
$sidebarKey = wfMemcKey( 'sidebar', $code );
$parserMemc->delete( $sidebarKey );
}
+
+ // Update the message in the message blob store
+ global $wgContLang;
+ MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
wfRunHooks( "MessageCacheReplace", array( $title, $text ) );
@@ -420,7 +437,6 @@ class MessageCache {
* Shortcut to update caches.
*
* @param $cache Array: cached messages with a version.
- * @param $cacheKey String: Identifier for the cache.
* @param $memc Bool: Wether to update or not memcache.
* @param $code String: Language code.
* @return False on somekind of error.
@@ -454,14 +470,11 @@ class MessageCache {
}
/**
- * Returns success
* Represents a write lock on the messages key
+ *
+ * @return Boolean: success
*/
function lock($key) {
- if ( !$this->mUseCache ) {
- return true;
- }
-
$lockKey = $key . ':lock';
for ($i=0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
sleep(1);
@@ -471,10 +484,6 @@ class MessageCache {
}
function unlock($key) {
- if ( !$this->mUseCache ) {
- return;
- }
-
$lockKey = $key . ':lock';
$this->mMemc->delete( $lockKey );
}
@@ -482,28 +491,37 @@ class MessageCache {
/**
* Get a message from either the content language or the user language.
*
- * @param string $key The message cache key
- * @param bool $useDB Get the message from the DB, false to use only the localisation
- * @param string $langcode Code of the language to get the message for, if
- * it is a valid code create a language for that
- * language, if it is a string but not a valid code
- * then make a basic language object, if it is a
- * false boolean then use the current users
- * language (as a fallback for the old parameter
- * functionality), or if it is a true boolean then
- * use the wikis content language (also as a
- * fallback).
- * @param bool $isFullKey Specifies whether $key is a two part key "msg/lang".
+ * @param $key String: the message cache key
+ * @param $useDB Boolean: get the message from the DB, false to use only
+ * the localisation
+ * @param $langcode String: code of the language to get the message for, if
+ * it is a valid code create a language for that language,
+ * if it is a string but not a valid code then make a basic
+ * language object, if it is a false boolean then use the
+ * current users language (as a fallback for the old
+ * parameter functionality), or if it is a true boolean
+ * then use the wikis content language (also as a
+ * fallback).
+ * @param $isFullKey Boolean: specifies whether $key is a two part key
+ * "msg/lang".
*/
function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
- global $wgContLanguageCode, $wgContLang;
+ global $wgLanguageCode, $wgContLang;
+
+ if ( !is_string( $key ) ) {
+ throw new MWException( "Non-string key given" );
+ }
if ( strval( $key ) === '' ) {
# Shortcut: the empty key is always missing
- return '&lt;&gt;';
+ return false;
}
$lang = wfGetLangObj( $langcode );
+ if ( !$lang ) {
+ throw new MWException( "Bad lang code $langcode given" );
+ }
+
$langcode = $lang->getCode();
$message = false;
@@ -521,7 +539,7 @@ class MessageCache {
# Try the MediaWiki namespace
if( !$this->mDisable && $useDB ) {
$title = $uckey;
- if(!$isFullKey && ( $langcode != $wgContLanguageCode ) ) {
+ if(!$isFullKey && ( $langcode != $wgLanguageCode ) ) {
$title .= '/' . $langcode;
}
$message = $this->getMsgFromNamespace( $title, $langcode );
@@ -552,13 +570,13 @@ class MessageCache {
# Is this a custom message? Try the default language in the db...
if( ($message === false || $message === '-' ) &&
!$this->mDisable && $useDB &&
- !$isFullKey && ($langcode != $wgContLanguageCode) ) {
- $message = $this->getMsgFromNamespace( $uckey, $wgContLanguageCode );
+ !$isFullKey && ($langcode != $wgLanguageCode) ) {
+ $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
}
# Final fallback
if( $message === false ) {
- return '&lt;' . htmlspecialchars($key) . '&gt;';
+ return false;
}
# Fix whitespace
@@ -568,6 +586,7 @@ class MessageCache {
'&#32;' => ' ',
# Fix for NBSP, converted to space by firefox
'&nbsp;' => "\xc2\xa0",
+ '&#160;' => "\xc2\xa0",
) );
return $message;
@@ -584,14 +603,12 @@ class MessageCache {
$type = false;
$message = false;
- if ( $this->mUseCache ) {
- $this->load( $code );
- if (isset( $this->mCache[$code][$title] ) ) {
- $entry = $this->mCache[$code][$title];
- $type = substr( $entry, 0, 1 );
- if ( $type == ' ' ) {
- return substr( $entry, 1 );
- }
+ $this->load( $code );
+ if ( isset( $this->mCache[$code][$title] ) ) {
+ $entry = $this->mCache[$code][$title];
+ $type = substr( $entry, 0, 1 );
+ if ( $type == ' ' ) {
+ return substr( $entry, 1 );
}
}
@@ -601,31 +618,23 @@ class MessageCache {
return $message;
}
- # If there is no cache entry and no placeholder, it doesn't exist
- if ( $type !== '!' ) {
- return false;
- }
-
$titleKey = wfMemcKey( 'messages', 'individual', $title );
# Try the individual message cache
- if ( $this->mUseCache ) {
- $entry = $this->mMemc->get( $titleKey );
- if ( $entry ) {
- $type = substr( $entry, 0, 1 );
-
- if ( $type === ' ' ) {
- # Ok!
- $message = substr( $entry, 1 );
- $this->mCache[$code][$title] = $entry;
- return $message;
- } elseif ( $entry === '!NONEXISTENT' ) {
- return false;
- } else {
- # Corrupt/obsolete entry, delete it
- $this->mMemc->delete( $titleKey );
- }
-
+ $entry = $this->mMemc->get( $titleKey );
+ if ( $entry ) {
+ $type = substr( $entry, 0, 1 );
+
+ if ( $type === ' ' ) {
+ # Ok!
+ $message = substr( $entry, 1 );
+ $this->mCache[$code][$title] = $entry;
+ return $message;
+ } elseif ( $entry === '!NONEXISTENT' ) {
+ return false;
+ } else {
+ # Corrupt/obsolete entry, delete it
+ $this->mMemc->delete( $titleKey );
}
}
@@ -633,10 +642,8 @@ class MessageCache {
$revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
if( $revision ) {
$message = $revision->getText();
- if ($this->mUseCache) {
- $this->mCache[$code][$title] = ' ' . $message;
- $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
- }
+ $this->mCache[$code][$title] = ' ' . $message;
+ $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
} else {
# Negative caching
# Use some special text instead of false, because false gets converted to '' somewhere
@@ -670,6 +677,7 @@ class MessageCache {
$popts = $this->getParserOptions();
$popts->setInterfaceMessage( $interface );
$popts->setTargetLanguage( $language );
+ $popts->setUserLang( $language );
$message = $this->mParser->transformMsg( $message, $popts );
}
return $message;
@@ -697,24 +705,23 @@ class MessageCache {
* Clear all stored messages. Mainly used after a mass rebuild.
*/
function clear() {
- if( $this->mUseCache ) {
- $langs = Language::getLanguageNames( false );
- foreach ( array_keys($langs) as $code ) {
- # Global cache
- $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
- # Invalidate all local caches
- $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
- }
- }
+ $langs = Language::getLanguageNames( false );
+ foreach ( array_keys($langs) as $code ) {
+ # Global cache
+ $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
+ # Invalidate all local caches
+ $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
+ }
+ $this->mLoadedLanguages = array();
}
/**
* Add a message to the cache
* @deprecated Use $wgExtensionMessagesFiles
*
- * @param mixed $key
- * @param mixed $value
- * @param string $lang The messages language, English by default
+ * @param $key Mixed
+ * @param $value Mixed
+ * @param $lang String: the messages language, English by default
*/
function addMessage( $key, $value, $lang = 'en' ) {
wfDeprecated( __METHOD__ );
@@ -726,8 +733,8 @@ class MessageCache {
* Add an associative array of message to the cache
* @deprecated Use $wgExtensionMessagesFiles
*
- * @param array $messages An associative array of key => values to be added
- * @param string $lang The messages language, English by default
+ * @param $messages Array: an associative array of key => values to be added
+ * @param $lang String: the messages language, English by default
*/
function addMessages( $messages, $lang = 'en' ) {
wfDeprecated( __METHOD__ );
@@ -739,7 +746,7 @@ class MessageCache {
* Add a 2-D array of messages by lang. Useful for extensions.
* @deprecated Use $wgExtensionMessagesFiles
*
- * @param array $messages The array to be added
+ * @param $messages Array: the array to be added
*/
function addMessagesByLang( $messages ) {
wfDeprecated( __METHOD__ );
@@ -767,15 +774,15 @@ class MessageCache {
}
public function figureMessage( $key ) {
- global $wgContLanguageCode;
+ global $wgLanguageCode;
$pieces = explode( '/', $key );
if( count( $pieces ) < 2 )
- return array( $key, $wgContLanguageCode );
+ return array( $key, $wgLanguageCode );
$lang = array_pop( $pieces );
$validCodes = Language::getLanguageNames();
if( !array_key_exists( $lang, $validCodes ) )
- return array( $key, $wgContLanguageCode );
+ return array( $key, $wgLanguageCode );
$message = implode( '/', $pieces );
return array( $message, $lang );
diff --git a/includes/Metadata.php b/includes/Metadata.php
index 0b4fbf8c..93ce4b27 100644
--- a/includes/Metadata.php
+++ b/includes/Metadata.php
@@ -1,6 +1,7 @@
<?php
/**
- * Metadata.php -- provides DublinCore and CreativeCommons metadata
+ * Provides DublinCore and CreativeCommons metadata
+ *
* Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +19,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* @author Evan Prodromou <evan@wikitravel.org>
+ * @file
*/
abstract class RdfMetaData {
@@ -27,7 +29,7 @@ abstract class RdfMetaData {
* Constructor
* @param $article Article object
*/
- public function __construct( Article $article ){
+ public function __construct( Article $article ) {
$this->mArticle = $article;
}
@@ -61,11 +63,11 @@ abstract class RdfMetaData {
}
protected function basics() {
- global $wgContLanguageCode, $wgSitename;
+ global $wgLanguageCode, $wgSitename;
$this->element( 'title', $this->mArticle->mTitle->getText() );
$this->pageOrString( 'publisher', wfMsg( 'aboutpage' ), $wgSitename );
- $this->element( 'language', $wgContLanguageCode );
+ $this->element( 'language', $wgLanguageCode );
$this->element( 'type', 'Text' );
$this->element( 'format', 'text/html' );
$this->element( 'identifier', $this->reallyFullUrl() );
@@ -92,7 +94,7 @@ abstract class RdfMetaData {
. substr($timestamp, 6, 2);
}
- protected function pageOrString( $name, $page, $str ){
+ protected function pageOrString( $name, $page, $str ) {
if( $page instanceof Title )
$nt = $page;
else
@@ -105,7 +107,7 @@ abstract class RdfMetaData {
}
}
- protected function page( $name, $title ){
+ protected function page( $name, $title ) {
$this->url( $name, $title->getFullUrl() );
}
@@ -114,15 +116,17 @@ abstract class RdfMetaData {
print "\t\t<dc:{$name} rdf:resource=\"{$url}\" />\n";
}
- protected function person($name, User $user ){
- global $wgContLang;
-
+ protected function person( $name, User $user ) {
if( $user->isAnon() ){
$this->element( $name, wfMsgExt( 'anonymous', array( 'parsemag' ), 1 ) );
- } else if( $real = $user->getRealName() ) {
- $this->element( $name, $real );
} else {
- $this->pageOrString( $name, $user->getUserPage(), wfMsg( 'siteuser', $user->getName() ) );
+ $real = $user->getRealName();
+ if( $real ) {
+ $this->element( $name, $real );
+ } else {
+ $userName = $user->getName();
+ $this->pageOrString( $name, $user->getUserPage(), wfMsgExt( 'siteuser', 'parsemag', $userName, $userName ) );
+ }
}
}
@@ -315,4 +319,4 @@ PROLOGUE;
protected function epilogue() {
echo "</rdf:RDF>\n";
}
-} \ No newline at end of file
+}
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index 39c82c9d..018f601d 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -1,15 +1,31 @@
<?php
-/** Module defining helper functions for detecting and dealing with mime types.
+/**
+ * Module defining helper functions for detecting and dealing with mime types.
*
+ * @file
*/
- /** Defines a set of well known mime types
+/**
+ * Defines a set of well known mime types
* This is used as a fallback to mime.types files.
* An extensive list of well known mime types is provided by
* the file mime.types in the includes directory.
+ *
+ * This list concatenated with mime.types is used to create a mime <-> ext
+ * map. Each line contains a mime type followed by a space separated list of
+ * extensions. If multiple extensions for a single mime type exist or if
+ * multiple mime types exist for a single extension then in most cases
+ * MediaWiki assumes that the first extension following the mime type is the
+ * canonical extension, and the first time a mime type appears for a certain
+ * extension is considered the canonical mime type.
+ *
+ * (Note that appending $wgMimeTypeFile to the end of MM_WELL_KNOWN_MIME_TYPES
+ * sucks because you can't redefine canonical types. This could be fixed by
+ * appending MM_WELL_KNOWN_MIME_TYPES behind $wgMimeTypeFile, but who knows
+ * what will break? In practice this probably isn't a problem anyway -- Bryan)
*/
define('MM_WELL_KNOWN_MIME_TYPES',<<<END_STRING
-application/ogg ogg ogm ogv
+application/ogg ogx ogg ogm ogv oga spx
application/pdf pdf
application/vnd.oasis.opendocument.chart odc
application/vnd.oasis.opendocument.chart-template otc
@@ -33,24 +49,28 @@ audio/midi mid midi kar
audio/mpeg mpga mpa mp2 mp3
audio/x-aiff aif aiff aifc
audio/x-wav wav
-audio/ogg ogg
+audio/ogg oga spx ogg
image/x-bmp bmp
image/gif gif
image/jpeg jpeg jpg jpe
image/png png
-image/svg+xml image/svg svg
+image/svg+xml svg
+image/svg svg
image/tiff tiff tif
-image/vnd.djvu image/x.djvu image/x-djvu djvu
+image/vnd.djvu djvu
+image/x.djvu djvu
+image/x-djvu djvu
image/x-portable-pixmap ppm
image/x-xcf xcf
text/plain txt
text/html html htm
-video/ogg ogm ogg ogv
+video/ogg ogv ogm ogg
video/mpeg mpg mpeg
END_STRING
);
- /** Defines a set of well known mime info entries
+/**
+ * Defines a set of well known mime info entries
* This is used as a fallback to mime.info files.
* An extensive list of well known mime types is provided by
* the file mime.info in the includes directory.
@@ -80,7 +100,7 @@ audio/x-aiff [AUDIO]
audio/x-wav [AUDIO]
audio/mp3 audio/mpeg [AUDIO]
application/ogg audio/ogg video/ogg [MULTIMEDIA]
-image/x-bmp image/bmp [BITMAP]
+image/x-bmp image/x-ms-bmp image/bmp [BITMAP]
image/gif [BITMAP]
image/jpeg [BITMAP]
image/png [BITMAP]
@@ -102,7 +122,7 @@ END_STRING
global $wgLoadFileinfoExtension;
if ($wgLoadFileinfoExtension) {
- if(!extension_loaded('fileinfo')) dl('fileinfo.' . PHP_SHLIB_SUFFIX);
+ wfDl( 'fileinfo' );
}
/**
@@ -396,7 +416,9 @@ class MimeMagic {
'xbm',
// Formats we recognize magic numbers for
- 'djvu', 'ogg', 'ogv', 'mid', 'pdf', 'wmf', 'xcf',
+ 'djvu', 'ogx', 'ogg', 'ogv', 'oga', 'spx',
+ 'mid', 'pdf', 'wmf', 'xcf', 'webm', 'mkv', 'mka',
+ 'webp',
// XML formats we sure hope we recognize reliably
'svg',
@@ -404,18 +426,73 @@ class MimeMagic {
return in_array( strtolower( $extension ), $types );
}
+ /** improves a mime type using the file extension. Some file formats are very generic,
+ * so their mime type is not very meaningful. A more useful mime type can be derived
+ * by looking at the file extension. Typically, this method would be called on the
+ * result of guessMimeType().
+ *
+ * Currently, this method does the following:
+ *
+ * If $mime is "unknown/unknown" and isRecognizableExtension( $ext ) returns false,
+ * return the result of guessTypesForExtension($ext).
+ *
+ * If $mime is "application/x-opc+zip" and isMatchingExtension( $ext, $mime )
+ * gives true, return the result of guessTypesForExtension($ext).
+ *
+ * @param $mime String: the mime type, typically guessed from a file's content.
+ * @param $ext String: the file extension, as taken from the file name
+ *
+ * @return string the mime type
+ */
+ function improveTypeFromExtension( $mime, $ext ) {
+ if ( $mime === "unknown/unknown" ) {
+ if( $this->isRecognizableExtension( $ext ) ) {
+ wfDebug( __METHOD__. ": refusing to guess mime type for .$ext file, " .
+ "we should have recognized it\n" );
+ } else {
+ /* Not something we can detect, so simply
+ * trust the file extension */
+ $mime = $this->guessTypesForExtension( $ext );
+ }
+ }
+ else if ( $mime === "application/x-opc+zip" ) {
+ if ( $this->isMatchingExtension( $ext, $mime ) ) {
+ /* A known file extension for an OPC file,
+ * find the proper mime type for that file extension */
+ $mime = $this->guessTypesForExtension( $ext );
+ } else {
+ wfDebug( __METHOD__. ": refusing to guess better type for $mime file, " .
+ ".$ext is not a known OPC extension.\n" );
+ $mime = "application/zip";
+ }
+ }
+
+ if ( isset( $this->mMimeTypeAliases[$mime] ) ) {
+ $mime = $this->mMimeTypeAliases[$mime];
+ }
+
+ wfDebug(__METHOD__.": improved mime type for .$ext: $mime\n");
+ return $mime;
+ }
/** mime type detection. This uses detectMimeType to detect the mime type of the file,
* but applies additional checks to determine some well known file formats that may be missed
- * or misinterpreter by the default mime detection (namely xml based formats like XHTML or SVG).
+ * or misinterpreter by the default mime detection (namely XML based formats like XHTML or SVG,
+ * as well as ZIP based formats like OPC/ODF files).
*
- * @param string $file The file to check
- * @param mixed $ext The file extension, or true to extract it from the filename.
- * Set it to false to ignore the extension.
+ * @param $file String: the file to check
+ * @param $ext Mixed: the file extension, or true (default) to extract it from the filename.
+ * Set it to false to ignore the extension. DEPRECATED! Set to false, use
+ * improveTypeFromExtension($mime, $ext) later to improve mime type.
*
* @return string the mime type of $file
*/
function guessMimeType( $file, $ext = true ) {
+ if( $ext ) { # TODO: make $ext default to false. Or better, remove it.
+ wfDebug( __METHOD__.": WARNING: use of the \$ext parameter is deprecated. " .
+ "Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
+ }
+
$mime = $this->doGuessMimeType( $file, $ext );
if( !$mime ) {
@@ -427,11 +504,11 @@ class MimeMagic {
$mime = $this->mMimeTypeAliases[$mime];
}
- wfDebug(__METHOD__.": final mime type of $file: $mime\n");
+ wfDebug(__METHOD__.": guessed mime type of $file: $mime\n");
return $mime;
}
- function doGuessMimeType( $file, $ext = true ) {
+ private function doGuessMimeType( $file, $ext ) { # TODO: remove $ext param
// Read a chunk of the file
wfSuppressWarnings();
$f = fopen( $file, "rt" );
@@ -442,6 +519,8 @@ class MimeMagic {
$tail = fread( $f, 65558 ); // 65558 = maximum size of a zip EOCDR
fclose( $f );
+ wfDebug( __METHOD__ . ": analyzing head and tail of $file for magic numbers.\n" );
+
// Hardcode a few magic number checks...
$headers = array(
// Multimedia...
@@ -468,6 +547,30 @@ class MimeMagic {
}
}
+ /* Look for WebM and Matroska files */
+ if( strncmp( $head, pack( "C4", 0x1a, 0x45, 0xdf, 0xa3 ), 4 ) == 0 ) {
+ $doctype = strpos( $head, "\x42\x82" );
+ if( $doctype ) {
+ // Next byte is datasize, then data (sizes larger than 1 byte are very stupid muxers)
+ $data = substr($head, $doctype+3, 8);
+ if( strncmp( $data, "matroska", 8 ) == 0 ) {
+ wfDebug( __METHOD__ . ": recognized file as video/x-matroska\n" );
+ return "video/x-matroska";
+ } else if ( strncmp( $data, "webm", 4 ) == 0 ) {
+ wfDebug( __METHOD__ . ": recognized file as video/webm\n" );
+ return "video/webm";
+ }
+ }
+ wfDebug( __METHOD__ . ": unknown EBML file\n" );
+ return "unknown/unknown";
+ }
+
+ /* Look for WebP */
+ if( strncmp( $head, "RIFF", 4 ) == 0 && strncmp( substr( $head, 8, 8), "WEBPVP8 ", 8 ) == 0 ) {
+ wfDebug( __METHOD__ . ": recognized file as image/webp\n" );
+ return "image/webp";
+ }
+
/*
* Look for PHP. Check for this before HTML/XML... Warning: this is a
* heuristic, and won't match a file with a lot of non-PHP before. It
@@ -545,10 +648,10 @@ class MimeMagic {
}
}
- // Check for ZIP (before getimagesize)
+ // Check for ZIP variants (before getimagesize)
if ( strpos( $tail, "PK\x05\x06" ) !== false ) {
- wfDebug( __METHOD__.": ZIP header present at end of $file\n" );
- return $this->detectZipType( $head );
+ wfDebug( __METHOD__.": ZIP header present in $file\n" );
+ return $this->detectZipType( $head, $tail, $ext );
}
wfSuppressWarnings();
@@ -573,13 +676,24 @@ class MimeMagic {
/**
* Detect application-specific file type of a given ZIP file from its
- * header data. Currently works for OpenDocument types...
+ * header data. Currently works for OpenDocument and OpenXML types...
* If can't tell, returns 'application/zip'.
*
- * @param string $header Some reasonably-sized chunk of file header
+ * @param $header String: some reasonably-sized chunk of file header
+ * @param $tail String: the tail of the file
+ * @param $ext Mixed: the file extension, or true to extract it from the filename.
+ * Set it to false (default) to ignore the extension. DEPRECATED! Set to false,
+ * use improveTypeFromExtension($mime, $ext) later to improve mime type.
+ *
* @return string
*/
- function detectZipType( $header ) {
+ function detectZipType( $header, $tail = null, $ext = false ) {
+ if( $ext ) { # TODO: remove $ext param
+ wfDebug( __METHOD__.": WARNING: use of the \$ext parameter is deprecated. " .
+ "Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
+ }
+
+ $mime = 'application/zip';
$opendocTypes = array(
'chart-template',
'chart',
@@ -601,16 +715,62 @@ class MimeMagic {
// http://lists.oasis-open.org/archives/office/200505/msg00006.html
$types = '(?:' . implode( '|', $opendocTypes ) . ')';
$opendocRegex = "/^mimetype(application\/vnd\.oasis\.opendocument\.$types)/";
- wfDebug( __METHOD__.": $opendocRegex\n" );
-
+
+ $openxmlRegex = "/^\[Content_Types\].xml/";
+
if( preg_match( $opendocRegex, substr( $header, 30 ), $matches ) ) {
$mime = $matches[1];
wfDebug( __METHOD__.": detected $mime from ZIP archive\n" );
- return $mime;
+ } elseif( preg_match( $openxmlRegex, substr( $header, 30 ) ) ) {
+ $mime = "application/x-opc+zip";
+ # TODO: remove the block below, as soon as improveTypeFromExtension is used everywhere
+ if( $ext !== true && $ext !== false ) {
+ /** This is the mode used by getPropsFromPath
+ * These mime's are stored in the database, where we don't really want
+ * x-opc+zip, because we use it only for internal purposes
+ */
+ if( $this->isMatchingExtension( $ext, $mime) ) {
+ /* A known file extension for an OPC file,
+ * find the proper mime type for that file extension */
+ $mime = $this->guessTypesForExtension( $ext );
+ } else {
+ $mime = "application/zip";
+ }
+ }
+ wfDebug( __METHOD__.": detected an Open Packaging Conventions archive: $mime\n" );
+ } else if( substr( $header, 0, 8 ) == "\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" &&
+ ($headerpos = strpos( $tail, "PK\x03\x04" ) ) !== false &&
+ preg_match( $openxmlRegex, substr( $tail, $headerpos + 30 ) ) ) {
+ if( substr( $header, 512, 4) == "\xEC\xA5\xC1\x00" ) {
+ $mime = "application/msword";
+ }
+ switch( substr( $header, 512, 6) ) {
+ case "\xEC\xA5\xC1\x00\x0E\x00":
+ case "\xEC\xA5\xC1\x00\x1C\x00":
+ case "\xEC\xA5\xC1\x00\x43\x00":
+ $mime = "application/vnd.ms-powerpoint";
+ break;
+ case "\xFD\xFF\xFF\xFF\x10\x00":
+ case "\xFD\xFF\xFF\xFF\x1F\x00":
+ case "\xFD\xFF\xFF\xFF\x22\x00":
+ case "\xFD\xFF\xFF\xFF\x23\x00":
+ case "\xFD\xFF\xFF\xFF\x28\x00":
+ case "\xFD\xFF\xFF\xFF\x29\x00":
+ case "\xFD\xFF\xFF\xFF\x10\x02":
+ case "\xFD\xFF\xFF\xFF\x1F\x02":
+ case "\xFD\xFF\xFF\xFF\x22\x02":
+ case "\xFD\xFF\xFF\xFF\x23\x02":
+ case "\xFD\xFF\xFF\xFF\x28\x02":
+ case "\xFD\xFF\xFF\xFF\x29\x02":
+ $mime = "application/vnd.msexcel";
+ break;
+ }
+
+ wfDebug( __METHOD__.": detected a MS Office document with OPC trailer\n");
} else {
wfDebug( __METHOD__.": unable to identify type of ZIP archive\n" );
- return 'application/zip';
}
+ return $mime;
}
/** Internal mime type detection, please use guessMimeType() for application code instead.
@@ -621,16 +781,21 @@ class MimeMagic {
* If the mime type is still unknown, getimagesize is used to detect the mime type if the file is an image.
* If no mime type can be determined, this function returns "unknown/unknown".
*
- * @param string $file The file to check
- * @param mixed $ext The file extension, or true to extract it from the filename.
- * Set it to false to ignore the extension.
+ * @param $file String: the file to check
+ * @param $ext Mixed: the file extension, or true (default) to extract it from the filename.
+ * Set it to false to ignore the extension. DEPRECATED! Set to false, use
+ * improveTypeFromExtension($mime, $ext) later to improve mime type.
*
* @return string the mime type of $file
* @access private
*/
- function detectMimeType( $file, $ext = true ) {
+ private function detectMimeType( $file, $ext = true ) {
global $wgMimeDetectorCommand;
+ if( $ext ) { # TODO: make $ext default to false. Or better, remove it.
+ wfDebug( __METHOD__.": WARNING: use of the \$ext parameter is deprecated. Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
+ }
+
$m = null;
if ( $wgMimeDetectorCommand ) {
$fn = wfEscapeShellArg( $file );
@@ -717,9 +882,9 @@ class MimeMagic {
* @todo analyse file if need be
* @todo look at multiple extension, separately and together.
*
- * @param string $path full path to the image file, in case we have to look at the contents
+ * @param $path String: full path to the image file, in case we have to look at the contents
* (if null, only the mime type is used to determine the media type code).
- * @param string $mime mime type. If null it will be guessed using guessMimeType.
+ * @param $mime String: mime type. If null it will be guessed using guessMimeType.
*
* @return (int?string?) a value to be used with the MEDIATYPE_xxx constants.
*/
@@ -756,7 +921,6 @@ class MimeMagic {
}
# Check for entry for file extension
- $e = null;
if ( $path ) {
$i = strrpos( $path, '.' );
$e = strtolower( $i ? substr( $path, $i + 1 ) : '' );
@@ -819,9 +983,9 @@ class MimeMagic {
* Get the MIME types that various versions of Internet Explorer would
* detect from a chunk of the content.
*
- * @param string $fileName The file name (unused at present)
- * @param string $chunk The first 256 bytes of the file
- * @param string $proposed The MIME type proposed by the server
+ * @param $fileName String: the file name (unused at present)
+ * @param $chunk String: the first 256 bytes of the file
+ * @param $proposed String: the MIME type proposed by the server
*/
public function getIEMimeTypes( $fileName, $chunk, $proposed ) {
$ca = $this->getIEContentAnalyzer();
diff --git a/includes/Namespace.php b/includes/Namespace.php
index e8e7523f..47dc3c5f 100644
--- a/includes/Namespace.php
+++ b/includes/Namespace.php
@@ -28,7 +28,8 @@ $wgCanonicalNamespaceNames = array(
NS_CATEGORY_TALK => 'Category_talk',
);
-if( isset( $wgExtraNamespaces ) && is_array( $wgExtraNamespaces ) ) {
+/// @todo UGLY UGLY
+if( is_array( $wgExtraNamespaces ) ) {
$wgCanonicalNamespaceNames = $wgCanonicalNamespaceNames + $wgExtraNamespaces;
}
@@ -112,21 +113,40 @@ class MWNamespace {
* Returns whether the specified namespace exists
*/
public static function exists( $index ) {
- global $wgCanonicalNamespaceNames;
- return isset( $wgCanonicalNamespaceNames[$index] );
+ $nslist = self::getCanonicalNamespaces();
+ return isset( $nslist[$index] );
}
+ /**
+ * Returns array of all defined namespaces with their canonical
+ * (English) names.
+ *
+ * @return \array
+ * @since 1.17
+ */
+ public static function getCanonicalNamespaces() {
+ static $namespaces = null;
+ if ( $namespaces === null ) {
+ global $wgExtraNamespaces, $wgCanonicalNamespaceNames;
+ $namespaces = array( NS_MAIN => '' ) + $wgCanonicalNamespaceNames;
+ if ( is_array( $wgExtraNamespaces ) ) {
+ $namespaces += $wgExtraNamespaces;
+ }
+ wfRunHooks( 'CanonicalNamespaces', array( &$namespaces ) );
+ }
+ return $namespaces;
+ }
/**
- * Returns the canonical (English Wikipedia) name for a given index
+ * Returns the canonical (English) name for a given index
*
* @param $index Int: namespace index
* @return string or false if no canonical definition.
*/
public static function getCanonicalName( $index ) {
- global $wgCanonicalNamespaceNames;
- if( isset( $wgCanonicalNamespaceNames[$index] ) ) {
- return $wgCanonicalNamespaceNames[$index];
+ $nslist = self::getCanonicalNamespaces();
+ if( isset( $nslist[$index] ) ) {
+ return $nslist[$index];
} else {
return false;
}
@@ -140,11 +160,10 @@ class MWNamespace {
* @return int
*/
public static function getCanonicalIndex( $name ) {
- global $wgCanonicalNamespaceNames;
static $xNamespaces = false;
if ( $xNamespaces === false ) {
$xNamespaces = array();
- foreach ( $wgCanonicalNamespaceNames as $i => $text ) {
+ foreach ( self::getCanonicalNamespaces() as $i => $text ) {
$xNamespaces[strtolower($text)] = $i;
}
}
@@ -156,6 +175,25 @@ class MWNamespace {
}
/**
+ * Returns an array of the namespaces (by integer id) that exist on the
+ * wiki. Used primarily by the api in help documentation.
+ * @return array
+ */
+ public static function getValidNamespaces() {
+ static $mValidNamespaces = null;
+
+ if ( is_null( $mValidNamespaces ) ) {
+ foreach ( array_keys( self::getCanonicalNamespaces() ) as $ns ) {
+ if ( $ns >= 0 ) {
+ $mValidNamespaces[] = $ns;
+ }
+ }
+ }
+
+ return $mValidNamespaces;
+ }
+
+ /**
* Can this namespace ever have a talk namespace?
*
* @param $index Int: namespace index
diff --git a/includes/NamespaceCompat.php b/includes/NamespaceCompat.php
deleted file mode 100644
index 15c76478..00000000
--- a/includes/NamespaceCompat.php
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-/**
- * For compatibility with extensions...
- * Will still die on PHP 5.3, of course. :P
- */
-class Namespace extends MWNamespace {
- // ..
-}
diff --git a/includes/ObjectCache.php b/includes/ObjectCache.php
index f83e0020..db22c074 100644
--- a/includes/ObjectCache.php
+++ b/includes/ObjectCache.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Functions to get cache objects
+ *
* @file
* @ingroup Cache
*/
@@ -66,6 +68,8 @@ function &wfGetCache( $inputType ) {
$wgCaches[CACHE_ACCEL] = new APCBagOStuff;
} elseif( function_exists( 'xcache_get' ) ) {
$wgCaches[CACHE_ACCEL] = new XCacheBagOStuff();
+ } elseif( function_exists( 'wincache_ucache_get' ) ) {
+ $wgCaches[CACHE_ACCEL] = new WinCacheBagOStuff();
} else {
$wgCaches[CACHE_ACCEL] = false;
}
diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php
index 91819cc7..8f310da2 100644
--- a/includes/OutputHandler.php
+++ b/includes/OutputHandler.php
@@ -1,4 +1,9 @@
<?php
+/**
+ * Functions to be used with PHP's output buffer
+ *
+ * @file
+ */
/**
* Standard output handler for use with ob_start
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 97e26110..6ecc2754 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -1,6 +1,7 @@
<?php
-if ( ! defined( 'MEDIAWIKI' ) )
+if ( !defined( 'MEDIAWIKI' ) ) {
die( 1 );
+}
/**
* @todo document
@@ -8,13 +9,23 @@ if ( ! defined( 'MEDIAWIKI' ) )
class OutputPage {
var $mMetatags = array(), $mKeywords = array(), $mLinktags = array();
var $mExtStyles = array();
- var $mPagetitle = '', $mBodytext = '', $mDebugtext = '';
- var $mHTMLtitle = '', $mHTMLtitleFromPagetitle = true, $mIsarticle = true, $mPrintable = false;
+ var $mPagetitle = '', $mBodytext = '';
+
+ /**
+ * Holds the debug lines that will be outputted as comments in page source if
+ * $wgDebugComments is enabled. See also $wgShowDebug.
+ * TODO: make a getter method for this
+ */
+ public $mDebugtext = '';
+
+ var $mHTMLtitle = '', $mIsarticle = true, $mPrintable = false;
var $mSubtitle = '', $mRedirect = '', $mStatusCode;
var $mLastModified = '', $mETag = false;
var $mCategoryLinks = array(), $mCategories = array(), $mLanguageLinks = array();
- var $mScripts = '', $mLinkColours, $mPageLinkTitle = '', $mHeadItems = array();
+ var $mScripts = '', $mInlineStyles = '', $mLinkColours, $mPageLinkTitle = '', $mHeadItems = array();
+ var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array();
+ var $mResourceLoader;
var $mInlineMsg = array();
var $mTemplateIds = array();
@@ -55,9 +66,10 @@ class OutputPage {
private $mIndexPolicy = 'index';
private $mFollowPolicy = 'follow';
- private $mVaryHeader = array( 'Accept-Encoding' => array('list-contains=gzip'),
- 'Cookie' => null );
-
+ private $mVaryHeader = array(
+ 'Accept-Encoding' => array( 'list-contains=gzip' ),
+ 'Cookie' => null
+ );
/**
* Constructor
@@ -99,7 +111,6 @@ class OutputPage {
$this->mStatusCode = $statusCode;
}
-
/**
* Add a new <meta> tag
* To add an http-equiv meta tag, precede the name with "http:"
@@ -148,7 +159,6 @@ class OutputPage {
$haveMeta = true;
}
-
/**
* Add raw HTML to the list of scripts (including \<script\> tag, etc.)
*
@@ -184,15 +194,19 @@ class OutputPage {
*
* @param $file String: filename in skins/common or complete on-server path
* (/foo/bar.js)
+ * @param $version String: style version of the file. Defaults to $wgStyleVersion
*/
- public function addScriptFile( $file ) {
+ public function addScriptFile( $file, $version = null ) {
global $wgStylePath, $wgStyleVersion;
- if( substr( $file, 0, 1 ) == '/' || substr( $file, 0, 7 ) == 'http://' ) {
+ // See if $file parameter is an absolute URL or begins with a slash
+ if( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
$path = $file;
} else {
- $path = "{$wgStylePath}/common/{$file}";
+ $path = "{$wgStylePath}/common/{$file}";
}
- $this->addScript( Html::linkedScript( wfAppendQuery( $path, $wgStyleVersion ) ) );
+ if ( is_null( $version ) )
+ $version = $wgStyleVersion;
+ $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
}
/**
@@ -214,6 +228,85 @@ class OutputPage {
}
/**
+ * Get the list of modules to include on this page
+ *
+ * @return Array of module names
+ */
+ public function getModules() {
+ return array_values( array_unique( $this->mModules ) );
+ }
+
+ /**
+ * Add one or more modules recognized by the resource loader. Modules added
+ * through this function will be loaded by the resource loader when the
+ * page loads.
+ *
+ * @param $modules Mixed: module name (string) or array of module names
+ */
+ public function addModules( $modules ) {
+ $this->mModules = array_merge( $this->mModules, (array)$modules );
+ }
+
+ /**
+ * Get the list of module JS to include on this page
+ * @return array of module names
+ */
+ public function getModuleScripts() {
+ return array_values( array_unique( $this->mModuleScripts ) );
+ }
+
+ /**
+ * Add only JS of one or more modules recognized by the resource loader. Module
+ * scripts added through this function will be loaded by the resource loader when
+ * the page loads.
+ *
+ * @param $modules Mixed: module name (string) or array of module names
+ */
+ public function addModuleScripts( $modules ) {
+ $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
+ }
+
+ /**
+ * Get the list of module CSS to include on this page
+ *
+ * @return Array of module names
+ */
+ public function getModuleStyles() {
+ return array_values( array_unique( $this->mModuleStyles ) );
+ }
+
+ /**
+ * Add only CSS of one or more modules recognized by the resource loader. Module
+ * styles added through this function will be loaded by the resource loader when
+ * the page loads.
+ *
+ * @param $modules Mixed: module name (string) or array of module names
+ */
+ public function addModuleStyles( $modules ) {
+ $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
+ }
+
+ /**
+ * Get the list of module messages to include on this page
+ *
+ * @return Array of module names
+ */
+ public function getModuleMessages() {
+ return array_values( array_unique( $this->mModuleMessages ) );
+ }
+
+ /**
+ * Add only messages of one or more modules recognized by the resource loader.
+ * Module messages added through this function will be loaded by the resource
+ * loader when the page loads.
+ *
+ * @param $modules Mixed: module name (string) or array of module names
+ */
+ public function addModuleMessages( $modules ) {
+ $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules );
+ }
+
+ /**
* Get all header items in a string
*
* @return String
@@ -275,7 +368,6 @@ class OutputPage {
return $this->mArticleBodyOnly;
}
-
/**
* checkLastModified tells the client to use the client-cached page if
* possible. If sucessful, the OutputPage is disabled so that
@@ -352,7 +444,7 @@ class OutputPage {
# Not modified
# Give a 304 response code and disable body output
wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
- ini_set('zlib.output_compression', 0);
+ ini_set( 'zlib.output_compression', 0 );
$wgRequest->response()->header( "HTTP/1.1 304 Not Modified" );
$this->sendCacheControl();
$this->disable();
@@ -365,6 +457,15 @@ class OutputPage {
return true;
}
+ /**
+ * Override the last modified timestamp
+ *
+ * @param $timestamp String: new timestamp, in a format readable by
+ * wfTimestamp()
+ */
+ public function setLastModified( $timestamp ) {
+ $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
+ }
/**
* Set the robot policy for the page: <http://www.robotstxt.org/meta.html>
@@ -377,10 +478,10 @@ class OutputPage {
public function setRobotPolicy( $policy ) {
$policy = Article::formatRobotPolicy( $policy );
- if( isset( $policy['index'] ) ){
+ if( isset( $policy['index'] ) ) {
$this->setIndexPolicy( $policy['index'] );
}
- if( isset( $policy['follow'] ) ){
+ if( isset( $policy['follow'] ) ) {
$this->setFollowPolicy( $policy['follow'] );
}
}
@@ -413,7 +514,6 @@ class OutputPage {
}
}
-
/**
* Set the new value of the "action text", this will be added to the
* "HTML title", separated from it with " - ".
@@ -438,17 +538,9 @@ class OutputPage {
/**
* "HTML title" means the contents of <title>.
* It is stored as plain, unescaped text and will be run through htmlspecialchars in the skin file.
- * If $name is from page title, it can only override names which are also from page title,
- * but if it is not from page title, it can override all other names.
*/
- public function setHTMLTitle( $name, $frompagetitle = false ) {
- if ( $frompagetitle && $this->mHTMLtitleFromPagetitle ) {
- $this->mHTMLtitle = $name;
- }
- elseif ( $this->mHTMLtitleFromPagetitle ) {
- $this->mHTMLtitle = $name;
- $this->mHTMLtitleFromPagetitle = false;
- }
+ public function setHTMLTitle( $name ) {
+ $this->mHTMLtitle = $name;
}
/**
@@ -472,11 +564,6 @@ class OutputPage {
$nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
$this->mPagetitle = $nameWithTags;
- $taction = $this->getPageTitleActionText();
- if( !empty( $taction ) ) {
- $name .= ' - '.$taction;
- }
-
# change "<i>foo&amp;bar</i>" to "foo&bar"
$this->setHTMLTitle( wfMsg( 'pagetitle', Sanitizer::stripAllTags( $nameWithTags ) ) );
}
@@ -508,7 +595,7 @@ class OutputPage {
if ( $this->mTitle instanceof Title ) {
return $this->mTitle;
} else {
- wfDebug( __METHOD__ . ' called and $mTitle is null. Return $wgTitle for sanity' );
+ wfDebug( __METHOD__ . " called and \$mTitle is null. Return \$wgTitle for sanity\n" );
global $wgTitle;
return $wgTitle;
}
@@ -541,7 +628,6 @@ class OutputPage {
return $this->mSubtitle;
}
-
/**
* Set the page as printable, i.e. it'll be displayed with with all
* print styles included
@@ -559,7 +645,6 @@ class OutputPage {
return $this->mPrintable;
}
-
/**
* Disable output completely, i.e. calling output() will have no effect
*/
@@ -576,7 +661,6 @@ class OutputPage {
return $this->mDoNothing;
}
-
/**
* Show an "add new section" link?
*
@@ -595,7 +679,6 @@ class OutputPage {
return $this->mHideNewSectionLink;
}
-
/**
* Add or remove feed links in the page header
* This is mainly kept for backward compatibility, see OutputPage::addFeedLink()
@@ -642,7 +725,11 @@ class OutputPage {
* @param $href String: URL
*/
public function addFeedLink( $format, $href ) {
- $this->mFeedLinks[$format] = $href;
+ global $wgAdvertisedFeedTypes;
+
+ if ( in_array( $format, $wgAdvertisedFeedTypes ) ) {
+ $this->mFeedLinks[$format] = $href;
+ }
}
/**
@@ -716,7 +803,6 @@ class OutputPage {
return $this->mIsArticleRelated;
}
-
/**
* Add new language links
*
@@ -746,7 +832,6 @@ class OutputPage {
return $this->mLanguageLinks;
}
-
/**
* Add an array of categories, with names in the keys
*
@@ -769,7 +854,7 @@ class OutputPage {
$pageTable = $dbr->tableName( 'page' );
$where = $lb->constructSet( 'page', $dbr );
$propsTable = $dbr->tableName( 'page_props' );
- $sql = "SELECT page_id, page_namespace, page_title, page_len, page_is_redirect, pp_value
+ $sql = "SELECT page_id, page_namespace, page_title, page_len, page_is_redirect, page_latest, pp_value
FROM $pageTable LEFT JOIN $propsTable ON pp_propname='hiddencat' AND pp_page=page_id WHERE $where";
$res = $dbr->query( $sql, __METHOD__ );
@@ -777,8 +862,10 @@ class OutputPage {
$lb->addResultToCache( LinkCache::singleton(), $res );
# Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+
- $categories = array_combine( array_keys( $categories ),
- array_fill( 0, count( $categories ), 'normal' ) );
+ $categories = array_combine(
+ array_keys( $categories ),
+ array_fill( 0, count( $categories ), 'normal' )
+ );
# Mark hidden categories
foreach ( $res as $row ) {
@@ -794,9 +881,11 @@ class OutputPage {
$origcategory = $category;
$title = Title::makeTitleSafe( NS_CATEGORY, $category );
$wgContLang->findVariantLink( $category, $title, true );
- if ( $category != $origcategory )
- if ( array_key_exists( $category, $categories ) )
+ if ( $category != $origcategory ) {
+ if ( array_key_exists( $category, $categories ) ) {
continue;
+ }
+ }
$text = $wgContLang->convertHtml( $title->getText() );
$this->mCategories[] = $title->getText();
$this->mCategoryLinks[$type][] = $sk->link( $title, $text );
@@ -835,7 +924,6 @@ class OutputPage {
return $this->mCategories;
}
-
/**
* Suppress the quickbar from the output, only for skin supporting
* the quickbar
@@ -853,7 +941,6 @@ class OutputPage {
return $this->mSuppressQuickbar;
}
-
/**
* Remove user JavaScript from scripts to load
*/
@@ -870,7 +957,6 @@ class OutputPage {
return $this->mAllowUserJs;
}
-
/**
* Prepend $text to the body HTML
*
@@ -905,7 +991,6 @@ class OutputPage {
return $this->mBodytext;
}
-
/**
* Add $text to the debug output
*
@@ -915,7 +1000,6 @@ class OutputPage {
$this->mDebugtext .= $text;
}
-
/**
* @deprecated use parserOptions() instead
*/
@@ -983,7 +1067,7 @@ class OutputPage {
}
/**
- * Add wikitext with a custom Title object and
+ * Add wikitext with a custom Title object and
*
* @param $text String: wikitext
* @param $title Title object
@@ -1001,7 +1085,7 @@ class OutputPage {
*/
public function addWikiTextTidy( $text, $linestart = true ) {
$title = $this->getTitle();
- $this->addWikiTextTitleTidy($text, $title, $linestart);
+ $this->addWikiTextTitleTidy( $text, $title, $linestart );
}
/**
@@ -1022,8 +1106,10 @@ class OutputPage {
$popts = $this->parserOptions();
$oldTidy = $popts->setTidy( $tidy );
- $parserOutput = $wgParser->parse( $text, $title, $popts,
- $linestart, true, $this->mRevisionId );
+ $parserOutput = $wgParser->parse(
+ $text, $title, $popts,
+ $linestart, true, $this->mRevisionId
+ );
$popts->setTidy( $oldTidy );
@@ -1047,13 +1133,15 @@ class OutputPage {
wfDeprecated( __METHOD__ );
$popts = $this->parserOptions();
- $popts->setTidy(true);
- $parserOutput = $wgParser->parse( $text, $article->mTitle,
- $popts, true, true, $this->mRevisionId );
- $popts->setTidy(false);
- if ( $cache && $article && $parserOutput->getCacheTime() != -1 ) {
+ $popts->setTidy( true );
+ $parserOutput = $wgParser->parse(
+ $text, $article->mTitle,
+ $popts, true, true, $this->mRevisionId
+ );
+ $popts->setTidy( false );
+ if ( $cache && $article && $parserOutput->isCacheable() ) {
$parserCache = ParserCache::singleton();
- $parserCache->save( $parserOutput, $article, $popts);
+ $parserCache->save( $parserOutput, $article, $popts );
}
$this->addParserOutput( $parserOutput );
@@ -1064,29 +1152,27 @@ class OutputPage {
*/
public function addSecondaryWikiText( $text, $linestart = true ) {
wfDeprecated( __METHOD__ );
- $this->addWikiTextTitleTidy($text, $this->getTitle(), $linestart);
+ $this->addWikiTextTitleTidy( $text, $this->getTitle(), $linestart );
}
-
/**
* Add a ParserOutput object, but without Html
*
* @param $parserOutput ParserOutput object
*/
public function addParserOutputNoText( &$parserOutput ) {
- global $wgExemptFromUserRobotsControl, $wgContentNamespaces;
-
$this->mLanguageLinks += $parserOutput->getLanguageLinks();
$this->addCategoryLinks( $parserOutput->getCategories() );
$this->mNewSectionLink = $parserOutput->getNewSection();
$this->mHideNewSectionLink = $parserOutput->getHideNewSection();
$this->mParseWarnings = $parserOutput->getWarnings();
- if ( $parserOutput->getCacheTime() == -1 ) {
+ if ( !$parserOutput->isCacheable() ) {
$this->enableClientCache( false );
}
$this->mNoGallery = $parserOutput->getNoGallery();
$this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
+ $this->addModules( $parserOutput->getModules() );
// Versioning...
foreach ( (array)$parserOutput->mTemplateIds as $ns => $dbks ) {
if ( isset( $this->mTemplateIds[$ns] ) ) {
@@ -1095,11 +1181,6 @@ class OutputPage {
$this->mTemplateIds[$ns] = $dbks;
}
}
- // Page title
- $title = $parserOutput->getTitleText();
- if ( $title != '' ) {
- $this->setPageTitle( $title );
- }
// Hooks registered in the object
global $wgParserOutputHooks;
@@ -1121,7 +1202,7 @@ class OutputPage {
function addParserOutput( &$parserOutput ) {
$this->addParserOutputNoText( $parserOutput );
$text = $parserOutput->getText();
- wfRunHooks( 'OutputPageBeforeHTML',array( &$this, &$text ) );
+ wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
$this->addHTML( $text );
}
@@ -1154,10 +1235,16 @@ class OutputPage {
throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
}
$popts = $this->parserOptions();
- if ( $interface) { $popts->setInterfaceMessage(true); }
- $parserOutput = $wgParser->parse( $text, $this->getTitle(), $popts,
- $linestart, true, $this->mRevisionId );
- if ( $interface) { $popts->setInterfaceMessage(false); }
+ if ( $interface ) {
+ $popts->setInterfaceMessage( true );
+ }
+ $parserOutput = $wgParser->parse(
+ $text, $this->getTitle(), $popts,
+ $linestart, true, $this->mRevisionId
+ );
+ if ( $interface ) {
+ $popts->setInterfaceMessage( false );
+ }
return $parserOutput->getText();
}
@@ -1192,7 +1279,7 @@ class OutputPage {
wfDeprecated( __METHOD__ );
$parserOutput = ParserCache::singleton()->get( $article, $article->getParserOptions() );
- if ($parserOutput !== false) {
+ if ( $parserOutput !== false ) {
$this->addParserOutput( $parserOutput );
return true;
} else {
@@ -1235,21 +1322,21 @@ class OutputPage {
),
$wgCacheVaryCookies
);
- wfRunHooks('GetCacheVaryCookies', array( $this, &$cookies ) );
+ wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) );
}
return $cookies;
}
/**
* Return whether this page is not cacheable because "useskin" or "uselang"
- * url parameters were passed
+ * URL parameters were passed.
*
* @return Boolean
*/
function uncacheableBecauseRequestVars() {
global $wgRequest;
- return $wgRequest->getText('useskin', false) === false
- && $wgRequest->getText('uselang', false) === false;
+ return $wgRequest->getText( 'useskin', false ) === false
+ && $wgRequest->getText( 'uselang', false ) === false;
}
/**
@@ -1268,11 +1355,11 @@ class OutputPage {
foreach ( $cvCookies as $cookieName ) {
# Check for a simple string match, like the way squid does it
if ( strpos( $cookieHeader, $cookieName ) !== false ) {
- wfDebug( __METHOD__.": found $cookieName\n" );
+ wfDebug( __METHOD__ . ": found $cookieName\n" );
return true;
}
}
- wfDebug( __METHOD__.": no cache-varying cookies found\n" );
+ wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
return false;
}
@@ -1281,16 +1368,16 @@ class OutputPage {
*
* @param $header String: header name
* @param $option either an Array or null
+ * @fixme Document the $option parameter; it appears to be for
+ * X-Vary-Options but what format is acceptable?
*/
public function addVaryHeader( $header, $option = null ) {
if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
- $this->mVaryHeader[$header] = $option;
- }
- elseif( is_array( $option ) ) {
+ $this->mVaryHeader[$header] = (array)$option;
+ } elseif( is_array( $option ) ) {
if( is_array( $this->mVaryHeader[$header] ) ) {
$this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option );
- }
- else {
+ } else {
$this->mVaryHeader[$header] = $option;
}
}
@@ -1304,22 +1391,23 @@ class OutputPage {
*/
public function getXVO() {
$cvCookies = $this->getCacheVaryCookies();
-
+
$cookiesOption = array();
foreach ( $cvCookies as $cookieName ) {
$cookiesOption[] = 'string-contains=' . $cookieName;
}
$this->addVaryHeader( 'Cookie', $cookiesOption );
-
+
$headers = array();
foreach( $this->mVaryHeader as $header => $option ) {
$newheader = $header;
- if( is_array( $option ) )
+ if( is_array( $option ) ) {
$newheader .= ';' . implode( ';', $option );
+ }
$headers[] = $newheader;
}
$xvo = 'X-Vary-Options: ' . implode( ',', $headers );
-
+
return $xvo;
}
@@ -1330,19 +1418,28 @@ class OutputPage {
* For example:
* /w/index.php?title=Main_page should always be served; but
* /w/index.php?title=Main_page&variant=zh-cn should never be served.
- *
- * patched by Liangent and Philip
*/
function addAcceptLanguage() {
global $wgRequest, $wgContLang;
- if( !$wgRequest->getCheck('variant') && $wgContLang->hasVariants() ) {
+ if( !$wgRequest->getCheck( 'variant' ) && $wgContLang->hasVariants() ) {
$variants = $wgContLang->getVariants();
$aloption = array();
foreach ( $variants as $variant ) {
- if( $variant === $wgContLang->getCode() )
+ if( $variant === $wgContLang->getCode() ) {
continue;
- else
- $aloption[] = "string-contains=$variant";
+ } else {
+ $aloption[] = 'string-contains=' . $variant;
+
+ // IE and some other browsers use another form of language code
+ // in their Accept-Language header, like "zh-CN" or "zh-TW".
+ // We should handle these too.
+ $ievariant = explode( '-', $variant );
+ if ( count( $ievariant ) == 2 ) {
+ $ievariant[1] = strtoupper( $ievariant[1] );
+ $ievariant = implode( '-', $ievariant );
+ $aloption[] = 'string-contains=' . $ievariant;
+ }
+ }
}
$this->addVaryHeader( 'Accept-Language', $aloption );
}
@@ -1390,8 +1487,9 @@ class OutputPage {
global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest, $wgUseXVO;
$response = $wgRequest->response();
- if ($wgUseETag && $this->mETag)
- $response->header("ETag: $this->mETag");
+ if ( $wgUseETag && $this->mETag ) {
+ $response->header( "ETag: $this->mETag" );
+ }
$this->addAcceptLanguage();
@@ -1405,8 +1503,10 @@ class OutputPage {
}
if( !$this->uncacheableBecauseRequestVars() && $this->mEnableClientCache ) {
- if( $wgUseSquid && session_id() == '' &&
- ! $this->isPrintable() && $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies() )
+ if(
+ $wgUseSquid && session_id() == '' && !$this->isPrintable() &&
+ $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies()
+ )
{
if ( $wgUseESI ) {
# We'll purge the proxy cache explicitly, but require end user agents
@@ -1513,12 +1613,12 @@ class OutputPage {
*/
public function output() {
global $wgUser, $wgOutputEncoding, $wgRequest;
- global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType;
+ global $wgLanguageCode, $wgDebugRedirects, $wgMimeType;
global $wgUseAjax, $wgAjaxWatch;
global $wgEnableMWSuggest, $wgUniversalEditButton;
global $wgArticle;
- if( $this->mDoNothing ){
+ if( $this->mDoNothing ) {
return;
}
wfProfileIn( __METHOD__ );
@@ -1547,35 +1647,44 @@ class OutputPage {
return;
} elseif ( $this->mStatusCode ) {
$message = self::getStatusMessage( $this->mStatusCode );
- if ( $message )
+ if ( $message ) {
$wgRequest->response()->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message );
+ }
}
$sk = $wgUser->getSkin();
+ // Add base resources
+ $this->addModules( 'mediawiki.util' );
+ global $wgIncludeLegacyJavaScript;
+ if( $wgIncludeLegacyJavaScript ){
+ $this->addModules( 'mediawiki.legacy.wikibits' );
+ }
+
+ // Add various resources if required
if ( $wgUseAjax ) {
- $this->addScriptFile( 'ajax.js' );
+ $this->addModules( 'mediawiki.legacy.ajax' );
wfRunHooks( 'AjaxAddScript', array( &$this ) );
if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
- $this->addScriptFile( 'ajaxwatch.js' );
+ $this->addModules( 'mediawiki.legacy.ajaxwatch' );
}
- if ( $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ){
- $this->addScriptFile( 'mwsuggest.js' );
+ if ( $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ) {
+ $this->addModules( 'mediawiki.legacy.mwsuggest' );
}
}
if( $wgUser->getBoolOption( 'editsectiononrightclick' ) ) {
- $this->addScriptFile( 'rightclickedit.js' );
+ $this->addModules( 'mediawiki.action.view.rightClickEdit' );
}
if( $wgUniversalEditButton ) {
if( isset( $wgArticle ) && $this->getTitle() && $this->getTitle()->quickUserCan( 'edit' )
&& ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create' ) ) ) {
// Original UniversalEditButton
- $msg = wfMsg('edit');
+ $msg = wfMsg( 'edit' );
$this->addLink( array(
'rel' => 'alternate',
'type' => 'application/x-wiki',
@@ -1591,11 +1700,12 @@ class OutputPage {
}
}
+
# Buffer output; final headers may depend on later processing
ob_start();
$wgRequest->response()->header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" );
- $wgRequest->response()->header( 'Content-language: '.$wgContLanguageCode );
+ $wgRequest->response()->header( 'Content-language: ' . $wgLanguageCode );
// Prevent framing, if requested
$frameOptions = $this->getFrameOptions();
@@ -1603,9 +1713,8 @@ class OutputPage {
$wgRequest->response()->header( "X-Frame-Options: $frameOptions" );
}
-
- if ($this->mArticleBodyOnly) {
- $this->out($this->mBodytext);
+ if ( $this->mArticleBodyOnly ) {
+ $this->out( $this->mBodytext );
} else {
// Hook that allows last minute changes to the output page, e.g.
// adding of CSS or Javascript by extensions.
@@ -1633,7 +1742,9 @@ class OutputPage {
$outs = $ins;
} else {
$outs = $wgContLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
- if ( false === $outs ) { $outs = $ins; }
+ if ( false === $outs ) {
+ $outs = $ins;
+ }
}
print $outs;
}
@@ -1643,7 +1754,6 @@ class OutputPage {
*/
public static function setEncodings() {
global $wgInputEncoding, $wgOutputEncoding;
- global $wgContLang;
$wgInputEncoding = strtolower( $wgInputEncoding );
@@ -1683,7 +1793,9 @@ class OutputPage {
if( $reason == '' ) {
$reason = wfMsg( 'blockednoreason' );
}
- $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $wgUser->mBlock->mTimestamp ), true );
+ $blockTimestamp = $wgLang->timeanddate(
+ wfTimestamp( TS_MW, $wgUser->mBlock->mTimestamp ), true
+ );
$ip = wfGetIP();
$link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
@@ -1696,16 +1808,20 @@ class OutputPage {
// Search for localization in 'ipboptions'
$scBlockExpiryOptions = wfMsg( 'ipboptions' );
foreach ( explode( ',', $scBlockExpiryOptions ) as $option ) {
- if ( strpos( $option, ":" ) === false )
+ if ( strpos( $option, ':' ) === false ) {
continue;
- list( $show, $value ) = explode( ":", $option );
+ }
+ list( $show, $value ) = explode( ':', $option );
if ( $value == 'infinite' || $value == 'indefinite' ) {
$blockExpiry = $show;
break;
}
}
} else {
- $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
+ $blockExpiry = $wgLang->timeanddate(
+ wfTimestamp( TS_MW, $blockExpiry ),
+ true
+ );
}
if ( $wgUser->mBlock->mAuto ) {
@@ -1715,10 +1831,13 @@ class OutputPage {
}
/* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
- * This could be a username, an ip range, or a single ip. */
+ * This could be a username, an IP range, or a single IP. */
$intended = $wgUser->mBlock->mAddress;
- $this->addWikiMsg( $msg, $link, $reason, $ip, $name, $blockid, $blockExpiry, $intended, $blockTimestamp );
+ $this->addWikiMsg(
+ $msg, $link, $reason, $ip, $name, $blockid, $blockExpiry,
+ $intended, $blockTimestamp
+ );
# Don't auto-return to special pages
if( $return ) {
@@ -1806,9 +1925,11 @@ class OutputPage {
$groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
User::getGroupsWithPermission( $permission ) );
if( $groups ) {
- $this->addWikiMsg( 'badaccess-groups',
+ $this->addWikiMsg(
+ 'badaccess-groups',
$wgLang->commaList( $groups ),
- count( $groups) );
+ count( $groups )
+ );
} else {
$this->addWikiMsg( 'badaccess-group0' );
}
@@ -1816,24 +1937,10 @@ class OutputPage {
}
/**
- * @deprecated use permissionRequired()
- */
- public function sysopRequired() {
- throw new MWException( "Call to deprecated OutputPage::sysopRequired() method\n" );
- }
-
- /**
- * @deprecated use permissionRequired()
- */
- public function developerRequired() {
- throw new MWException( "Call to deprecated OutputPage::developerRequired() method\n" );
- }
-
- /**
* Produce the stock "please login to use the wiki" page
*/
public function loginToUse() {
- global $wgUser, $wgContLang;
+ global $wgUser;
if( $wgUser->isLoggedIn() ) {
$this->permissionRequired( 'read' );
@@ -1856,13 +1963,14 @@ class OutputPage {
array( 'known', 'noclasses' )
);
$this->addHTML( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) );
- $this->addHTML( "\n<!--" . $this->getTitle()->getPrefixedUrl() . "-->" );
+ $this->addHTML( "\n<!--" . $this->getTitle()->getPrefixedUrl() . '-->' );
# Don't return to the main page if the user can't read it
# otherwise we'll end up in a pointless loop
$mainPage = Title::newMainPage();
- if( $mainPage->userCanRead() )
+ if( $mainPage->userCanRead() ) {
$this->returnToMain( null, $mainPage );
+ }
}
/**
@@ -1873,26 +1981,30 @@ class OutputPage {
* @return String: the wikitext error-messages, formatted into a list.
*/
public function formatPermissionsErrorMessage( $errors, $action = null ) {
- if ($action == null) {
- $text = wfMsgNoTrans( 'permissionserrorstext', count($errors)). "\n\n";
+ if ( $action == null ) {
+ $text = wfMsgNoTrans( 'permissionserrorstext', count( $errors ) ) . "\n\n";
} else {
- global $wgLang;
$action_desc = wfMsgNoTrans( "action-$action" );
- $text = wfMsgNoTrans( 'permissionserrorstext-withaction', count($errors), $action_desc ) . "\n\n";
+ $text = wfMsgNoTrans(
+ 'permissionserrorstext-withaction',
+ count( $errors ),
+ $action_desc
+ ) . "\n\n";
}
- if (count( $errors ) > 1) {
+ if ( count( $errors ) > 1 ) {
$text .= '<ul class="permissions-errors">' . "\n";
- foreach( $errors as $error )
- {
+ foreach( $errors as $error ) {
$text .= '<li>';
$text .= call_user_func_array( 'wfMsgNoTrans', $error );
$text .= "</li>\n";
}
$text .= '</ul>';
} else {
- $text .= "<div class=\"permissions-errors\">\n" . call_user_func_array( 'wfMsgNoTrans', reset( $errors ) ) . "\n</div>";
+ $text .= "<div class=\"permissions-errors\">\n" .
+ call_user_func_array( 'wfMsgNoTrans', reset( $errors ) ) .
+ "\n</div>";
}
return $text;
@@ -1927,25 +2039,16 @@ class OutputPage {
// 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) ) {
+ if ( $protected && empty( $reasons ) ) {
$reasons[] = array( 'badaccess-group0' );
}
- if ( !empty($reasons) ) {
+ if ( !empty( $reasons ) ) {
// Permissions error
if( $source ) {
$this->setPageTitle( wfMsg( 'viewsource' ) );
$this->setSubtitle(
- wfMsg(
- 'viewsourcefor',
- $skin->link(
- $this->getTitle(),
- null,
- array(),
- array(),
- array( 'known', 'noclasses' )
- )
- )
+ wfMsg( 'viewsourcefor', $skin->linkKnown( $this->getTitle() ) )
);
} else {
$this->setPageTitle( wfMsg( 'badaccess' ) );
@@ -1955,7 +2058,7 @@ class OutputPage {
// Wiki is read only
$this->setPageTitle( wfMsg( 'readonly' ) );
$reason = wfReadOnlyReason();
- $this->wrapWikiMsg( '<div class="mw-readonly-error">\n$1</div>', array( 'readonlytext', $reason ) );
+ $this->wrapWikiMsg( "<div class='mw-readonly-error'>\n$1\n</div>", array( 'readonlytext', $reason ) );
}
// Show source, if supplied
@@ -2036,8 +2139,8 @@ class OutputPage {
}
public function showFatalError( $message ) {
- $this->setPageTitle( wfMsg( "internalerror" ) );
- $this->setRobotPolicy( "noindex,nofollow" );
+ $this->setPageTitle( wfMsg( 'internalerror' ) );
+ $this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
$this->enableClientCache( false );
$this->mRedirect = '';
@@ -2069,12 +2172,15 @@ class OutputPage {
*
* @param $title Title to link
* @param $query String: query string
+ * @param $text String text of the link (input is not escaped)
*/
- public function addReturnTo( $title, $query = array() ) {
+ public function addReturnTo( $title, $query = array(), $text = null ) {
global $wgUser;
- $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullUrl() ) );
- $link = wfMsgHtml( 'returnto', $wgUser->getSkin()->link(
- $title, null, array(), $query ) );
+ $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL() ) );
+ $link = wfMsgHtml(
+ 'returnto',
+ $wgUser->getSkin()->link( $title, $text, array(), $query )
+ );
$this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
}
@@ -2115,88 +2221,75 @@ class OutputPage {
/**
* @param $sk Skin The given Skin
- * @param $includeStyle Unused (?)
+ * @param $includeStyle Boolean: unused
* @return String: The doctype, opening <html>, and head element.
*/
public function headElement( Skin $sk, $includeStyle = true ) {
- global $wgDocType, $wgDTD, $wgContLanguageCode, $wgOutputEncoding, $wgMimeType;
- global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces, $wgHtml5Version;
- global $wgContLang, $wgUseTrackbacks, $wgStyleVersion, $wgHtml5, $wgWellFormedXml;
+ global $wgOutputEncoding, $wgMimeType;
+ global $wgUseTrackbacks, $wgHtml5;
global $wgUser, $wgRequest, $wgLang;
- $this->addMeta( "http:Content-Type", "$wgMimeType; charset={$wgOutputEncoding}" );
if ( $sk->commonPrintStylesheet() ) {
- $this->addStyle( 'common/wikiprintable.css', 'print' );
+ $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
}
$sk->setupUserCss( $this );
- $ret = '';
-
- if( $wgMimeType == 'text/xml' || $wgMimeType == 'application/xhtml+xml' || $wgMimeType == 'application/xml' ) {
- $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?" . ">\n";
- }
+ $lang = wfUILang();
+ $ret = Html::htmlHeader( array( 'lang' => $lang->getCode(), 'dir' => $lang->getDir() ) );
if ( $this->getHTMLTitle() == '' ) {
- $this->setHTMLTitle( wfMsg( 'pagetitle', $this->getPageTitle() ));
+ $this->setHTMLTitle( wfMsg( 'pagetitle', $this->getPageTitle() ) );
}
- $dir = $wgContLang->getDir();
+ $openHead = Html::openElement( 'head' );
+ if ( $openHead ) {
+ # Don't bother with the newline if $head == ''
+ $ret .= "$openHead\n";
+ }
if ( $wgHtml5 ) {
- if ( $wgWellFormedXml ) {
- # Unknown elements and attributes are okay in XML, but unknown
- # named entities are well-formedness errors and will break XML
- # parsers. Thus we need a doctype that gives us appropriate
- # entity definitions. The HTML5 spec permits four legacy
- # doctypes as obsolete but conforming, so let's pick one of
- # those, although it makes our pages look like XHTML1 Strict.
- # Isn't compatibility great?
- $ret .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
- } else {
- # Much saner.
- $ret .= "<!doctype html>\n";
- }
- $ret .= "<html lang=\"$wgContLanguageCode\" dir=\"$dir\"";
- if ( $wgHtml5Version ) $ret .= " version=\"$wgHtml5Version\"";
- $ret .= ">\n";
+ # More succinct than <meta http-equiv=Content-Type>, has the
+ # same effect
+ $ret .= Html::element( 'meta', array( 'charset' => $wgOutputEncoding ) ) . "\n";
} else {
- $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
- $ret .= "<html xmlns=\"{$wgXhtmlDefaultNamespace}\" ";
- foreach($wgXhtmlNamespaces as $tag => $ns) {
- $ret .= "xmlns:{$tag}=\"{$ns}\" ";
- }
- $ret .= "lang=\"$wgContLanguageCode\" dir=\"$dir\">\n";
+ $this->addMeta( 'http:Content-Type', "$wgMimeType; charset=$wgOutputEncoding" );
}
- $ret .= "<head>\n";
- $ret .= "<title>" . htmlspecialchars( $this->getHTMLTitle() ) . "</title>\n";
+ $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
+
$ret .= implode( "\n", array(
- $this->getHeadLinks(),
- $this->buildCssLinks(),
- $this->getHeadScripts( $sk ),
- $this->getHeadItems(),
- ));
- if( $sk->usercss ){
- $ret .= Html::inlineStyle( $sk->usercss );
- }
+ $this->getHeadLinks( $sk ),
+ $this->buildCssLinks( $sk ),
+ $this->getHeadItems()
+ ) );
- if ($wgUseTrackbacks && $this->isArticleRelated())
+ if ( $wgUseTrackbacks && $this->isArticleRelated() ) {
$ret .= $this->getTitle()->trackbackRDF();
+ }
- $ret .= "</head>\n";
+ $closeHead = Html::closeElement( 'head' );
+ if ( $closeHead ) {
+ $ret .= "$closeHead\n";
+ }
$bodyAttrs = array();
# Crazy edit-on-double-click stuff
$action = $wgRequest->getVal( 'action', 'view' );
- if ( $this->getTitle()->getNamespace() != NS_SPECIAL
- && !in_array( $action, array( 'edit', 'submit' ) )
- && $wgUser->getOption( 'editondblclick' ) ) {
- $bodyAttrs['ondblclick'] = "document.location = '" . Xml::escapeJsString( $this->getTitle()->getEditURL() ) . "'";
+ if (
+ $this->getTitle()->getNamespace() != NS_SPECIAL &&
+ !in_array( $action, array( 'edit', 'submit' ) ) &&
+ $wgUser->getOption( 'editondblclick' )
+ )
+ {
+ $editUrl = $this->getTitle()->getLocalUrl( $sk->editUrlOptions() );
+ $bodyAttrs['ondblclick'] = "document.location = '" .
+ Xml::escapeJsString( $editUrl ) . "'";
}
# Class bloat
+ $dir = wfUILang()->getDir();
$bodyAttrs['class'] = "mediawiki $dir";
if ( $wgLang->capitalizeAllNouns() ) {
@@ -2214,55 +2307,220 @@ class OutputPage {
$bodyAttrs['class'] .= ' ' . Sanitizer::escapeClass( 'page-' . $this->getTitle()->getPrefixedText() );
$bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $wgUser->getSkin()->getSkinName() );
+ $sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need
+ wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
+
$ret .= Html::openElement( 'body', $bodyAttrs ) . "\n";
return $ret;
}
/**
+ * Get a ResourceLoader object associated with this OutputPage
+ */
+ public function getResourceLoader() {
+ if ( is_null( $this->mResourceLoader ) ) {
+ $this->mResourceLoader = new ResourceLoader();
+ }
+ return $this->mResourceLoader;
+ }
+
+ /**
+ * TODO: Document
+ * @param $skin Skin
+ * @param $modules Array/string with the module name
+ * @param $only string May be styles, messages or scripts
+ * @param $useESI boolean
+ * @return string html <script> and <style> tags
+ */
+ protected function makeResourceLoaderLink( Skin $skin, $modules, $only, $useESI = false ) {
+ global $wgUser, $wgLang, $wgLoadScript, $wgResourceLoaderUseESI,
+ $wgResourceLoaderInlinePrivateModules, $wgRequest;
+ // Lazy-load ResourceLoader
+ // TODO: Should this be a static function of ResourceLoader instead?
+ $baseQuery = array(
+ 'lang' => $wgLang->getCode(),
+ 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
+ 'skin' => $skin->getSkinName(),
+ 'only' => $only,
+ );
+ // Propagate printable and handheld parameters if present
+ if ( $wgRequest->getBool( 'printable' ) ) {
+ $baseQuery['printable'] = 1;
+ }
+ if ( $wgRequest->getBool( 'handheld' ) ) {
+ $baseQuery['handheld'] = 1;
+ }
+
+ if ( !count( $modules ) ) {
+ return '';
+ }
+
+ if ( count( $modules ) > 1 ) {
+ // Remove duplicate module requests
+ $modules = array_unique( (array) $modules );
+ // Sort module names so requests are more uniform
+ sort( $modules );
+
+ if ( ResourceLoader::inDebugMode() ) {
+ // Recursively call us for every item
+ $links = '';
+ foreach ( $modules as $name ) {
+ $links .= $this->makeResourceLoaderLink( $skin, $name, $only, $useESI );
+ }
+ return $links;
+ }
+ }
+
+ // Create keyed-by-group list of module objects from modules list
+ $groups = array();
+ $resourceLoader = $this->getResourceLoader();
+ foreach ( (array) $modules as $name ) {
+ $module = $resourceLoader->getModule( $name );
+
+ $group = $module->getGroup();
+ if ( !isset( $groups[$group] ) ) {
+ $groups[$group] = array();
+ }
+ $groups[$group][$name] = $module;
+ }
+ $links = '';
+ foreach ( $groups as $group => $modules ) {
+ $query = $baseQuery;
+ // Special handling for user-specific groups
+ if ( ( $group === 'user' || $group === 'private' ) && $wgUser->isLoggedIn() ) {
+ $query['user'] = $wgUser->getName();
+ }
+
+ // Create a fake request based on the one we are about to make so modules return
+ // correct timestamp and emptiness data
+ $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
+ // Drop modules that know they're empty
+ foreach ( $modules as $key => $module ) {
+ if ( $module->isKnownEmpty( $context ) ) {
+ unset( $modules[$key] );
+ }
+ }
+ // If there are no modules left, skip this group
+ if ( $modules === array() ) {
+ continue;
+ }
+
+ $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $modules ) );
+
+ // Support inlining of private modules if configured as such
+ if ( $group === 'private' && $wgResourceLoaderInlinePrivateModules ) {
+ if ( $only == 'styles' ) {
+ $links .= Html::inlineStyle(
+ $resourceLoader->makeModuleResponse( $context, $modules )
+ );
+ } else {
+ $links .= Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ $resourceLoader->makeModuleResponse( $context, $modules )
+ )
+ );
+ }
+ continue;
+ }
+ // Special handling for the user group; because users might change their stuff
+ // on-wiki like user pages, or user preferences; we need to find the highest
+ // timestamp of these user-changable modules so we can ensure cache misses on change
+ // This should NOT be done for the site group (bug 27564) because anons get that too
+ // and we shouldn't be putting timestamps in Squid-cached HTML
+ if ( $group === 'user' ) {
+ // Get the maximum timestamp
+ $timestamp = 1;
+ foreach ( $modules as $module ) {
+ $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
+ }
+ // Add a version parameter so cache will break when things change
+ $query['version'] = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
+ }
+ // Make queries uniform in order
+ ksort( $query );
+
+ $url = wfAppendQuery( $wgLoadScript, $query );
+ // Prevent the IE6 extension check from being triggered (bug 28840)
+ // by appending a character that's invalid in Windows extensions ('*')
+ $url .= '&*';
+ if ( $useESI && $wgResourceLoaderUseESI ) {
+ $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
+ if ( $only == 'styles' ) {
+ $links .= Html::inlineStyle( $esi );
+ } else {
+ $links .= Html::inlineScript( $esi );
+ }
+ } else {
+ // Automatically select style/script elements
+ if ( $only === 'styles' ) {
+ $links .= Html::linkedStyle( $url ) . "\n";
+ } else {
+ $links .= Html::linkedScript( $url ) . "\n";
+ }
+ }
+ }
+ return $links;
+ }
+
+ /**
* Gets the global variables and mScripts; also adds userjs to the end if
- * enabled
+ * enabled. Despite the name, these scripts are no longer put in the
+ * <head> but at the bottom of the <body>
*
* @param $sk Skin object to use
* @return String: HTML fragment
*/
function getHeadScripts( Skin $sk ) {
- global $wgUser, $wgRequest, $wgJsMimeType, $wgUseSiteJs;
- global $wgStylePath, $wgStyleVersion;
+ global $wgUser, $wgRequest, $wgUseSiteJs;
- $scripts = Skin::makeGlobalVariablesScript( $sk->getSkinName() );
- $scripts .= Html::linkedScript( "{$wgStylePath}/common/wikibits.js?$wgStyleVersion" );
+ // Startup - this will immediately load jquery and mediawiki modules
+ $scripts = $this->makeResourceLoaderLink( $sk, 'startup', 'scripts', true );
- //add site JS if enabled:
- if( $wgUseSiteJs ) {
- $jsCache = $wgUser->isLoggedIn() ? '&smaxage=0' : '';
- $this->addScriptFile( Skin::makeUrl( '-',
- "action=raw$jsCache&gen=js&useskin=" .
- urlencode( $sk->getSkinName() )
- )
- );
+ // Configuration -- This could be merged together with the load and go, but
+ // makeGlobalVariablesScript returns a whole script tag -- grumble grumble...
+ $scripts .= Skin::makeGlobalVariablesScript( $sk->getSkinName() ) . "\n";
+
+ // Script and Messages "only" requests
+ $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleScripts(), 'scripts' );
+ $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleMessages(), 'messages' );
+
+ // Modules requests - let the client calculate dependencies and batch requests as it likes
+ if ( $this->getModules() ) {
+ $scripts .= Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mediaWiki.loader.load', array( $this->getModules() ) ) .
+ Xml::encodeJsCall( 'mediaWiki.loader.go', array() )
+ )
+ ) . "\n";
}
- //add user js if enabled:
- if( $this->isUserJsAllowed() && $wgUser->isLoggedIn() ) {
+ // Legacy Scripts
+ $scripts .= "\n" . $this->mScripts;
+
+ // Add site JS if enabled
+ if ( $wgUseSiteJs ) {
+ $scripts .= $this->makeResourceLoaderLink( $sk, 'site', 'scripts' );
+ }
+
+ // Add user JS if enabled - trying to load user.options as a bundle if possible
+ $userOptionsAdded = false;
+ if ( $this->isUserJsAllowed() && $wgUser->isLoggedIn() ) {
$action = $wgRequest->getVal( 'action', 'view' );
- if( $this->mTitle && $this->mTitle->isJsSubpage() and $sk->userCanPreview( $action ) ) {
+ if( $this->mTitle && $this->mTitle->isJsSubpage() && $sk->userCanPreview( $action ) ) {
# XXX: additional security check/prompt?
- $this->addInlineScript( $wgRequest->getText( 'wpTextbox1' ) );
+ $scripts .= Html::inlineScript( "\n" . $wgRequest->getText( 'wpTextbox1' ) . "\n" ) . "\n";
} else {
- $userpage = $wgUser->getUserPage();
- $scriptpage = Title::makeTitleSafe(
- NS_USER,
- $userpage->getDBkey() . '/' . $sk->getSkinName() . '.js'
+ $scripts .= $this->makeResourceLoaderLink(
+ $sk, array( 'user', 'user.options' ), 'scripts'
);
- if ( $scriptpage && $scriptpage->exists() ) {
- $userjs = Skin::makeUrl( $scriptpage->getPrefixedText(), 'action=raw&ctype=' . $wgJsMimeType );
- $this->addScriptFile( $userjs );
- }
+ $userOptionsAdded = true;
}
}
-
- $scripts .= "\n" . $this->mScripts;
+ if ( !$userOptionsAdded ) {
+ $scripts .= $this->makeResourceLoaderLink( $sk, 'user.options', 'scripts' );
+ }
+
return $scripts;
}
@@ -2280,7 +2538,7 @@ class OutputPage {
$called = true;
if ( !$wgHtml5 ) {
- $this->addMeta( 'http:Content-Style-Type', 'text/css' ); //bug 15835
+ $this->addMeta( 'http:Content-Style-Type', 'text/css' ); // bug 15835
}
$this->addMeta( 'generator', "MediaWiki $wgVersion" );
@@ -2296,15 +2554,22 @@ class OutputPage {
"/<.*?" . ">/" => '',
"/_/" => ' '
);
- $this->addMeta( 'keywords', preg_replace(array_keys($strip), array_values($strip),implode( ",", $this->mKeywords ) ) );
+ $this->addMeta(
+ 'keywords',
+ preg_replace(
+ array_keys( $strip ),
+ array_values( $strip ),
+ implode( ',', $this->mKeywords )
+ )
+ );
}
}
/**
* @return string HTML tag links to be put in the header.
*/
- public function getHeadLinks() {
- global $wgRequest, $wgFeed;
+ public function getHeadLinks( Skin $sk ) {
+ global $wgFeed;
// Ideally this should happen earlier, somewhere. :P
$this->addDefaultMeta();
@@ -2321,7 +2586,9 @@ class OutputPage {
$tags[] = Html::element( 'meta',
array(
$a => $tag[0],
- 'content' => $tag[1] ) );
+ 'content' => $tag[1]
+ )
+ );
}
foreach ( $this->mLinktags as $tag ) {
$tags[] = Html::element( 'link', $tag );
@@ -2338,7 +2605,8 @@ class OutputPage {
$format,
$link,
# Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
- wfMsg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() ) );
+ wfMsg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )
+ );
}
# Recent changes feed should appear on every page (except recentchanges,
@@ -2354,17 +2622,19 @@ class OutputPage {
if ( $wgOverrideSiteFeed ) {
foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
- $tags[] = $this->feedLink (
+ $tags[] = $this->feedLink(
$type,
htmlspecialchars( $feedUrl ),
- wfMsg( "site-{$type}-feed", $wgSitename ) );
+ wfMsg( "site-{$type}-feed", $wgSitename )
+ );
}
} elseif ( $this->getTitle()->getPrefixedText() != $rctitle->getPrefixedText() ) {
foreach ( $wgAdvertisedFeedTypes as $format ) {
$tags[] = $this->feedLink(
$format,
$rctitle->getLocalURL( "feed={$format}" ),
- wfMsg( "site-{$format}-feed", $wgSitename ) ); # For grep: 'site-rss-feed', 'site-atom-feed'.
+ wfMsg( "site-{$format}-feed", $wgSitename ) # For grep: 'site-rss-feed', 'site-atom-feed'.
+ );
}
}
}
@@ -2385,7 +2655,8 @@ class OutputPage {
'rel' => 'alternate',
'type' => "application/$type+xml",
'title' => $text,
- 'href' => $url ) );
+ 'href' => $url )
+ );
}
/**
@@ -2397,16 +2668,19 @@ class OutputPage {
* @param $condition String: for IE conditional comments, specifying an IE version
* @param $dir String: set to 'rtl' or 'ltr' for direction-specific sheets
*/
- public function addStyle( $style, $media='', $condition='', $dir='' ) {
+ public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
$options = array();
// Even though we expect the media type to be lowercase, but here we
// force it to lowercase to be safe.
- if( $media )
+ if( $media ) {
$options['media'] = $media;
- if( $condition )
+ }
+ if( $condition ) {
$options['condition'] = $condition;
- if( $dir )
+ }
+ if( $dir ) {
$options['dir'] = $dir;
+ }
$this->styles[$style] = $options;
}
@@ -2415,22 +2689,58 @@ class OutputPage {
* @param $style_css Mixed: inline CSS
*/
public function addInlineStyle( $style_css ){
- $this->mScripts .= Html::inlineStyle( $style_css );
+ $this->mInlineStyles .= Html::inlineStyle( $style_css );
}
/**
* Build a set of <link>s for the stylesheets specified in the $this->styles array.
* These will be applied to various media & IE conditionals.
+ * @param $sk Skin object
*/
- public function buildCssLinks() {
+ public function buildCssLinks( $sk ) {
+ $ret = '';
+ // Add ResourceLoader styles
+ // Split the styles into four groups
+ $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array() );
+ $resourceLoader = $this->getResourceLoader();
+ foreach ( $this->getModuleStyles() as $name ) {
+ $group = $resourceLoader->getModule( $name )->getGroup();
+ // Modules in groups named "other" or anything different than "user", "site" or "private"
+ // will be placed in the "other" group
+ $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
+ }
+
+ // We want site, private and user styles to override dynamically added styles from modules, but we want
+ // dynamically added styles to override statically added styles from other modules. So the order
+ // has to be other, dynamic, site, private, user
+ // Add statically added styles for other modules
+ $ret .= $this->makeResourceLoaderLink( $sk, $styles['other'], 'styles' );
+ // Add normal styles added through addStyle()/addInlineStyle() here
+ $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles;
+ // Add marker tag to mark the place where the client-side loader should inject dynamic styles
+ // We use a <meta> tag with a made-up name for this because that's valid HTML
+ $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) );
+
+ // Add site, private and user styles
+ // 'private' at present only contains user.options, so put that before 'user'
+ // Any future private modules will likely have a similar user-specific character
+ foreach ( array( 'site', 'private', 'user' ) as $group ) {
+ $ret .= $this->makeResourceLoaderLink(
+ $sk, array_merge( $styles['site'], $styles['user'] ), 'styles'
+ );
+ }
+ return $ret;
+ }
+
+ public function buildCssLinksArray() {
$links = array();
foreach( $this->styles as $file => $options ) {
$link = $this->styleLink( $file, $options );
- if( $link )
- $links[] = $link;
+ if( $link ) {
+ $links[$file] = $link;
+ }
}
-
- return implode( "\n", $links );
+ return $links;
}
/**
@@ -2442,17 +2752,15 @@ class OutputPage {
* @return String: HTML fragment
*/
protected function styleLink( $style, $options ) {
- global $wgRequest;
-
if( isset( $options['dir'] ) ) {
- global $wgContLang;
- $siteDir = $wgContLang->getDir();
- if( $siteDir != $options['dir'] )
+ $siteDir = wfUILang()->getDir();
+ if( $siteDir != $options['dir'] ) {
return '';
+ }
}
if( isset( $options['media'] ) ) {
- $media = $this->transformCssMedia( $options['media'] );
+ $media = self::transformCssMedia( $options['media'] );
if( is_null( $media ) ) {
return '';
}
@@ -2484,7 +2792,7 @@ class OutputPage {
* @param $media String: current value of the "media" attribute
* @return String: modified value of the "media" attribute
*/
- function transformCssMedia( $media ) {
+ public static function transformCssMedia( $media ) {
global $wgRequest, $wgHandheldForIPhone;
// Switch in on-screen display for media testing
@@ -2522,13 +2830,13 @@ class OutputPage {
* for when rate limiting has triggered.
*/
public function rateLimited() {
- $this->setPageTitle(wfMsg('actionthrottled'));
+ $this->setPageTitle( wfMsg( 'actionthrottled' ) );
$this->setRobotPolicy( 'noindex,follow' );
$this->setArticleRelated( false );
$this->enableClientCache( false );
$this->mRedirect = '';
$this->clearHTML();
- $this->setStatusCode(503);
+ $this->setStatusCode( 503 );
$this->addWikiMsg( 'actionthrottledtext' );
$this->returnToMain( null, $this->getTitle() );
@@ -2595,11 +2903,11 @@ class OutputPage {
*
* For example:
*
- * $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>", 'some-error' );
+ * $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>", 'some-error' );
*
* Is equivalent to:
*
- * $wgOut->addWikiText( "<div class='error'>\n" . wfMsgNoTrans( 'some-error' ) . "</div>" );
+ * $wgOut->addWikiText( "<div class='error'>\n" . wfMsgNoTrans( 'some-error' ) . "\n</div>" );
*
* The newline after opening div is needed in some wikitext. See bug 19226.
*/
@@ -2628,26 +2936,15 @@ class OutputPage {
/**
* Include jQuery core. Use this to avoid loading it multiple times
- * before we get a usable script loader.
+ * before we get a usable script loader.
*
* @param $modules Array: list of jQuery modules which should be loaded
* @return Array: the list of modules which were not loaded.
+ * @since 1.16
+ * @deprecated No longer needed as of 1.17
*/
public function includeJQuery( $modules = array() ) {
- global $wgStylePath, $wgStyleVersion, $wgJsMimeType;
-
- $supportedModules = array( /** TODO: add things here */ );
- $unsupported = array_diff( $modules, $supportedModules );
-
- $params = array(
- 'type' => $wgJsMimeType,
- 'src' => "$wgStylePath/common/jquery.min.js?$wgStyleVersion",
- );
- if ( !$this->mJQueryDone ) {
- $this->mJQueryDone = true;
- $this->mScripts = Html::element( 'script', $params ) . "\n" . $this->mScripts;
- }
- return $unsupported;
+ return array();
}
}
diff --git a/includes/PageQueryPage.php b/includes/PageQueryPage.php
index a2091e8b..892ff259 100644
--- a/includes/PageQueryPage.php
+++ b/includes/PageQueryPage.php
@@ -18,8 +18,9 @@ class PageQueryPage extends QueryPage {
global $wgContLang;
$title = Title::makeTitleSafe( $row->namespace, $row->title );
$text = $row->title;
- if ($title instanceof Title)
- $text = $wgContLang->convert( $title->getPrefixedText() );
- return $skin->link( $title, htmlspecialchars($text), array(), array(), array('known', 'noclasses') );
+ if ( $title instanceof Title ) {
+ $text = $wgContLang->convert( $title->getPrefixedText() );
+ }
+ return $skin->link( $title, htmlspecialchars( $text ), array(), array(), array('known', 'noclasses') );
}
}
diff --git a/includes/Pager.php b/includes/Pager.php
index e5eef1fc..19b61e8d 100644
--- a/includes/Pager.php
+++ b/includes/Pager.php
@@ -164,7 +164,7 @@ abstract class IndexPager implements Pager {
}
/**
- * Return the result wrapper.
+ * @return ResultWrapper The result wrapper.
*/
function getResult() {
return $this->mResult;
@@ -186,12 +186,20 @@ abstract class IndexPager implements Pager {
/**
* Extract some useful data from the result object for use by
* the navigation bar, put it into $this
+ *
+ * @param $offset String: index offset, inclusive
+ * @param $limit Integer: exact query limit
+ * @param $res ResultWrapper
*/
function extractResultInfo( $offset, $limit, ResultWrapper $res ) {
$numRows = $res->numRows();
if ( $numRows ) {
+ # Remove any table prefix from index field
+ $parts = explode( '.', $this->mIndexField );
+ $indexColumn = end( $parts );
+
$row = $res->fetchRow();
- $firstIndex = $row[$this->mIndexField];
+ $firstIndex = $row[$indexColumn];
# Discard the extra result row if there is one
if ( $numRows > $this->mLimit && $numRows > 1 ) {
@@ -201,7 +209,7 @@ abstract class IndexPager implements Pager {
$this->mPastTheEndIndex = $this->mPastTheEndRow->$indexField;
$res->seek( $numRows - 2 );
$row = $res->fetchRow();
- $lastIndex = $row[$this->mIndexField];
+ $lastIndex = $row[$indexColumn];
} else {
$this->mPastTheEndRow = null;
# Setting indexes to an empty string means that they will be
@@ -211,7 +219,7 @@ abstract class IndexPager implements Pager {
$this->mPastTheEndIndex = '';
$res->seek( $numRows - 1 );
$row = $res->fetchRow();
- $lastIndex = $row[$this->mIndexField];
+ $lastIndex = $row[$indexColumn];
}
} else {
$firstIndex = '';
@@ -235,6 +243,8 @@ abstract class IndexPager implements Pager {
/**
* Get some text to go in brackets in the "function name" part of the SQL comment
+ *
+ * @return String
*/
function getSqlComment() {
return get_class( $this );
@@ -244,9 +254,9 @@ abstract class IndexPager implements Pager {
* Do a query with specified parameters, rather than using the object
* context
*
- * @param string $offset Index offset, inclusive
- * @param integer $limit Exact query limit
- * @param boolean $descending Query direction, false for ascending, true for descending
+ * @param $offset String: index offset, inclusive
+ * @param $limit Integer: exact query limit
+ * @param $descending Boolean: query direction, false for ascending, true for descending
* @return ResultWrapper
*/
function reallyDoQuery( $offset, $limit, $descending ) {
@@ -275,13 +285,15 @@ abstract class IndexPager implements Pager {
/**
* Pre-process results; useful for performing batch existence checks, etc.
*
- * @param ResultWrapper $result Result wrapper
+ * @param $result ResultWrapper
*/
protected function preprocessResults( $result ) {}
/**
* Get the formatted result list. Calls getStartBody(), formatRow() and
* getEndBody(), concatenates the results and returns them.
+ *
+ * @return String
*/
function getBody() {
if ( !$this->mQueryDone ) {
@@ -314,6 +326,11 @@ abstract class IndexPager implements Pager {
/**
* Make a self-link
+ *
+ * @param $text String: text displayed on the link
+ * @param $query Array: associative array of paramter to be in the query string
+ * @param $type String: value of the "rel" attribute
+ * @return String: HTML fragment
*/
function makeLink($text, $query = null, $type=null) {
if ( $query === null ) {
@@ -341,6 +358,8 @@ abstract class IndexPager implements Pager {
/**
* Hook into getBody(), allows text to be inserted at the start. This
* will be called even if there are no rows in the result set.
+ *
+ * @return String
*/
function getStartBody() {
return '';
@@ -348,6 +367,8 @@ abstract class IndexPager implements Pager {
/**
* Hook into getBody() for the end of the list
+ *
+ * @return String
*/
function getEndBody() {
return '';
@@ -356,6 +377,8 @@ abstract class IndexPager implements Pager {
/**
* Hook into getBody(), for the bit between the start and the
* end when there are no rows
+ *
+ * @return String
*/
function getEmptyBody() {
return '';
@@ -364,6 +387,8 @@ abstract class IndexPager implements Pager {
/**
* Title used for self-links. Override this if you want to be able to
* use a title other than $wgTitle
+ *
+ * @return Title object
*/
function getTitle() {
return $GLOBALS['wgTitle'];
@@ -371,6 +396,8 @@ abstract class IndexPager implements Pager {
/**
* Get the current skin. This can be overridden if necessary.
+ *
+ * @return Skin object
*/
function getSkin() {
if ( !isset( $this->mSkin ) ) {
@@ -384,6 +411,8 @@ abstract class IndexPager implements Pager {
* Get an array of query parameters that should be put into self-links.
* By default, all parameters passed in the URL are used, except for a
* short blacklist.
+ *
+ * @return Associative array
*/
function getDefaultQuery() {
if ( !isset( $this->mDefaultQuery ) ) {
@@ -401,6 +430,8 @@ abstract class IndexPager implements Pager {
/**
* Get the number of rows in the result set
+ *
+ * @return Integer
*/
function getNumRows() {
if ( !$this->mQueryDone ) {
@@ -411,6 +442,8 @@ abstract class IndexPager implements Pager {
/**
* Get a URL query array for the prev, next, first and last links.
+ *
+ * @return Array
*/
function getPagingQueries() {
if ( !$this->mQueryDone ) {
@@ -446,6 +479,11 @@ abstract class IndexPager implements Pager {
);
}
+ /**
+ * Returns whether to show the "navigation bar"
+ *
+ * @return Boolean
+ */
function isNavigationBarShown() {
if ( !$this->mQueryDone ) {
$this->doQuery();
@@ -459,6 +497,8 @@ abstract class IndexPager implements Pager {
* will be used. If there is no such item, the unlinked text from
* $linkTexts will be used. Both $linkTexts and $disabledTexts are arrays
* of HTML.
+ *
+ * @return Array
*/
function getPagingLinks( $linkTexts, $disabledTexts = array() ) {
$queries = $this->getPagingQueries();
@@ -501,6 +541,9 @@ abstract class IndexPager implements Pager {
* Abstract formatting function. This should return an HTML string
* representing the result row $row. Rows will be concatenated and
* returned by getBody()
+ *
+ * @param $row Object: database row
+ * @return String
*/
abstract function formatRow( $row );
@@ -513,6 +556,8 @@ abstract class IndexPager implements Pager {
* conds => WHERE conditions
* options => option array
* join_conds => JOIN conditions
+ *
+ * @return Array
*/
abstract function getQueryInfo();
@@ -544,6 +589,8 @@ abstract class IndexPager implements Pager {
* $this->mDefaultDirection member variable. That's the default for this
* particular instantiation, which is a single value. This is the set of
* all defaults for the class.
+ *
+ * @return Boolean
*/
protected function getDefaultDirections() { return false; }
}
@@ -632,7 +679,8 @@ abstract class AlphabeticPager extends IndexPager {
* enabling each one in getNavigationBar. The return type is an associa-
* tive array whose keys must exactly match the keys of the array returned
* by getIndexField(), and whose values are message keys.
- * @return array
+ *
+ * @return Array
*/
protected function getOrderTypeMessages() {
return null;
@@ -773,7 +821,7 @@ abstract class TablePager extends IndexPager {
# Make table header
foreach ( $fields as $field => $name ) {
if ( strval( $name ) == '' ) {
- $s .= "<th>&nbsp;</th>\n";
+ $s .= "<th>&#160;</th>\n";
} elseif ( $this->isFieldSortable( $field ) ) {
$query = array( 'sort' => $field, 'limit' => $this->mLimit );
if ( $field == $this->mSort ) {
@@ -826,7 +874,7 @@ abstract class TablePager extends IndexPager {
$value = isset( $row->$field ) ? $row->$field : null;
$formatted = strval( $this->formatValue( $field, $value ) );
if ( $formatted == '' ) {
- $formatted = '&nbsp;';
+ $formatted = '&#160;';
}
$s .= Xml::tags( 'td', $this->getCellAttrs( $field, $value ), $formatted );
}
@@ -836,7 +884,9 @@ abstract class TablePager extends IndexPager {
/**
* Get a class name to be applied to the given row.
- * @param object $row The database result row
+ *
+ * @param $row Object: the database result row
+ * @return String
*/
function getRowClass( $row ) {
return '';
@@ -844,20 +894,28 @@ abstract class TablePager extends IndexPager {
/**
* Get attributes to be applied to the given row.
- * @param object $row The database result row
- * @return associative array
+ *
+ * @param $row Object: the database result row
+ * @return Associative array
*/
function getRowAttrs( $row ) {
- return array( 'class' => $this->getRowClass( $row ) );
+ $class = $this->getRowClass( $row );
+ if ( $class === '' ) {
+ // Return an empty array to avoid clutter in HTML like class=""
+ return array();
+ } else {
+ return array( 'class' => $this->getRowClass( $row ) );
+ }
}
/**
* Get any extra attributes to be applied to the given cell. Don't
* take this as an excuse to hardcode styles; use classes and
* CSS instead. Row context is available in $this->mCurrentRow
+ *
* @param $field The column
* @param $value The cell contents
- * @return associative array
+ * @return Associative array
*/
function getCellAttrs( $field, $value ) {
return array( 'class' => 'TablePager_col_' . $field );
@@ -885,7 +943,9 @@ abstract class TablePager extends IndexPager {
function getNavigationBar() {
global $wgStylePath, $wgContLang;
- if ( !$this->isNavigationBarShown() ) return '';
+ if ( !$this->isNavigationBarShown() ) {
+ return '';
+ }
$path = "$wgStylePath/common/images";
$labels = array(
@@ -933,10 +993,19 @@ abstract class TablePager extends IndexPager {
/**
* Get a <select> element which has options for each of the allowed limits
+ *
+ * @return String: HTML fragment
*/
function getLimitSelect() {
global $wgLang;
- $s = "<select name=\"limit\">";
+
+ # Add the current limit from the query string
+ # to avoid that the limit is lost after clicking Go next time
+ if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) {
+ $this->mLimitsShown[] = $this->mLimit;
+ sort( $this->mLimitsShown );
+ }
+ $s = Html::openElement( 'select', array( 'name' => 'limit' ) ) . "\n";
foreach ( $this->mLimitsShown as $key => $value ) {
# The pair is either $index => $limit, in which case the $value
# will be numeric, or $limit => $text, in which case the $value
@@ -948,10 +1017,9 @@ abstract class TablePager extends IndexPager {
$limit = $key;
$text = $value;
}
- $selected = ( $limit == $this->mLimit ? 'selected="selected"' : '' );
- $s .= "<option value=\"$limit\" $selected>$text</option>\n";
+ $s .= Xml::option( $text, $limit, $limit == $this->mLimit ) . "\n";
}
- $s .= "</select>";
+ $s .= Html::closeElement( 'select' );
return $s;
}
@@ -959,6 +1027,8 @@ abstract class TablePager extends IndexPager {
* Get <input type="hidden"> elements for use in a method="get" form.
* Resubmits all defined elements of the $_GET array, except for a
* blacklist, passed in the $blacklist parameter.
+ *
+ * @return String: HTML fragment
*/
function getHiddenFields( $blacklist = array() ) {
$blacklist = (array)$blacklist;
@@ -977,43 +1047,51 @@ abstract class TablePager extends IndexPager {
/**
* Get a form containing a limit selection dropdown
+ *
+ * @return String: HTML fragment
*/
function getLimitForm() {
global $wgScript;
+ return Xml::openElement(
+ 'form',
+ array(
+ 'method' => 'get',
+ 'action' => $wgScript
+ ) ) . "\n" . $this->getLimitDropdown() . "</form>\n";
+ }
+
+ /**
+ * Gets a limit selection dropdown
+ *
+ * @return string
+ */
+ function getLimitDropdown() {
# Make the select with some explanatory text
$msgSubmit = wfMsgHtml( 'table_pager_limit_submit' );
- return
- Xml::openElement(
- 'form',
- array(
- 'method' => 'get',
- 'action' => $wgScript
- )
- ) . "\n" .
- wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) .
+
+ return wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) .
"\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" .
- $this->getHiddenFields( array( 'limit' ) ) .
- "</form>\n";
+ $this->getHiddenFields( array( 'limit' ) );
}
/**
* Return true if the named field should be sortable by the UI, false
* otherwise
*
- * @param string $field
+ * @param $field String
*/
abstract function isFieldSortable( $field );
/**
* Format a table cell. The return value should be HTML, but use an empty
- * string not &nbsp; for empty cells. Do not include the <td> and </td>.
+ * string not &#160; for empty cells. Do not include the <td> and </td>.
*
* The current result row is available as $this->mCurrentRow, in case you
* need more context.
*
- * @param string $name The database field name
- * @param string $value The value retrieved from the database
+ * @param $name String: the database field name
+ * @param $value String: the value retrieved from the database
*/
abstract function formatValue( $name, $value );
diff --git a/includes/PoolCounter.php b/includes/PoolCounter.php
index 2564fbc6..3851767f 100644
--- a/includes/PoolCounter.php
+++ b/includes/PoolCounter.php
@@ -1,6 +1,68 @@
<?php
+/**
+ * When you have many workers (threads/servers) giving service, and a
+ * cached item expensive to produce expires, you may get several workers
+ * doing the job at the same time.
+ * Given enough requests and the item expiring fast (non-cacheable,
+ * lots of edits...) that single work can end up unfairly using most (all)
+ * of the cpu of the pool. This is also known as 'Michael Jackson effect'.
+ * The PoolCounter provides semaphore semantics for restricting the number
+ * of workers that may be concurrently performing such single task.
+ *
+ * By default PoolCounter_Stub is used, which provides no locking. You
+ * can get a useful one in the PoolCounter extension.
+ */
abstract class PoolCounter {
+
+ /* Return codes */
+ const LOCKED = 1; /* Lock acquired */
+ const RELEASED = 2; /* Lock released */
+ const DONE = 3; /* Another one did the work for you */
+
+ const ERROR = -1; /* Indeterminate error */
+ const NOT_LOCKED = -2; /* Called release() with no lock held */
+ const QUEUE_FULL = -3; /* There are already maxqueue workers on this lock */
+ const TIMEOUT = -4; /* Timeout exceeded */
+ const LOCK_HELD = -5; /* Cannot acquire another lock while you have one lock held */
+
+ /**
+ * I want to do this task and I need to do it myself.
+ *
+ * @return Locked/Error
+ */
+ abstract function acquireForMe();
+
+ /**
+ * I want to do this task, but if anyone else does it
+ * instead, it's also fine for me. I will read its cached data.
+ *
+ * @return Locked/Done/Error
+ */
+ abstract function acquireForAnyone();
+
+ /**
+ * I have successfully finished my task.
+ * Lets another one grab the lock, and returns the workers
+ * waiting on acquireForAnyone()
+ *
+ * @return Released/NotLocked/Error
+ */
+ abstract function release();
+
+ /**
+ * $key: All workers with the same key share the lock.
+ * $workers: It wouldn't be a good idea to have more than this number of
+ * workers doing the task simultaneously.
+ * $maxqueue: If this number of workers are already working/waiting,
+ * fail instead of wait.
+ * $timeout: Maximum time in seconds to wait for the lock.
+ */
+ protected $key, $workers, $maxqueue, $timeout;
+
+ /**
+ * Create a Pool counter. This should only be called from the PoolWorks.
+ */
public static function factory( $type, $key ) {
global $wgPoolCounterConf;
if ( !isset( $wgPoolCounterConf[$type] ) ) {
@@ -8,57 +70,120 @@ abstract class PoolCounter {
}
$conf = $wgPoolCounterConf[$type];
$class = $conf['class'];
+
return new $class( $conf, $type, $key );
}
-
- abstract public function acquire();
- abstract public function release();
- abstract public function wait();
-
- public function executeProtected( $mainCallback, $dirtyCallback = false ) {
- $status = $this->acquire();
- if ( !$status->isOK() ) {
- return $status;
- }
- if ( !empty( $status->value['overload'] ) ) {
- # Overloaded. Try a dirty cache entry.
- if ( $dirtyCallback ) {
- if ( call_user_func( $dirtyCallback ) ) {
- $this->release();
- return Status::newGood();
- }
- }
-
- # Wait for a thread
- $status = $this->wait();
- if ( !$status->isOK() ) {
- $this->release();
- return $status;
- }
- }
- # Call the main callback
- call_user_func( $mainCallback );
- return $this->release();
+
+ protected function __construct( $conf, $type, $key ) {
+ $this->key = $key;
+ $this->workers = $conf['workers'];
+ $this->maxqueue = $conf['maxqueue'];
+ $this->timeout = $conf['timeout'];
}
}
class PoolCounter_Stub extends PoolCounter {
- public function acquire() {
- return Status::newGood();
+ function acquireForMe() {
+ return Status::newGood( PoolCounter::LOCKED );
}
- public function release() {
- return Status::newGood();
+ function acquireForAnyone() {
+ return Status::newGood( PoolCounter::LOCKED );
}
- public function wait() {
- return Status::newGood();
+ function release() {
+ return Status::newGood( PoolCounter::RELEASED );
}
-
- public function executeProtected( $mainCallback, $dirtyCallback = false ) {
- call_user_func( $mainCallback );
- return Status::newGood();
+
+ public function __construct() {
+ /* No parameters needed */
}
}
+/**
+ * Handy class for dealing with PoolCounters using class members instead of callbacks.
+ */
+abstract class PoolCounterWork {
+ protected $cacheable = false; //Does this override getCachedWork() ?
+
+ /**
+ * Actually perform the work, caching it if needed.
+ */
+ abstract function doWork();
+ /**
+ * Retrieve the work from cache
+ * @return mixed work result or false
+ */
+ function getCachedWork() {
+ return false;
+ }
+
+ /**
+ * A work not so good (eg. expired one) but better than an error
+ * message.
+ * @return mixed work result or false
+ */
+ function fallback() {
+ return false;
+ }
+
+ /**
+ * Do something with the error, like showing it to the user.
+ */
+ function error( $status ) {
+ return false;
+ }
+
+ /**
+ * Get the result of the work (whatever it is), or false.
+ */
+ function execute( $skipcache = false ) {
+ if ( $this->cacheable && !$skipcache ) {
+ $status = $this->poolCounter->acquireForAnyone();
+ } else {
+ $status = $this->poolCounter->acquireForMe();
+ }
+
+ if ( $status->isOK() ) {
+ switch ( $status->value ) {
+ case PoolCounter::LOCKED:
+ $result = $this->doWork();
+ $this->poolCounter->release();
+ return $result;
+
+ case PoolCounter::DONE:
+ $result = $this->getCachedWork();
+ if ( $result === false ) {
+ /* That someone else work didn't serve us.
+ * Acquire the lock for me
+ */
+ return $this->execute( true );
+ }
+ return $result;
+
+ case PoolCounter::QUEUE_FULL:
+ case PoolCounter::TIMEOUT:
+ $result = $this->fallback();
+
+ if ( $result !== false ) {
+ return $result;
+ }
+ /* no break */
+
+ /* These two cases should never be hit... */
+ case PoolCounter::ERROR:
+ default:
+ $errors = array( PoolCounter::QUEUE_FULL => 'pool-queuefull', PoolCounter::TIMEOUT => 'pool-timeout' );
+
+ $status = Status::newFatal( isset($errors[$status->value]) ? $errors[$status->value] : 'pool-errorunknown' );
+ /* continue to the error */
+ }
+ }
+ return $this->error( $status );
+ }
+
+ function __construct( $type, $key ) {
+ $this->poolCounter = PoolCounter::factory( $type, $key );
+ }
+}
diff --git a/includes/Preferences.php b/includes/Preferences.php
index 70d88ec9..c8ea2cc6 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -1,5 +1,4 @@
<?php
-
/**
* We're now using the HTMLForm object with some customisation to generate the
* Preferences form. This object handles generic submission, CSRF protection,
@@ -25,19 +24,22 @@
* Once fields have been retrieved and validated, submission logic is handed
* over to the tryUISubmit static method of this class.
*/
+
class Preferences {
static $defaultPreferences = null;
- static $saveFilters =
- array(
+ static $saveFilters = array(
'timecorrection' => array( 'Preferences', 'filterTimezoneInput' ),
- );
+ 'cols' => array( 'Preferences', 'filterIntval' ),
+ 'rows' => array( 'Preferences', 'filterIntval' ),
+ 'rclimit' => array( 'Preferences', 'filterIntval' ),
+ 'wllimit' => array( 'Preferences', 'filterIntval' ),
+ 'searchlimit' => array( 'Preferences', 'filterIntval' ),
+ );
static function getPreferences( $user ) {
if ( self::$defaultPreferences )
return self::$defaultPreferences;
- global $wgRCMaxAge;
-
$defaultPreferences = array();
self::profilePreferences( $user, $defaultPreferences );
@@ -63,14 +65,13 @@ class Preferences {
}
## Prod in defaults from the user
- global $wgDefaultUserOptions;
- foreach( $defaultPreferences as $name => &$info ) {
+ foreach ( $defaultPreferences as $name => &$info ) {
$prefFromUser = self::getOptionFromUser( $name, $info, $user );
- $field = HTMLForm::loadInputFromParameters( $info ); // For validation
+ $field = HTMLForm::loadInputFromParameters( $name, $info ); // For validation
$defaultOptions = User::getDefaultOptions();
$globalDefault = isset( $defaultOptions[$name] )
- ? $defaultOptions[$name]
- : null;
+ ? $defaultOptions[$name]
+ : null;
// If it validates, set it as the default
if ( isset( $info['default'] ) ) {
@@ -79,7 +80,7 @@ class Preferences {
} elseif ( !is_null( $prefFromUser ) && // Make sure we're not just pulling nothing
$field->validate( $prefFromUser, $user->mOptions ) === true ) {
$info['default'] = $prefFromUser;
- } elseif( $field->validate( $globalDefault, $user->mOptions ) === true ) {
+ } elseif ( $field->validate( $globalDefault, $user->mOptions ) === true ) {
$info['default'] = $globalDefault;
} else {
throw new MWException( "Global default '$globalDefault' is invalid for field $name" );
@@ -98,13 +99,12 @@ class Preferences {
// Handling for array-type preferences
if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
-
$options = HTMLFormField::flattenOptions( $info['options'] );
$prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
$val = array();
- foreach( $options as $label => $value ) {
- if( $user->getOption( "$prefix$value" ) ) {
+ foreach ( $options as $value ) {
+ if ( $user->getOption( "$prefix$value" ) ) {
$val[] = $value;
}
}
@@ -117,27 +117,25 @@ class Preferences {
global $wgLang, $wgUser;
## User info #####################################
// Information panel
- $defaultPreferences['username'] =
- array(
- 'type' => 'info',
- 'label-message' => 'username',
- 'default' => $user->getName(),
- 'section' => 'personal/info',
- );
+ $defaultPreferences['username'] = array(
+ 'type' => 'info',
+ 'label-message' => 'username',
+ 'default' => $user->getName(),
+ 'section' => 'personal/info',
+ );
- $defaultPreferences['userid'] =
- array(
- 'type' => 'info',
- 'label-message' => 'uid',
- 'default' => $user->getId(),
- 'section' => 'personal/info',
- );
+ $defaultPreferences['userid'] = array(
+ 'type' => 'info',
+ 'label-message' => 'uid',
+ 'default' => $user->getId(),
+ 'section' => 'personal/info',
+ );
# Get groups to which the user belongs
$userEffectiveGroups = $user->getEffectiveGroups();
$userGroups = $userMembers = array();
- foreach( $userEffectiveGroups as $ueg ) {
- if( $ueg == '*' ) {
+ foreach ( $userEffectiveGroups as $ueg ) {
+ if ( $ueg == '*' ) {
// Skip the default * group, seems useless here
continue;
}
@@ -150,147 +148,149 @@ class Preferences {
asort( $userGroups );
asort( $userMembers );
- $defaultPreferences['usergroups'] =
- array(
- 'type' => 'info',
- 'label' => wfMsgExt( 'prefs-memberingroups', 'parseinline',
- $wgLang->formatNum( count($userGroups) ) ),
- 'default' => wfMsgExt( 'prefs-memberingroups-type', array(),
- $wgLang->commaList( $userGroups ),
- $wgLang->commaList( $userMembers )
- ),
- 'raw' => true,
- 'section' => 'personal/info',
- );
+ $defaultPreferences['usergroups'] = array(
+ 'type' => 'info',
+ 'label' => wfMsgExt(
+ 'prefs-memberingroups', 'parseinline',
+ $wgLang->formatNum( count( $userGroups ) )
+ ),
+ 'default' => wfMsgExt(
+ 'prefs-memberingroups-type', array(),
+ $wgLang->commaList( $userGroups ),
+ $wgLang->commaList( $userMembers )
+ ),
+ 'raw' => true,
+ 'section' => 'personal/info',
+ );
- $defaultPreferences['editcount'] =
- array(
- 'type' => 'info',
- 'label-message' => 'prefs-edits',
- 'default' => $wgLang->formatNum( $user->getEditCount() ),
- 'section' => 'personal/info',
- );
+ $defaultPreferences['editcount'] = array(
+ 'type' => 'info',
+ 'label-message' => 'prefs-edits',
+ 'default' => $wgLang->formatNum( $user->getEditCount() ),
+ 'section' => 'personal/info',
+ );
- if( $user->getRegistration() ) {
- $defaultPreferences['registrationdate'] =
- array(
- 'type' => 'info',
- 'label-message' => 'prefs-registration',
- 'default' => wfMsgExt( 'prefs-registration-date-time', 'parsemag',
- $wgLang->timeanddate( $user->getRegistration(), true ),
- $wgLang->date( $user->getRegistration(), true ),
- $wgLang->time( $user->getRegistration(), true ) ),
- 'section' => 'personal/info',
- );
+ if ( $user->getRegistration() ) {
+ $defaultPreferences['registrationdate'] = array(
+ 'type' => 'info',
+ 'label-message' => 'prefs-registration',
+ 'default' => wfMsgExt(
+ 'prefs-registration-date-time', 'parsemag',
+ $wgLang->timeanddate( $user->getRegistration(), true ),
+ $wgLang->date( $user->getRegistration(), true ),
+ $wgLang->time( $user->getRegistration(), true )
+ ),
+ 'section' => 'personal/info',
+ );
}
// Actually changeable stuff
global $wgAuth;
- $defaultPreferences['realname'] =
- array(
- 'type' => $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
- 'default' => $user->getRealName(),
- 'section' => 'personal/info',
- 'label-message' => 'yourrealname',
- 'help-message' => 'prefs-help-realname',
- );
+ $defaultPreferences['realname'] = array(
+ 'type' => $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
+ 'default' => $user->getRealName(),
+ 'section' => 'personal/info',
+ 'label-message' => 'yourrealname',
+ 'help-message' => 'prefs-help-realname',
+ );
- $defaultPreferences['gender'] =
- array(
- 'type' => 'select',
- 'section' => 'personal/info',
- 'options' => array(
- wfMsg( 'gender-male' ) => 'male',
- wfMsg( 'gender-female' ) => 'female',
- wfMsg( 'gender-unknown' ) => 'unknown',
- ),
- 'label-message' => 'yourgender',
- 'help-message' => 'prefs-help-gender',
- );
+ $defaultPreferences['gender'] = array(
+ 'type' => 'select',
+ 'section' => 'personal/info',
+ 'options' => array(
+ wfMsg( 'gender-male' ) => 'male',
+ wfMsg( 'gender-female' ) => 'female',
+ wfMsg( 'gender-unknown' ) => 'unknown',
+ ),
+ 'label-message' => 'yourgender',
+ 'help-message' => 'prefs-help-gender',
+ );
- if( $wgAuth->allowPasswordChange() ) {
+ if ( $wgAuth->allowPasswordChange() ) {
$link = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'Resetpass' ),
wfMsgHtml( 'prefs-resetpass' ), array(),
array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' ) ) );
- $defaultPreferences['password'] =
- array(
- 'type' => 'info',
- 'raw' => true,
- 'default' => $link,
- 'label-message' => 'yourpassword',
- 'section' => 'personal/info',
- );
+ $defaultPreferences['password'] = array(
+ 'type' => 'info',
+ 'raw' => true,
+ 'default' => $link,
+ 'label-message' => 'yourpassword',
+ 'section' => 'personal/info',
+ );
+ }
+ global $wgCookieExpiration;
+ if ( $wgCookieExpiration > 0 ) {
+ $defaultPreferences['rememberpassword'] = array(
+ 'type' => 'toggle',
+ 'label' => wfMsgExt(
+ 'tog-rememberpassword',
+ array( 'parsemag' ),
+ $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )
+ ),
+ 'section' => 'personal/info',
+ );
}
-
- $defaultPreferences['rememberpassword'] =
- array(
- 'type' => 'toggle',
- 'label-message' => 'tog-rememberpassword',
- 'section' => 'personal/info',
- );
// Language
- global $wgContLanguageCode;
- $languages = array_reverse( Language::getLanguageNames( false ) );
- if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
- $languages[$wgContLanguageCode] = $wgContLanguageCode;
+ global $wgLanguageCode;
+ $languages = Language::getLanguageNames( false );
+ if ( !array_key_exists( $wgLanguageCode, $languages ) ) {
+ $languages[$wgLanguageCode] = $wgLanguageCode;
}
ksort( $languages );
$options = array();
- foreach( $languages as $code => $name ) {
+ foreach ( $languages as $code => $name ) {
$display = wfBCP47( $code ) . ' - ' . $name;
$options[$display] = $code;
}
- $defaultPreferences['language'] =
- array(
- 'type' => 'select',
- 'section' => 'personal/i18n',
- 'options' => $options,
- 'label-message' => 'yourlanguage',
- );
+ $defaultPreferences['language'] = array(
+ 'type' => 'select',
+ 'section' => 'personal/i18n',
+ 'options' => $options,
+ 'label-message' => 'yourlanguage',
+ );
global $wgContLang, $wgDisableLangConversion;
global $wgDisableTitleConversion;
/* see if there are multiple language variants to choose from*/
$variantArray = array();
- if( !$wgDisableLangConversion ) {
+ if ( !$wgDisableLangConversion ) {
$variants = $wgContLang->getVariants();
$languages = Language::getLanguageNames( true );
- foreach( $variants as $v ) {
+ foreach ( $variants as $v ) {
$v = str_replace( '_', '-', strtolower( $v ) );
- if( array_key_exists( $v, $languages ) ) {
+ if ( array_key_exists( $v, $languages ) ) {
// If it doesn't have a name, we'll pretend it doesn't exist
$variantArray[$v] = $languages[$v];
}
}
$options = array();
- foreach( $variantArray as $code => $name ) {
+ foreach ( $variantArray as $code => $name ) {
$display = wfBCP47( $code ) . ' - ' . $name;
$options[$display] = $code;
}
- if( count( $variantArray ) > 1 ) {
- $defaultPreferences['variant'] =
- array(
- 'label-message' => 'yourvariant',
- 'type' => 'select',
- 'options' => $options,
- 'section' => 'personal/i18n',
- );
+ if ( count( $variantArray ) > 1 ) {
+ $defaultPreferences['variant'] = array(
+ 'label-message' => 'yourvariant',
+ 'type' => 'select',
+ 'options' => $options,
+ 'section' => 'personal/i18n',
+ );
}
}
- if( count( $variantArray ) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion ) {
+ if ( count( $variantArray ) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion ) {
$defaultPreferences['noconvertlink'] =
array(
- 'type' => 'toggle',
- 'section' => 'personal/i18n',
- 'label-message' => 'tog-noconvertlink',
- );
+ 'type' => 'toggle',
+ 'section' => 'personal/i18n',
+ 'label-message' => 'tog-noconvertlink',
+ );
}
global $wgMaxSigChars, $wgOut, $wgParser;
@@ -298,66 +298,62 @@ class Preferences {
// show a preview of the old signature first
$oldsigWikiText = $wgParser->preSaveTransform( "~~~", new Title , $user, new ParserOptions );
$oldsigHTML = $wgOut->parseInline( $oldsigWikiText );
- $defaultPreferences['oldsig'] =
- array(
- 'type' => 'info',
- 'raw' => true,
- 'label-message' => 'tog-oldsig',
- 'default' => $oldsigHTML,
- 'section' => 'personal/signature',
- );
- $defaultPreferences['nickname'] =
- array(
- 'type' => $wgAuth->allowPropChange( 'nickname' ) ? 'text' : 'info',
- 'maxlength' => $wgMaxSigChars,
- 'label-message' => 'yournick',
- 'validation-callback' =>
- array( 'Preferences', 'validateSignature' ),
- 'section' => 'personal/signature',
- 'filter-callback' => array( 'Preferences', 'cleanSignature' ),
- );
- $defaultPreferences['fancysig'] =
- array(
- 'type' => 'toggle',
- 'label-message' => 'tog-fancysig',
- 'help-message' => 'prefs-help-signature', // show general help about signature at the bottom of the section
- 'section' => 'personal/signature'
- );
-
+ $defaultPreferences['oldsig'] = array(
+ 'type' => 'info',
+ 'raw' => true,
+ 'label-message' => 'tog-oldsig',
+ 'default' => $oldsigHTML,
+ 'section' => 'personal/signature',
+ );
+ $defaultPreferences['nickname'] = array(
+ 'type' => $wgAuth->allowPropChange( 'nickname' ) ? 'text' : 'info',
+ 'maxlength' => $wgMaxSigChars,
+ 'label-message' => 'yournick',
+ 'validation-callback' => array( 'Preferences', 'validateSignature' ),
+ 'section' => 'personal/signature',
+ 'filter-callback' => array( 'Preferences', 'cleanSignature' ),
+ );
+ $defaultPreferences['fancysig'] = array(
+ 'type' => 'toggle',
+ 'label-message' => 'tog-fancysig',
+ 'help-message' => 'prefs-help-signature', // show general help about signature at the bottom of the section
+ 'section' => 'personal/signature'
+ );
+
## Email stuff
-
+
global $wgEnableEmail;
- if ($wgEnableEmail) {
-
+ if ( $wgEnableEmail ) {
global $wgEmailConfirmToEdit;
-
- $defaultPreferences['emailaddress'] =
- array(
- 'type' => $wgAuth->allowPropChange( 'emailaddress' ) ? 'email' : 'info',
- 'default' => $user->getEmail(),
- 'section' => 'personal/email',
- 'label-message' => 'youremail',
- 'help-message' => $wgEmailConfirmToEdit
- ? 'prefs-help-email-required'
- : 'prefs-help-email',
- 'validation-callback' => array( 'Preferences', 'validateEmail' ),
- );
-
+
+ $defaultPreferences['emailaddress'] = array(
+ 'type' => $wgAuth->allowPropChange( 'emailaddress' ) ? 'email' : 'info',
+ 'default' => $user->getEmail(),
+ 'section' => 'personal/email',
+ 'label-message' => 'youremail',
+ 'help-message' => $wgEmailConfirmToEdit
+ ? 'prefs-help-email-required'
+ : 'prefs-help-email',
+ 'validation-callback' => array( 'Preferences', 'validateEmail' ),
+ );
+
global $wgEnableUserEmail, $wgEmailAuthentication;
-
+
$disableEmailPrefs = false;
-
+
if ( $wgEmailAuthentication ) {
if ( $user->getEmail() ) {
- if( $user->getEmailAuthenticationTimestamp() ) {
+ if ( $user->getEmailAuthenticationTimestamp() ) {
// date and time are separate parameters to facilitate localisation.
// $time is kept for backward compat reasons.
// 'emailauthenticated' is also used in SpecialConfirmemail.php
$time = $wgLang->timeAndDate( $user->getEmailAuthenticationTimestamp(), true );
$d = $wgLang->date( $user->getEmailAuthenticationTimestamp(), true );
$t = $wgLang->time( $user->getEmailAuthenticationTimestamp(), true );
- $emailauthenticated = wfMsgExt( 'emailauthenticated', 'parseinline',
- array($time, $d, $t ) ) . '<br />';
+ $emailauthenticated = wfMsgExt(
+ 'emailauthenticated', 'parseinline',
+ array( $time, $d, $t )
+ ) . '<br />';
$disableEmailPrefs = false;
} else {
$disableEmailPrefs = true;
@@ -375,131 +371,149 @@ class Preferences {
$disableEmailPrefs = true;
$emailauthenticated = wfMsgHtml( 'noemailprefs' );
}
-
- $defaultPreferences['emailauthentication'] =
- array(
- 'type' => 'info',
- 'raw' => true,
- 'section' => 'personal/email',
- 'label-message' => 'prefs-emailconfirm-label',
- 'default' => $emailauthenticated,
- );
-
+
+ $defaultPreferences['emailauthentication'] = array(
+ 'type' => 'info',
+ 'raw' => true,
+ 'section' => 'personal/email',
+ 'label-message' => 'prefs-emailconfirm-label',
+ 'default' => $emailauthenticated,
+ );
+
}
-
- if( $wgEnableUserEmail && $user->isAllowed( 'sendemail' ) ) {
- $defaultPreferences['disablemail'] =
- array(
- 'type' => 'toggle',
- 'invert' => true,
- 'section' => 'personal/email',
- 'label-message' => 'allowemail',
- 'disabled' => $disableEmailPrefs,
- );
- $defaultPreferences['ccmeonemails'] =
- array(
- 'type' => 'toggle',
- 'section' => 'personal/email',
- 'label-message' => 'tog-ccmeonemails',
- 'disabled' => $disableEmailPrefs,
- );
+
+ if ( $wgEnableUserEmail && $user->isAllowed( 'sendemail' ) ) {
+ $defaultPreferences['disablemail'] = array(
+ 'type' => 'toggle',
+ 'invert' => true,
+ 'section' => 'personal/email',
+ 'label-message' => 'allowemail',
+ 'disabled' => $disableEmailPrefs,
+ );
+ $defaultPreferences['ccmeonemails'] = array(
+ 'type' => 'toggle',
+ 'section' => 'personal/email',
+ 'label-message' => 'tog-ccmeonemails',
+ 'disabled' => $disableEmailPrefs,
+ );
}
-
+
global $wgEnotifWatchlist;
if ( $wgEnotifWatchlist ) {
- $defaultPreferences['enotifwatchlistpages'] =
- array(
- 'type' => 'toggle',
- 'section' => 'personal/email',
- 'label-message' => 'tog-enotifwatchlistpages',
- 'disabled' => $disableEmailPrefs,
- );
+ $defaultPreferences['enotifwatchlistpages'] = array(
+ 'type' => 'toggle',
+ 'section' => 'personal/email',
+ 'label-message' => 'tog-enotifwatchlistpages',
+ 'disabled' => $disableEmailPrefs,
+ );
}
global $wgEnotifUserTalk;
- if( $wgEnotifUserTalk ) {
- $defaultPreferences['enotifusertalkpages'] =
- array(
- 'type' => 'toggle',
- 'section' => 'personal/email',
- 'label-message' => 'tog-enotifusertalkpages',
- 'disabled' => $disableEmailPrefs,
- );
- }
- if( $wgEnotifUserTalk || $wgEnotifWatchlist ) {
- $defaultPreferences['enotifminoredits'] =
- array(
- 'type' => 'toggle',
- 'section' => 'personal/email',
- 'label-message' => 'tog-enotifminoredits',
- 'disabled' => $disableEmailPrefs,
- );
+ if ( $wgEnotifUserTalk ) {
+ $defaultPreferences['enotifusertalkpages'] = array(
+ 'type' => 'toggle',
+ 'section' => 'personal/email',
+ 'label-message' => 'tog-enotifusertalkpages',
+ 'disabled' => $disableEmailPrefs,
+ );
}
- $defaultPreferences['enotifrevealaddr'] =
- array(
+ if ( $wgEnotifUserTalk || $wgEnotifWatchlist ) {
+ $defaultPreferences['enotifminoredits'] = array(
+ 'type' => 'toggle',
+ 'section' => 'personal/email',
+ 'label-message' => 'tog-enotifminoredits',
+ 'disabled' => $disableEmailPrefs,
+ );
+
+ global $wgEnotifRevealEditorAddress;
+ if ( $wgEnotifRevealEditorAddress ) {
+ $defaultPreferences['enotifrevealaddr'] = array(
'type' => 'toggle',
'section' => 'personal/email',
'label-message' => 'tog-enotifrevealaddr',
'disabled' => $disableEmailPrefs,
);
+ }
+ }
}
}
static function skinPreferences( $user, &$defaultPreferences ) {
## Skin #####################################
- $defaultPreferences['skin'] =
- array(
- 'type' => 'radio',
- 'options' => self::generateSkinOptions( $user ),
- 'label' => '&nbsp;',
- 'section' => 'rendering/skin',
- );
+ global $wgLang, $wgAllowUserCss, $wgAllowUserJs;
+
+ $defaultPreferences['skin'] = array(
+ 'type' => 'radio',
+ 'options' => self::generateSkinOptions( $user ),
+ 'label' => '&#160;',
+ 'section' => 'rendering/skin',
+ );
+
+ # Create links to user CSS/JS pages for all skins
+ # This code is basically copied from generateSkinOptions(). It'd
+ # be nice to somehow merge this back in there to avoid redundancy.
+ if ( $wgAllowUserCss || $wgAllowUserJs ) {
+ $sk = $user->getSkin();
+ $linkTools = array();
+
+ if ( $wgAllowUserCss ) {
+ $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/common.css' );
+ $linkTools[] = $sk->link( $cssPage, wfMsgHtml( 'prefs-custom-css' ) );
+ }
+
+ if ( $wgAllowUserJs ) {
+ $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/common.js' );
+ $linkTools[] = $sk->link( $jsPage, wfMsgHtml( 'prefs-custom-js' ) );
+ }
+
+ $defaultPreferences['commoncssjs'] = array(
+ 'type' => 'info',
+ 'raw' => true,
+ 'default' => $wgLang->pipeList( $linkTools ),
+ 'label-message' => 'prefs-common-css-js',
+ 'section' => 'rendering/skin',
+ );
+ }
$selectedSkin = $user->getOption( 'skin' );
if ( in_array( $selectedSkin, array( 'cologneblue', 'standard' ) ) ) {
- global $wgLang;
$settings = array_flip( $wgLang->getQuickbarSettings() );
- $defaultPreferences['quickbar'] =
- array(
- 'type' => 'radio',
- 'options' => $settings,
- 'section' => 'rendering/skin',
- 'label-message' => 'qbsettings',
- );
+ $defaultPreferences['quickbar'] = array(
+ 'type' => 'radio',
+ 'options' => $settings,
+ 'section' => 'rendering/skin',
+ 'label-message' => 'qbsettings',
+ );
}
}
static function mathPreferences( $user, &$defaultPreferences ) {
## Math #####################################
global $wgUseTeX, $wgLang;
- if( $wgUseTeX ) {
- $defaultPreferences['math'] =
- array(
- 'type' => 'radio',
- 'options' =>
- array_flip( array_map( 'wfMsgHtml', $wgLang->getMathNames() ) ),
- 'label' => '&nbsp;',
- 'section' => 'rendering/math',
- );
+ if ( $wgUseTeX ) {
+ $defaultPreferences['math'] = array(
+ 'type' => 'radio',
+ 'options' => array_flip( array_map( 'wfMsgHtml', $wgLang->getMathNames() ) ),
+ 'label' => '&#160;',
+ 'section' => 'rendering/math',
+ );
}
}
static function filesPreferences( $user, &$defaultPreferences ) {
## Files #####################################
- $defaultPreferences['imagesize'] =
- array(
- 'type' => 'select',
- 'options' => self::getImageSizes(),
- 'label-message' => 'imagemaxsize',
- 'section' => 'rendering/files',
- );
- $defaultPreferences['thumbsize'] =
- array(
- 'type' => 'select',
- 'options' => self::getThumbSizes(),
- 'label-message' => 'thumbsize',
- 'section' => 'rendering/files',
- );
+ $defaultPreferences['imagesize'] = array(
+ 'type' => 'select',
+ 'options' => self::getImageSizes(),
+ 'label-message' => 'imagemaxsize',
+ 'section' => 'rendering/files',
+ );
+ $defaultPreferences['thumbsize'] = array(
+ 'type' => 'select',
+ 'options' => self::getThumbSizes(),
+ 'label-message' => 'thumbsize',
+ 'section' => 'rendering/files',
+ );
}
static function datetimePreferences( $user, &$defaultPreferences ) {
@@ -507,380 +521,353 @@ class Preferences {
## Date and time #####################################
$dateOptions = self::getDateOptions();
- if( $dateOptions ) {
- $defaultPreferences['date'] =
- array(
- 'type' => 'radio',
- 'options' => $dateOptions,
- 'label' => '&nbsp;',
- 'section' => 'datetime/dateformat',
- );
+ if ( $dateOptions ) {
+ $defaultPreferences['date'] = array(
+ 'type' => 'radio',
+ 'options' => $dateOptions,
+ 'label' => '&#160;',
+ 'section' => 'datetime/dateformat',
+ );
}
// Info
$nowlocal = Xml::element( 'span', array( 'id' => 'wpLocalTime' ),
$wgLang->time( $now = wfTimestampNow(), true ) );
$nowserver = $wgLang->time( $now, false ) .
- Xml::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) );
-
- $defaultPreferences['nowserver'] =
- array(
- 'type' => 'info',
- 'raw' => 1,
- 'label-message' => 'servertime',
- 'default' => $nowserver,
- 'section' => 'datetime/timeoffset',
- );
+ Html::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) );
+
+ $defaultPreferences['nowserver'] = array(
+ 'type' => 'info',
+ 'raw' => 1,
+ 'label-message' => 'servertime',
+ 'default' => $nowserver,
+ 'section' => 'datetime/timeoffset',
+ );
- $defaultPreferences['nowlocal'] =
- array(
- 'type' => 'info',
- 'raw' => 1,
- 'label-message' => 'localtime',
- 'default' => $nowlocal,
- 'section' => 'datetime/timeoffset',
- );
+ $defaultPreferences['nowlocal'] = array(
+ 'type' => 'info',
+ 'raw' => 1,
+ 'label-message' => 'localtime',
+ 'default' => $nowlocal,
+ 'section' => 'datetime/timeoffset',
+ );
// Grab existing pref.
$tzOffset = $user->getOption( 'timecorrection' );
$tz = explode( '|', $tzOffset, 2 );
$tzSetting = $tzOffset;
- if( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
+ if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
$minDiff = $tz[1];
- $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff/60 ), abs( $minDiff )%60 );
+ $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
}
- $defaultPreferences['timecorrection'] =
- array(
- 'class' => 'HTMLSelectOrOtherField',
- 'label-message' => 'timezonelegend',
- 'options' => self::getTimezoneOptions(),
- 'default' => $tzSetting,
- 'size' => 20,
- 'section' => 'datetime/timeoffset',
- );
+ $defaultPreferences['timecorrection'] = array(
+ 'class' => 'HTMLSelectOrOtherField',
+ 'label-message' => 'timezonelegend',
+ 'options' => self::getTimezoneOptions(),
+ 'default' => $tzSetting,
+ 'size' => 20,
+ 'section' => 'datetime/timeoffset',
+ );
}
static function renderingPreferences( $user, &$defaultPreferences ) {
## Page Rendering ##############################
- $defaultPreferences['underline'] =
- array(
- 'type' => 'select',
- 'options' => array(
- wfMsg( 'underline-never' ) => 0,
- wfMsg( 'underline-always' ) => 1,
- wfMsg( 'underline-default' ) => 2,
- ),
- 'label-message' => 'tog-underline',
- 'section' => 'rendering/advancedrendering',
- );
+ global $wgAllowUserCssPrefs;
+ if ( $wgAllowUserCssPrefs ) {
+ $defaultPreferences['underline'] = array(
+ 'type' => 'select',
+ 'options' => array(
+ wfMsg( 'underline-never' ) => 0,
+ wfMsg( 'underline-always' ) => 1,
+ wfMsg( 'underline-default' ) => 2,
+ ),
+ 'label-message' => 'tog-underline',
+ 'section' => 'rendering/advancedrendering',
+ );
+ }
- $stubThresholdValues = array( 0, 50, 100, 500, 1000, 2000, 5000, 10000 );
- $stubThresholdOptions = array();
- foreach( $stubThresholdValues as $value ) {
+ $stubThresholdValues = array( 50, 100, 500, 1000, 2000, 5000, 10000 );
+ $stubThresholdOptions = array( wfMsg( 'stub-threshold-disabled' ) => 0 );
+ foreach ( $stubThresholdValues as $value ) {
$stubThresholdOptions[wfMsg( 'size-bytes', $value )] = $value;
}
- $defaultPreferences['stubthreshold'] =
- array(
- 'type' => 'selectorother',
- 'section' => 'rendering/advancedrendering',
- 'options' => $stubThresholdOptions,
- 'size' => 20,
- 'label' => wfMsg( 'stub-threshold' ), // Raw HTML message. Yay?
- );
- $defaultPreferences['highlightbroken'] =
- array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label' => wfMsg( 'tog-highlightbroken' ), // Raw HTML
- );
- $defaultPreferences['showtoc'] =
- array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-showtoc',
- );
- $defaultPreferences['nocache'] =
- array(
- 'type' => 'toggle',
- 'label-message' => 'tog-nocache',
- 'section' => 'rendering/advancedrendering',
- );
- $defaultPreferences['showhiddencats'] =
- array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-showhiddencats'
- );
- $defaultPreferences['showjumplinks'] =
- array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-showjumplinks',
- );
- $defaultPreferences['justify'] =
- array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-justify',
- );
- $defaultPreferences['numberheadings'] =
- array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-numberheadings',
- );
+ $defaultPreferences['stubthreshold'] = array(
+ 'type' => 'selectorother',
+ 'section' => 'rendering/advancedrendering',
+ 'options' => $stubThresholdOptions,
+ 'size' => 20,
+ 'label' => wfMsg( 'stub-threshold' ), // Raw HTML message. Yay?
+ );
+
+ if ( $wgAllowUserCssPrefs ) {
+ $defaultPreferences['highlightbroken'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rendering/advancedrendering',
+ 'label' => wfMsg( 'tog-highlightbroken' ), // Raw HTML
+ );
+ $defaultPreferences['showtoc'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rendering/advancedrendering',
+ 'label-message' => 'tog-showtoc',
+ );
+ }
+ $defaultPreferences['nocache'] = array(
+ 'type' => 'toggle',
+ 'label-message' => 'tog-nocache',
+ 'section' => 'rendering/advancedrendering',
+ );
+ $defaultPreferences['showhiddencats'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rendering/advancedrendering',
+ 'label-message' => 'tog-showhiddencats'
+ );
+ $defaultPreferences['showjumplinks'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rendering/advancedrendering',
+ 'label-message' => 'tog-showjumplinks',
+ );
+
+ if ( $wgAllowUserCssPrefs ) {
+ $defaultPreferences['justify'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rendering/advancedrendering',
+ 'label-message' => 'tog-justify',
+ );
+ }
+
+ $defaultPreferences['numberheadings'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rendering/advancedrendering',
+ 'label-message' => 'tog-numberheadings',
+ );
}
static function editingPreferences( $user, &$defaultPreferences ) {
- global $wgUseExternalEditor, $wgLivePreview;
+ global $wgUseExternalEditor, $wgAllowUserCssPrefs;
## Editing #####################################
- $defaultPreferences['cols'] =
- array(
- 'type' => 'int',
- 'label-message' => 'columns',
- 'section' => 'editing/textboxsize',
- 'min' => 4,
- 'max' => 1000,
- );
- $defaultPreferences['rows'] =
- array(
- 'type' => 'int',
- 'label-message' => 'rows',
- 'section' => 'editing/textboxsize',
- 'min' => 4,
- 'max' => 1000,
- );
+ $defaultPreferences['cols'] = array(
+ 'type' => 'int',
+ 'label-message' => 'columns',
+ 'section' => 'editing/textboxsize',
+ 'min' => 4,
+ 'max' => 1000,
+ );
+ $defaultPreferences['rows'] = array(
+ 'type' => 'int',
+ 'label-message' => 'rows',
+ 'section' => 'editing/textboxsize',
+ 'min' => 4,
+ 'max' => 1000,
+ );
- $defaultPreferences['editfont'] =
- array(
- 'type' => 'select',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'editfont-style',
- 'options' => array(
- wfMsg( 'editfont-default' ) => 'default',
- wfMsg( 'editfont-monospace' ) => 'monospace',
- wfMsg( 'editfont-sansserif' ) => 'sans-serif',
- wfMsg( 'editfont-serif' ) => 'serif',
- )
- );
- $defaultPreferences['previewontop'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-previewontop',
- );
- $defaultPreferences['previewonfirst'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-previewonfirst',
- );
- $defaultPreferences['editsection'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-editsection',
- );
- $defaultPreferences['editsectiononrightclick'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-editsectiononrightclick',
- );
- $defaultPreferences['editondblclick'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-editondblclick',
- );
- $defaultPreferences['editwidth'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-editwidth',
- );
- $defaultPreferences['showtoolbar'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-showtoolbar',
- );
- $defaultPreferences['minordefault'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-minordefault',
- );
+ if ( $wgAllowUserCssPrefs ) {
+ $defaultPreferences['editfont'] = array(
+ 'type' => 'select',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'editfont-style',
+ 'options' => array(
+ wfMsg( 'editfont-default' ) => 'default',
+ wfMsg( 'editfont-monospace' ) => 'monospace',
+ wfMsg( 'editfont-sansserif' ) => 'sans-serif',
+ wfMsg( 'editfont-serif' ) => 'serif',
+ )
+ );
+ }
+ $defaultPreferences['previewontop'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-previewontop',
+ );
+ $defaultPreferences['previewonfirst'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-previewonfirst',
+ );
- if ( $wgUseExternalEditor ) {
- $defaultPreferences['externaleditor'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-externaleditor',
- );
- $defaultPreferences['externaldiff'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-externaldiff',
- );
+ if ( $wgAllowUserCssPrefs ) {
+ $defaultPreferences['editsection'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-editsection',
+ );
}
+ $defaultPreferences['editsectiononrightclick'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-editsectiononrightclick',
+ );
+ $defaultPreferences['editondblclick'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-editondblclick',
+ );
+ $defaultPreferences['showtoolbar'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-showtoolbar',
+ );
+ $defaultPreferences['minordefault'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-minordefault',
+ );
- $defaultPreferences['forceeditsummary'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-forceeditsummary',
- );
- if ( $wgLivePreview ) {
- $defaultPreferences['uselivepreview'] =
- array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-uselivepreview',
- );
+ if ( $wgUseExternalEditor ) {
+ $defaultPreferences['externaleditor'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-externaleditor',
+ );
+ $defaultPreferences['externaldiff'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-externaldiff',
+ );
}
+
+ $defaultPreferences['forceeditsummary'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-forceeditsummary',
+ );
+
+
+ $defaultPreferences['uselivepreview'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-uselivepreview',
+ );
}
static function rcPreferences( $user, &$defaultPreferences ) {
global $wgRCMaxAge, $wgUseRCPatrol, $wgLang;
+
## RecentChanges #####################################
- $defaultPreferences['rcdays'] =
- array(
- 'type' => 'float',
- 'label-message' => 'recentchangesdays',
- 'section' => 'rc/display',
- 'min' => 1,
- 'max' => ceil( $wgRCMaxAge / ( 3600*24 ) ),
- 'help' => wfMsgExt( 'recentchangesdays-max', array( 'parsemag' ), $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600*24 ) ) ) ),
- );
- $defaultPreferences['rclimit'] =
- array(
- 'type' => 'int',
- 'label-message' => 'recentchangescount',
- 'help-message' => 'prefs-help-recentchangescount',
- 'section' => 'rc/display',
- );
- $defaultPreferences['usenewrc'] =
- array(
- 'type' => 'toggle',
- 'label-message' => 'tog-usenewrc',
- 'section' => 'rc/advancedrc',
- );
- $defaultPreferences['hideminor'] =
- array(
- 'type' => 'toggle',
- 'label-message' => 'tog-hideminor',
- 'section' => 'rc/advancedrc',
- );
+ $defaultPreferences['rcdays'] = array(
+ 'type' => 'float',
+ 'label-message' => 'recentchangesdays',
+ 'section' => 'rc/displayrc',
+ 'min' => 1,
+ 'max' => ceil( $wgRCMaxAge / ( 3600 * 24 ) ),
+ 'help' => wfMsgExt(
+ 'recentchangesdays-max',
+ array( 'parsemag' ),
+ $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600 * 24 ) ) )
+ )
+ );
+ $defaultPreferences['rclimit'] = array(
+ 'type' => 'int',
+ 'label-message' => 'recentchangescount',
+ 'help-message' => 'prefs-help-recentchangescount',
+ 'section' => 'rc/displayrc',
+ );
+ $defaultPreferences['usenewrc'] = array(
+ 'type' => 'toggle',
+ 'label-message' => 'tog-usenewrc',
+ 'section' => 'rc/advancedrc',
+ );
+ $defaultPreferences['hideminor'] = array(
+ 'type' => 'toggle',
+ 'label-message' => 'tog-hideminor',
+ 'section' => 'rc/advancedrc',
+ );
- if( $wgUseRCPatrol ) {
- $defaultPreferences['hidepatrolled'] =
- array(
- 'type' => 'toggle',
- 'section' => 'rc/advancedrc',
- 'label-message' => 'tog-hidepatrolled',
- );
- $defaultPreferences['newpageshidepatrolled'] =
- array(
- 'type' => 'toggle',
- 'section' => 'rc/advancedrc',
- 'label-message' => 'tog-newpageshidepatrolled',
- );
+ if ( $wgUseRCPatrol ) {
+ $defaultPreferences['hidepatrolled'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rc/advancedrc',
+ 'label-message' => 'tog-hidepatrolled',
+ );
+ $defaultPreferences['newpageshidepatrolled'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rc/advancedrc',
+ 'label-message' => 'tog-newpageshidepatrolled',
+ );
}
global $wgRCShowWatchingUsers;
- if( $wgRCShowWatchingUsers ) {
- $defaultPreferences['shownumberswatching'] =
- array(
- 'type' => 'toggle',
- 'section' => 'rc/advancedrc',
- 'label-message' => 'tog-shownumberswatching',
- );
+ if ( $wgRCShowWatchingUsers ) {
+ $defaultPreferences['shownumberswatching'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rc/advancedrc',
+ 'label-message' => 'tog-shownumberswatching',
+ );
}
}
static function watchlistPreferences( $user, &$defaultPreferences ) {
global $wgUseRCPatrol, $wgEnableAPI;
+
## Watchlist #####################################
- $defaultPreferences['watchlistdays'] =
- array(
- 'type' => 'float',
- 'min' => 0,
- 'max' => 7,
- 'section' => 'watchlist/display',
- 'help' => wfMsgHtml( 'prefs-watchlist-days-max' ),
- 'label-message' => 'prefs-watchlist-days',
- );
- $defaultPreferences['wllimit'] =
- array(
- 'type' => 'int',
- 'min' => 0,
- 'max' => 1000,
- 'label-message' => 'prefs-watchlist-edits',
- 'help' => wfMsgHtml( 'prefs-watchlist-edits-max' ),
- 'section' => 'watchlist/display',
- );
- $defaultPreferences['extendwatchlist'] =
- array(
- 'type' => 'toggle',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'tog-extendwatchlist',
- );
- $defaultPreferences['watchlisthideminor'] =
- array(
- 'type' => 'toggle',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'tog-watchlisthideminor',
- );
- $defaultPreferences['watchlisthidebots'] =
- array(
- 'type' => 'toggle',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'tog-watchlisthidebots',
- );
- $defaultPreferences['watchlisthideown'] =
- array(
- 'type' => 'toggle',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'tog-watchlisthideown',
- );
- $defaultPreferences['watchlisthideanons'] =
- array(
- 'type' => 'toggle',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'tog-watchlisthideanons',
- );
- $defaultPreferences['watchlisthideliu'] =
- array(
- 'type' => 'toggle',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'tog-watchlisthideliu',
- );
+ $defaultPreferences['watchlistdays'] = array(
+ 'type' => 'float',
+ 'min' => 0,
+ 'max' => 7,
+ 'section' => 'watchlist/displaywatchlist',
+ 'help' => wfMsgHtml( 'prefs-watchlist-days-max' ),
+ 'label-message' => 'prefs-watchlist-days',
+ );
+ $defaultPreferences['wllimit'] = array(
+ 'type' => 'int',
+ 'min' => 0,
+ 'max' => 1000,
+ 'label-message' => 'prefs-watchlist-edits',
+ 'help' => wfMsgHtml( 'prefs-watchlist-edits-max' ),
+ 'section' => 'watchlist/displaywatchlist',
+ );
+ $defaultPreferences['extendwatchlist'] = array(
+ 'type' => 'toggle',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'tog-extendwatchlist',
+ );
+ $defaultPreferences['watchlisthideminor'] = array(
+ 'type' => 'toggle',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'tog-watchlisthideminor',
+ );
+ $defaultPreferences['watchlisthidebots'] = array(
+ 'type' => 'toggle',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'tog-watchlisthidebots',
+ );
+ $defaultPreferences['watchlisthideown'] = array(
+ 'type' => 'toggle',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'tog-watchlisthideown',
+ );
+ $defaultPreferences['watchlisthideanons'] = array(
+ 'type' => 'toggle',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'tog-watchlisthideanons',
+ );
+ $defaultPreferences['watchlisthideliu'] = array(
+ 'type' => 'toggle',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'tog-watchlisthideliu',
+ );
+
+ if ( $wgUseRCPatrol ) {
+ $defaultPreferences['watchlisthidepatrolled'] = array(
+ 'type' => 'toggle',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'tog-watchlisthidepatrolled',
+ );
+ }
+
if ( $wgEnableAPI ) {
# Some random gibberish as a proposed default
$hash = sha1( mt_rand() . microtime( true ) );
- $defaultPreferences['watchlisttoken'] =
- array(
- 'type' => 'text',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'prefs-watchlist-token',
- 'help' => wfMsgHtml( 'prefs-help-watchlist-token', $hash )
- );
- }
- if ( $wgUseRCPatrol ) {
- $defaultPreferences['watchlisthidepatrolled'] =
- array(
- 'type' => 'toggle',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'tog-watchlisthidepatrolled',
- );
+ $defaultPreferences['watchlisttoken'] = array(
+ 'type' => 'text',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'prefs-watchlist-token',
+ 'help' => wfMsgHtml( 'prefs-help-watchlist-token', $hash )
+ );
}
$watchTypes = array(
@@ -890,11 +877,11 @@ class Preferences {
);
// Kinda hacky
- if( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
+ if ( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
$watchTypes['read'] = 'watchcreations';
}
- foreach( $watchTypes as $action => $pref ) {
+ foreach ( $watchTypes as $action => $pref ) {
if ( $user->isAllowed( $action ) ) {
$defaultPreferences[$pref] = array(
'type' => 'toggle',
@@ -909,101 +896,108 @@ class Preferences {
global $wgContLang;
## Search #####################################
- $defaultPreferences['searchlimit'] =
- array(
- 'type' => 'int',
- 'label-message' => 'resultsperpage',
- 'section' => 'searchoptions/display',
- 'min' => 0,
- );
- $defaultPreferences['contextlines'] =
- array(
- 'type' => 'int',
- 'label-message' => 'contextlines',
- 'section' => 'searchoptions/display',
- 'min' => 0,
- );
- $defaultPreferences['contextchars'] =
- array(
- 'type' => 'int',
- 'label-message' => 'contextchars',
- 'section' => 'searchoptions/display',
- 'min' => 0,
- );
+ $defaultPreferences['searchlimit'] = array(
+ 'type' => 'int',
+ 'label-message' => 'resultsperpage',
+ 'section' => 'searchoptions/displaysearchoptions',
+ 'min' => 0,
+ );
+ $defaultPreferences['contextlines'] = array(
+ 'type' => 'int',
+ 'label-message' => 'contextlines',
+ 'section' => 'searchoptions/displaysearchoptions',
+ 'min' => 0,
+ );
+ $defaultPreferences['contextchars'] = array(
+ 'type' => 'int',
+ 'label-message' => 'contextchars',
+ 'section' => 'searchoptions/displaysearchoptions',
+ 'min' => 0,
+ );
+
global $wgEnableMWSuggest;
- if( $wgEnableMWSuggest ) {
- $defaultPreferences['disablesuggest'] =
- array(
- 'type' => 'toggle',
- 'label-message' => 'mwsuggest-disable',
- 'section' => 'searchoptions/display',
- );
+ if ( $wgEnableMWSuggest ) {
+ $defaultPreferences['disablesuggest'] = array(
+ 'type' => 'toggle',
+ 'label-message' => 'mwsuggest-disable',
+ 'section' => 'searchoptions/displaysearchoptions',
+ );
+ }
+
+ global $wgVectorUseSimpleSearch;
+ if ( $wgVectorUseSimpleSearch ) {
+ $defaultPreferences['vector-simplesearch'] = array(
+ 'type' => 'toggle',
+ 'label-message' => 'vector-simplesearch-preference',
+ 'section' => 'searchoptions/displaysearchoptions'
+ );
}
- $defaultPreferences['searcheverything'] =
- array(
- 'type' => 'toggle',
- 'label-message' => 'searcheverything-enable',
- 'section' => 'searchoptions/advancedsearchoptions',
- );
+ $defaultPreferences['searcheverything'] = array(
+ 'type' => 'toggle',
+ 'label-message' => 'searcheverything-enable',
+ 'section' => 'searchoptions/advancedsearchoptions',
+ );
// Searchable namespaces back-compat with old format
$searchableNamespaces = SearchEngine::searchableNamespaces();
$nsOptions = array();
- foreach( $wgContLang->getNamespaces() as $ns => $name ) {
- if( $ns < 0 ) continue;
+
+ foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
+ if ( $ns < 0 ) {
+ continue;
+ }
+
$displayNs = str_replace( '_', ' ', $name );
- if( !$displayNs ) $displayNs = wfMsg( 'blanknamespace' );
+ if ( !$displayNs ) {
+ $displayNs = wfMsg( 'blanknamespace' );
+ }
$displayNs = htmlspecialchars( $displayNs );
$nsOptions[$displayNs] = $ns;
}
- $defaultPreferences['searchnamespaces'] =
- array(
- 'type' => 'multiselect',
- 'label-message' => 'defaultns',
- 'options' => $nsOptions,
- 'section' => 'searchoptions/advancedsearchoptions',
- 'prefix' => 'searchNs',
- );
+ $defaultPreferences['searchnamespaces'] = array(
+ 'type' => 'multiselect',
+ 'label-message' => 'defaultns',
+ 'options' => $nsOptions,
+ 'section' => 'searchoptions/advancedsearchoptions',
+ 'prefix' => 'searchNs',
+ );
}
static function miscPreferences( $user, &$defaultPreferences ) {
## Misc #####################################
- $defaultPreferences['diffonly'] =
- array(
- 'type' => 'toggle',
- 'section' => 'misc/diffs',
- 'label-message' => 'tog-diffonly',
- );
- $defaultPreferences['norollbackdiff'] =
- array(
- 'type' => 'toggle',
- 'section' => 'misc/diffs',
- 'label-message' => 'tog-norollbackdiff',
- );
+ $defaultPreferences['diffonly'] = array(
+ 'type' => 'toggle',
+ 'section' => 'misc/diffs',
+ 'label-message' => 'tog-diffonly',
+ );
+ $defaultPreferences['norollbackdiff'] = array(
+ 'type' => 'toggle',
+ 'section' => 'misc/diffs',
+ 'label-message' => 'tog-norollbackdiff',
+ );
// Stuff from Language::getExtraUserToggles()
global $wgContLang;
$toggles = $wgContLang->getExtraUserToggles();
- foreach( $toggles as $toggle ) {
- $defaultPreferences[$toggle] =
- array(
- 'type' => 'toggle',
- 'section' => 'personal/i18n',
- 'label-message' => "tog-$toggle",
- );
+ foreach ( $toggles as $toggle ) {
+ $defaultPreferences[$toggle] = array(
+ 'type' => 'toggle',
+ 'section' => 'personal/i18n',
+ 'label-message' => "tog-$toggle",
+ );
}
}
/**
- * @param object $user The user object
- * @return array Text/links to display as key; $skinkey as value
+ * @param $user The User object
+ * @return Array: text/links to display as key; $skinkey as value
*/
static function generateSkinOptions( $user ) {
global $wgDefaultSkin, $wgLang, $wgAllowUserCss, $wgAllowUserJs;
@@ -1011,9 +1005,11 @@ class Preferences {
$mptitle = Title::newMainPage();
$previewtext = wfMsgHtml( 'skin-preview' );
+
# Only show members of Skin::getSkinNames() rather than
# $skinNames (skins is all skin names from Language.php)
$validSkinNames = Skin::getUsableSkins();
+
# Sort by UI skin name. First though need to update validSkinNames as sometimes
# the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI).
foreach ( $validSkinNames as $skinkey => &$skinname ) {
@@ -1026,11 +1022,11 @@ class Preferences {
asort( $validSkinNames );
$sk = $user->getSkin();
- foreach( $validSkinNames as $skinkey => $sn ) {
+ foreach ( $validSkinNames as $skinkey => $sn ) {
$linkTools = array();
# Mark the default skin
- if( $skinkey == $wgDefaultSkin ) {
+ if ( $skinkey == $wgDefaultSkin ) {
$linkTools[] = wfMsgHtml( 'default' );
}
@@ -1039,11 +1035,12 @@ class Preferences {
$linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
# Create links to user CSS/JS pages
- if( $wgAllowUserCss ) {
+ if ( $wgAllowUserCss ) {
$cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
$linkTools[] = $sk->link( $cssPage, wfMsgHtml( 'prefs-custom-css' ) );
}
- if( $wgAllowUserJs ) {
+
+ if ( $wgAllowUserJs ) {
$jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
$linkTools[] = $sk->link( $jsPage, wfMsgHtml( 'prefs-custom-js' ) );
}
@@ -1061,16 +1058,21 @@ class Preferences {
$ret = array();
- if( $dateopts ) {
+ if ( $dateopts ) {
if ( !in_array( 'default', $dateopts ) ) {
$dateopts[] = 'default'; // Make sure default is always valid
// Bug 19237
}
- $idCnt = 0;
+ // KLUGE: site default might not be valid for user language
+ global $wgDefaultUserOptions;
+ if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
+ $wgDefaultUserOptions['date'] = 'default';
+ }
+
$epoch = wfTimestampNow();
- foreach( $dateopts as $key ) {
- if( $key == 'default' ) {
+ foreach ( $dateopts as $key ) {
+ if ( $key == 'default' ) {
$formatted = wfMsgHtml( 'datedefault' );
} else {
$formatted = htmlspecialchars( $wgLang->timeanddate( $epoch, false, $key ) );
@@ -1109,14 +1111,14 @@ class Preferences {
static function validateSignature( $signature, $alldata ) {
global $wgParser, $wgMaxSigChars, $wgLang;
- if( mb_strlen( $signature ) > $wgMaxSigChars ) {
- return
- Xml::element( 'span', array( 'class' => 'error' ),
- wfMsgExt( 'badsiglength', 'parsemag',
- $wgLang->formatNum( $wgMaxSigChars )
- )
- );
- } elseif( !empty( $alldata['fancysig'] ) &&
+ if ( mb_strlen( $signature ) > $wgMaxSigChars ) {
+ return Xml::element( 'span', array( 'class' => 'error' ),
+ wfMsgExt( 'badsiglength', 'parsemag',
+ $wgLang->formatNum( $wgMaxSigChars )
+ )
+ );
+ } elseif ( isset( $alldata['fancysig'] ) &&
+ $alldata['fancysig'] &&
false === $wgParser->validateSig( $signature ) ) {
return Xml::element( 'span', array( 'class' => 'error' ), wfMsg( 'badsig' ) );
} else {
@@ -1126,7 +1128,7 @@ class Preferences {
static function cleanSignature( $signature, $alldata ) {
global $wgParser;
- if( $alldata['fancysig'] ) {
+ if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
$signature = $wgParser->cleanSig( $signature );
} else {
// When no fancy sig used, make sure ~{3,5} get removed.
@@ -1142,17 +1144,19 @@ class Preferences {
}
global $wgEmailConfirmToEdit;
- if( $wgEmailConfirmToEdit && !$email ) {
+ if ( $wgEmailConfirmToEdit && !$email ) {
return wfMsgExt( 'noemailtitle', 'parseinline' );
}
return true;
}
- static function getFormObject( $user ) {
+ static function getFormObject( $user, $formClass = 'PreferencesForm' ) {
$formDescriptor = Preferences::getPreferences( $user );
- $htmlForm = new PreferencesForm( $formDescriptor, 'prefs' );
+ $htmlForm = new $formClass( $formDescriptor, 'prefs' );
$htmlForm->setSubmitText( wfMsg( 'saveprefs' ) );
+ # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
+ $htmlForm->setSubmitTooltip( 'preferences-save' );
$htmlForm->setTitle( SpecialPage::getTitleFor( 'Preferences' ) );
$htmlForm->setSubmitID( 'prefsubmit' );
$htmlForm->setSubmitCallback( array( 'Preferences', 'tryFormSubmit' ) );
@@ -1196,24 +1200,29 @@ class Preferences {
$z = explode( '/', $tz, 2 );
# timezone_identifiers_list() returns a number of
- # backwards-compatibility entries. This filters them out of the
+ # backwards-compatibility entries. This filters them out of the
# list presented to the user.
- if ( count( $z ) != 2 || !array_key_exists( $z[0], $tzRegions ) )
+ if ( count( $z ) != 2 || !array_key_exists( $z[0], $tzRegions ) ) {
continue;
+ }
# Localize region
$z[0] = $tzRegions[$z[0]];
$minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 );
-
+
$display = str_replace( '_', ' ', $z[0] . '/' . $z[1] );
$value = "ZoneInfo|$minDiff|$tz";
-
+
$opt[$z[0]][$display] = $value;
}
}
return $opt;
}
+
+ static function filterIntval( $value, $alldata ){
+ return intval( $value );
+ }
static function filterTimezoneInput( $tz, $alldata ) {
$data = explode( '|', $tz, 3 );
@@ -1223,12 +1232,11 @@ class Preferences {
return $tz;
default:
$data = explode( ':', $tz, 2 );
- $minDiff = 0;
- if( count( $data ) == 2 ) {
+ if ( count( $data ) == 2 ) {
$data[0] = intval( $data[0] );
$data[1] = intval( $data[1] );
$minDiff = abs( $data[0] ) * 60 + $data[1];
- if ( $data[0] < 0 ) $minDiff = -$minDiff;
+ if ( $data[0] < 0 ) $minDiff = - $minDiff;
} else {
$minDiff = intval( $data[0] ) * 60;
}
@@ -1236,18 +1244,18 @@ class Preferences {
# Max is +14:00 and min is -12:00, see:
# http://en.wikipedia.org/wiki/Timezone
$minDiff = min( $minDiff, 840 ); # 14:00
- $minDiff = max( $minDiff, -720 ); # -12:00
- return 'Offset|'.$minDiff;
+ $minDiff = max( $minDiff, - 720 ); # -12:00
+ return 'Offset|' . $minDiff;
}
}
-
+
static function tryFormSubmit( $formData, $entryPoint = 'internal' ) {
global $wgUser, $wgEmailAuthentication, $wgEnableEmail;
$result = true;
// Filter input
- foreach( array_keys( $formData ) as $name ) {
+ foreach ( array_keys( $formData ) as $name ) {
if ( isset( self::$saveFilters[$name] ) ) {
$formData[$name] =
call_user_func( self::$saveFilters[$name], $formData[$name], $formData );
@@ -1260,30 +1268,31 @@ class Preferences {
'emailaddress',
);
- if( $wgEnableEmail ) {
- $newadr = $formData['emailaddress'];
- $oldadr = $wgUser->getEmail();
- if( ( $newadr != '' ) && ( $newadr != $oldadr ) ) {
+ if ( $wgEnableEmail ) {
+ $newaddr = $formData['emailaddress'];
+ $oldaddr = $wgUser->getEmail();
+ if ( ( $newaddr != '' ) && ( $newaddr != $oldaddr ) ) {
# the user has supplied a new email address on the login page
# new behaviour: set this new emailaddr from login-page into user database record
- $wgUser->setEmail( $newadr );
+ $wgUser->setEmail( $newaddr );
# but flag as "dirty" = unauthenticated
$wgUser->invalidateEmail();
- if( $wgEmailAuthentication ) {
+ if ( $wgEmailAuthentication ) {
# Mail a temporary password to the dirty address.
# User can come back through the confirmation URL to re-enable email.
- $result = $wgUser->sendConfirmationMail();
- if( WikiError::isError( $result ) ) {
- return wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) );
- } elseif( $entryPoint == 'ui' ) {
+ $type = $oldaddr != '' ? 'changed' : 'set';
+ $result = $wgUser->sendConfirmationMail( $type );
+ if ( !$result->isGood() ) {
+ return htmlspecialchars( $result->getWikiText( 'mailerror' ) );
+ } elseif ( $entryPoint == 'ui' ) {
$result = 'eauth';
}
}
} else {
- $wgUser->setEmail( $newadr );
+ $wgUser->setEmail( $newaddr );
}
- if( $oldadr != $newadr ) {
- wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldadr, $newadr ) );
+ if ( $oldaddr != $newaddr ) {
+ wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldaddr, $newaddr ) );
}
}
@@ -1294,14 +1303,15 @@ class Preferences {
$wgUser->setRealName( $realName );
}
- foreach( $saveBlacklist as $b )
+ foreach ( $saveBlacklist as $b ) {
unset( $formData[$b] );
+ }
// Keeps old preferences from interfering due to back-compat
// code, etc.
$wgUser->resetOptions();
- foreach( $formData as $key => $value ) {
+ foreach ( $formData as $key => $value ) {
$wgUser->setOption( $key, $value );
}
@@ -1313,19 +1323,22 @@ class Preferences {
public static function tryUISubmit( $formData ) {
$res = self::tryFormSubmit( $formData, 'ui' );
- if( $res ) {
+ if ( $res ) {
$urlOptions = array( 'success' );
- if( $res === 'eauth' )
+
+ if ( $res === 'eauth' ) {
$urlOptions[] = 'eauth';
+ }
$queryString = implode( '&', $urlOptions );
$url = SpecialPage::getTitleFor( 'Preferences' )->getFullURL( $queryString );
+
global $wgOut;
$wgOut->redirect( $url );
}
- return true;
+ return Status::newGood();
}
public static function loadOldSearchNs( $user ) {
@@ -1333,8 +1346,8 @@ class Preferences {
// Back compat with old format
$arr = array();
- foreach( $searchableNamespaces as $ns => $name ) {
- if( $user->getOption( 'searchNs' . $ns ) ) {
+ foreach ( $searchableNamespaces as $ns => $name ) {
+ if ( $user->getOption( 'searchNs' . $ns ) ) {
$arr[] = $ns;
}
}
@@ -1345,7 +1358,6 @@ class Preferences {
/** Some tweaks to allow js prefs to work */
class PreferencesForm extends HTMLForm {
-
function wrapForm( $html ) {
$html = Xml::tags( 'div', array( 'id' => 'preferences' ), $html );
@@ -1370,13 +1382,13 @@ class PreferencesForm extends HTMLForm {
function filterDataForSubmit( $data ) {
// Support for separating MultiSelect preferences into multiple preferences
// Due to lack of array support.
- foreach( $this->mFlatFields as $fieldname => $field ) {
+ foreach ( $this->mFlatFields as $fieldname => $field ) {
$info = $field->mParams;
- if( $field instanceof HTMLMultiSelectField ) {
+ if ( $field instanceof HTMLMultiSelectField ) {
$options = HTMLFormField::flattenOptions( $info['options'] );
$prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
- foreach( $options as $opt ) {
+ foreach ( $options as $opt ) {
$data["$prefix$opt"] = in_array( $opt, $data[$fieldname] );
}
diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php
index 930b29d4..236e4370 100644
--- a/includes/PrefixSearch.php
+++ b/includes/PrefixSearch.php
@@ -1,5 +1,4 @@
<?php
-
/**
* PrefixSearch - Handles searching prefixes of titles and finding any page
* names that match. Used largely by the OpenSearch implementation.
@@ -23,6 +22,7 @@ class PrefixSearch {
}
$namespaces = self::validateNamespaces( $namespaces );
+ // Find a Title which is not an interwiki and is in NS_MAIN
$title = Title::newFromText( $search );
if( $title && $title->getInterwiki() == '' ) {
$ns = array($title->getNamespace());
@@ -77,6 +77,10 @@ class PrefixSearch {
*/
protected static function specialSearch( $search, $limit ) {
global $wgContLang;
+
+ # normalize searchKey, so aliases with spaces can be found - bug 25675
+ $search = str_replace( ' ', '_', $search );
+
$searchKey = $wgContLang->caseFold( $search );
// Unlike SpecialPage itself, we want the canonical forms of both
@@ -87,9 +91,11 @@ class PrefixSearch {
foreach( array_keys( SpecialPage::$mList ) as $page ) {
$keys[$wgContLang->caseFold( $page )] = $page;
}
+
foreach( $wgContLang->getSpecialPageAliases() as $page => $aliases ) {
- if( !array_key_exists( $page, SpecialPage::$mList ) ) # bug 20885
+ if( !array_key_exists( $page, SpecialPage::$mList ) ) {# bug 20885
continue;
+ }
foreach( $aliases as $alias ) {
$keys[$wgContLang->caseFold( $alias )] = $alias;
@@ -100,12 +106,20 @@ class PrefixSearch {
$srchres = array();
foreach( $keys as $pageKey => $page ) {
if( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) {
- $srchres[] = Title::makeTitle( NS_SPECIAL, $page )->getPrefixedText();
+ wfSuppressWarnings();
+ // bug 27671: Don't use SpecialPage::getTitleFor() here because it
+ // localizes its input leading to searches for e.g. Special:All
+ // returning Spezial:MediaWiki-Systemnachrichten and returning
+ // Spezial:Alle_Seiten twice when $wgLanguageCode == 'de'
+ $srchres[] = Title::makeTitleSafe( NS_SPECIAL, $page )->getPrefixedText();
+ wfRestoreWarnings();
}
+
if( count( $srchres ) >= $limit ) {
break;
}
}
+
return $srchres;
}
@@ -156,10 +170,12 @@ class PrefixSearch {
* Validate an array of numerical namespace indexes
*
* @param $namespaces Array
- * @return Array
+ * @return Array (default: contains only NS_MAIN)
*/
protected static function validateNamespaces($namespaces){
global $wgContLang;
+
+ // We will look at each given namespace against wgContLang namespaces
$validNamespaces = $wgContLang->getNamespaces();
if( is_array($namespaces) && count($namespaces)>0 ){
$valid = array();
@@ -167,8 +183,9 @@ class PrefixSearch {
if( is_numeric($ns) && array_key_exists($ns, $validNamespaces) )
$valid[] = $ns;
}
- if( count($valid) > 0 )
+ if( count($valid) > 0 ) {
return $valid;
+ }
}
return array( NS_MAIN );
diff --git a/includes/Profiler.php b/includes/Profiler.php
index 817b71ab..6deb742e 100644
--- a/includes/Profiler.php
+++ b/includes/Profiler.php
@@ -12,7 +12,7 @@ $wgProfiling = true;
/**
* Begin profiling of a function
- * @param $functionname name of the function we will profile
+ * @param $functionname String: name of the function we will profile
*/
function wfProfileIn( $functionname ) {
global $wgProfiler;
@@ -21,7 +21,7 @@ function wfProfileIn( $functionname ) {
/**
* Stop profiling of a function
- * @param $functionname name of the function we have profiled
+ * @param $functionname String: name of the function we have profiled
*/
function wfProfileOut( $functionname = 'missing' ) {
global $wgProfiler;
@@ -61,6 +61,7 @@ if (!function_exists('memory_get_usage')) {
class Profiler {
var $mStack = array (), $mWorkStack = array (), $mCollated = array ();
var $mCalls = array (), $mTotals = array ();
+ var $mTemplated = false;
function __construct() {
// Push an entry for the pre-profile setup time onto the stack
@@ -75,7 +76,8 @@ class Profiler {
/**
* Called by wfProfieIn()
- * @param $functionname string
+ *
+ * @param $functionname String
*/
function profileIn( $functionname ) {
global $wgDebugFunctionEntry, $wgProfiling;
@@ -89,7 +91,8 @@ class Profiler {
/**
* Called by wfProfieOut()
- * @param $functionname string
+ *
+ * @param $functionname String
*/
function profileOut($functionname) {
global $wgDebugFunctionEntry, $wgProfiling;
@@ -140,7 +143,16 @@ class Profiler {
}
/**
- * called by wfGetProfilingOutput()
+ * Mark this call as templated or not
+ *
+ * @param $t Boolean
+ */
+ function setTemplated( $t ) {
+ $this->mTemplated = $t;
+ }
+
+ /**
+ * Called by wfGetProfilingOutput()
*/
function getOutput() {
global $wgDebugFunctionEntry, $wgProfileCallTree;
@@ -164,7 +176,7 @@ class Profiler {
}
/**
- * returns a tree of function call instead of a list of functions
+ * Returns a tree of function call instead of a list of functions
*/
function getCallTree() {
return implode( '', array_map( array( &$this, 'getCallTreeLine' ), $this->remapCallTree( $this->mStack ) ) );
@@ -275,9 +287,9 @@ class Profiler {
$overheadInternal[] = $elapsed;
}
}
- $overheadTotal = array_sum( $overheadTotal ) / count( $overheadInternal );
- $overheadMemory = array_sum( $overheadMemory ) / count( $overheadInternal );
- $overheadInternal = array_sum( $overheadInternal ) / count( $overheadInternal );
+ $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 ){
@@ -356,9 +368,10 @@ class Profiler {
/**
* Log a function into the database.
*
- * @param $name string: function name
- * @param $timeSum float
- * @param $eventCount int: number of times that function was called
+ * @param $name String: function name
+ * @param $timeSum Float
+ * @param $eventCount Integer: number of times that function was called
+ * @param $memorySum Integer: memory used by the function
*/
static function logToDB( $name, $timeSum, $eventCount, $memorySum ){
# Do not log anything if database is readonly (bug 5375)
@@ -420,7 +433,8 @@ class Profiler {
/**
* Get function caller
- * @param $level int
+ *
+ * @param $level Integer
*/
static function getCaller( $level ) {
$backtrace = wfDebugBacktrace();
@@ -438,7 +452,8 @@ class Profiler {
/**
* Add an entry in the debug log file
- * @param $s string to output
+ *
+ * @param $s String to output
*/
function debug( $s ) {
if( function_exists( 'wfDebug' ) ) {
diff --git a/includes/ProfilerSimple.php b/includes/ProfilerSimple.php
index 5989061d..8aab1ecc 100644
--- a/includes/ProfilerSimple.php
+++ b/includes/ProfilerSimple.php
@@ -109,8 +109,9 @@ class ProfilerSimple extends Profiler {
function getCpuTime($ru=null) {
if ( function_exists( 'getrusage' ) ) {
- if ( $ru == null )
+ if ( $ru == null ) {
$ru = getrusage();
+ }
return ($ru['ru_utime.tv_sec'] + $ru['ru_stime.tv_sec'] + ($ru['ru_utime.tv_usec'] +
$ru['ru_stime.tv_usec']) * 1e-6);
} else {
@@ -120,8 +121,9 @@ class ProfilerSimple extends Profiler {
/* If argument is passed, it assumes that it is dual-format time string, returns proper float time value */
function getTime($time=null) {
- if ($time==null)
+ if ($time==null) {
return microtime(true);
+ }
list($a,$b)=explode(" ",$time);
return (float)($a+$b);
}
diff --git a/includes/ProfilerSimpleText.php b/includes/ProfilerSimpleText.php
index d3df3908..db4b6053 100644
--- a/includes/ProfilerSimpleText.php
+++ b/includes/ProfilerSimpleText.php
@@ -9,7 +9,7 @@ require_once( dirname( __FILE__ ) . '/ProfilerSimple.php' );
/**
* The least sophisticated profiler output class possible, view your source! :)
*
- * Put it to StartProfiler.php like this:
+ * Put the following 3 lines in StartProfiler.php:
*
* require_once( dirname( __FILE__ ) . '/includes/ProfilerSimpleText.php' );
* $wgProfiler = new ProfilerSimpleText;
@@ -19,21 +19,21 @@ require_once( dirname( __FILE__ ) . '/ProfilerSimple.php' );
*/
class ProfilerSimpleText extends ProfilerSimple {
public $visible=false; /* Show as <PRE> or <!-- ? */
+ static private $out;
function getFunctionReport() {
- global $wgRequest;
- if ( $wgRequest->getVal( 'action' ) == 'raw' ) # bug 20388
- return;
-
- if ($this->visible) print "<pre>";
- else print "<!--\n";
- uasort($this->mCollated,array('self','sort'));
- array_walk($this->mCollated,array('self','format'));
- if ($this->visible) print "</pre>\n";
- else print "-->\n";
+ if($this->mTemplated) {
+ uasort($this->mCollated,array('self','sort'));
+ array_walk($this->mCollated,array('self','format'));
+ if ($this->visible) {
+ print '<pre>'.self::$out.'</pre>';
+ } else {
+ print "<!--\n".self::$out."\n-->\n";
+ }
+ }
}
/* dense is good */
static function sort($a,$b) { return $a['real']<$b['real']; /* sort descending by time elapsed */ }
- static function format($item,$key) { printf("%3.6f %6d - %s\n",$item['real'],$item['count'], $key); }
+ static function format($item,$key) { self::$out .= sprintf("%3.6f %6d - %s\n",$item['real'],$item['count'], $key); }
}
diff --git a/includes/ProfilerSimpleTrace.php b/includes/ProfilerSimpleTrace.php
index 63119228..8f6a2a1c 100644
--- a/includes/ProfilerSimpleTrace.php
+++ b/includes/ProfilerSimpleTrace.php
@@ -21,50 +21,48 @@ class ProfilerSimpleTrace extends ProfilerSimple {
function __construct() {
global $wgRequestTime, $wgRUstart;
- if (!empty($wgRequestTime) && !empty($wgRUstart)) {
- $this->mWorkStack[] = array( '-total', 0, $wgRequestTime,$this->getCpuTime($wgRUstart));
- $elapsedcpu = $this->getCpuTime() - $this->getCpuTime($wgRUstart);
- $elapsedreal = microtime(true) - $wgRequestTime;
+ if ( !empty( $wgRequestTime ) && !empty( $wgRUstart ) ) {
+ $this->mWorkStack[] = array( '-total', 0, $wgRequestTime, $this->getCpuTime( $wgRUstart ) );
}
$this->trace .= "Beginning trace: \n";
}
function profileIn($functionname) {
- global $wgDebugFunctionEntry;
$this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), microtime(true), $this->getCpuTime());
- $this->trace .= " " . sprintf("%6.1f",$this->memoryDiff()) . str_repeat( " ", count($this->mWorkStack)) . " > " . $functionname . "\n";
+ $this->trace .= " " . sprintf("%6.1f",$this->memoryDiff()) .
+ str_repeat( " ", count($this->mWorkStack)) . " > " . $functionname . "\n";
}
function profileOut($functionname) {
global $wgDebugFunctionEntry;
- if ($wgDebugFunctionEntry) {
+ if ( $wgDebugFunctionEntry ) {
$this->debug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n");
}
- list($ofname, /* $ocount */ ,$ortime,$octime) = array_pop($this->mWorkStack);
+ list( $ofname, /* $ocount */ , $ortime, $octime ) = array_pop( $this->mWorkStack );
- if (!$ofname) {
+ if ( !$ofname ) {
$this->trace .= "Profiling error: $functionname\n";
} else {
- if ($functionname == 'close') {
+ if ( $functionname == 'close' ) {
$message = "Profile section ended by close(): {$ofname}";
$functionname = $ofname;
$this->trace .= $message . "\n";
}
- elseif ($ofname != $functionname) {
- $self->trace .= "Profiling error: in({$ofname}), out($functionname)";
+ elseif ( $ofname != $functionname ) {
+ $this->trace .= "Profiling error: in({$ofname}), out($functionname)";
}
- $elapsedcpu = $this->getCpuTime() - $octime;
- $elapsedreal = microtime(true) - $ortime;
- $this->trace .= sprintf("%03.6f %6.1f",$elapsedreal,$this->memoryDiff()) . str_repeat(" ",count($this->mWorkStack)+1) . " < " . $functionname . "\n";
+ $elapsedreal = microtime( true ) - $ortime;
+ $this->trace .= sprintf( "%03.6f %6.1f", $elapsedreal, $this->memoryDiff() ) .
+ str_repeat(" ", count( $this->mWorkStack ) + 1 ) . " < " . $functionname . "\n";
}
}
function memoryDiff() {
$diff = memory_get_usage() - $this->memory;
$this->memory = memory_get_usage();
- return $diff/1024;
+ return $diff / 1024;
}
function getOutput() {
diff --git a/includes/ProfilerStub.php b/includes/ProfilerStub.php
index 100cb8df..e624e6f0 100644
--- a/includes/ProfilerStub.php
+++ b/includes/ProfilerStub.php
@@ -7,6 +7,7 @@
/** backward compatibility */
$wgProfiling = false;
+$wgProfiler = null;
/** is setproctitle function available ? */
$haveProctitle = function_exists( 'setproctitle' );
@@ -29,12 +30,15 @@ function wfProfileIn( $fn = '' ) {
*/
function wfProfileOut( $fn = '' ) {
global $hackwhere, $wgDBname, $haveProctitle;
- if( !$haveProctitle )
+ if( !$haveProctitle ) {
return;
- if( count( $hackwhere ) )
+ }
+ if( count( $hackwhere ) ) {
array_pop( $hackwhere );
- if( count( $hackwhere ) )
+ }
+ if( count( $hackwhere ) ) {
setproctitle( $hackwhere[count( $hackwhere )-1] . " [$wgDBname]" );
+ }
}
/**
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index 28df01ac..2d95d801 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -1,6 +1,8 @@
<?php
/**
- * Copyright (C) 2005 Brion Vibber <brion@pobox.com>
+ * Page protection
+ *
+ * Copyright © 2005 Brion Vibber <brion@pobox.com>
* http://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
@@ -17,6 +19,8 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
/**
@@ -71,7 +75,9 @@ class ProtectionForm {
$this->loadData();
}
- // Loads the current state of protection into the object.
+ /**
+ * Loads the current state of protection into the object.
+ */
function loadData() {
global $wgRequest, $wgUser;
global $wgRestrictionLevels;
@@ -140,7 +146,8 @@ class ProtectionForm {
/**
* Get the expiry time for a given action, by combining the relevant inputs.
- * Returns a 14-char timestamp or "infinity", or false if the input was invalid
+ *
+ * @return 14-char timestamp or "infinity", or false if the input was invalid
*/
function getExpiry( $action ) {
if ( $this->mExpirySelection[$action] == 'existing' ) {
@@ -166,6 +173,9 @@ class ProtectionForm {
return $time;
}
+ /**
+ * Main entry point for action=protect and action=unprotect
+ */
function execute() {
global $wgRequest, $wgOut;
if( $wgRequest->wasPosted() ) {
@@ -178,6 +188,11 @@ class ProtectionForm {
}
}
+ /**
+ * Show the input form with optional error message
+ *
+ * @param $err String: error message or null if there's no error
+ */
function show( $err = null ) {
global $wgOut, $wgUser;
@@ -228,8 +243,14 @@ class ProtectionForm {
$this->showLogExtract( $wgOut );
}
+ /**
+ * Save submitted protection form
+ *
+ * @return Boolean: success
+ */
function save() {
global $wgRequest, $wgUser;
+
# Permission check!
if ( $this->disabled ) {
$this->show();
@@ -306,21 +327,21 @@ class ProtectionForm {
/**
* Build the input form
*
- * @return $out string HTML form
+ * @return String: HTML form
*/
function buildForm() {
- global $wgUser, $wgLang;
+ global $wgUser, $wgLang, $wgOut;
$mProtectreasonother = Xml::label( wfMsg( 'protectcomment' ), 'wpProtectReasonSelection' );
$mProtectreason = Xml::label( wfMsg( 'protect-otherreason' ), 'mwProtect-reason' );
$out = '';
if( !$this->disabled ) {
- $out .= $this->buildScript();
+ $wgOut->addModules( 'mediawiki.legacy.protect' );
$out .= Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->mTitle->getLocalUrl( 'action=protect' ),
'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) );
- $out .= Xml::hidden( 'wpEditToken',$wgUser->editToken() );
+ $out .= Html::hidden( 'wpEditToken',$wgUser->editToken() );
}
$out .= Xml::openElement( 'fieldset' ) .
@@ -487,13 +508,20 @@ class ProtectionForm {
}
if ( !$this->disabled ) {
- $out .= Xml::closeElement( 'form' ) .
- $this->buildCleanupScript();
+ $out .= Xml::closeElement( 'form' );
+ $wgOut->addScript( $this->buildCleanupScript() );
}
return $out;
}
+ /**
+ * Build protection level selector
+ *
+ * @param $action String: action to protect
+ * @param $selected String: current protection level
+ * @return String: HTML fragment
+ */
function buildSelector( $action, $selected ) {
global $wgRestrictionLevels, $wgUser;
@@ -530,8 +558,8 @@ class ProtectionForm {
/**
* Prepare the label for a protection selector option
*
- * @param string $permission Permission required
- * @return string
+ * @param $permission String: permission required
+ * @return String
*/
private function getOptionLabel( $permission ) {
if( $permission == '' ) {
@@ -544,14 +572,7 @@ class ProtectionForm {
return $msg;
}
}
-
- function buildScript() {
- global $wgStylePath, $wgStyleVersion;
- return Xml::tags( 'script', array(
- 'type' => 'text/javascript',
- 'src' => $wgStylePath . "/common/protect.js?$wgStyleVersion.1" ), '' );
- }
-
+
function buildCleanupScript() {
global $wgRestrictionLevels, $wgGroupPermissions;
$script = 'var wgCascadeableLevels=';
@@ -571,11 +592,13 @@ class ProtectionForm {
$encOptions = Xml::encodeJsVar( $options );
$script .= "ProtectionForm.init($encOptions)";
- return Xml::tags( 'script', array( 'type' => 'text/javascript' ), $script );
+ return Html::inlineScript( "if ( window.mediaWiki ) { $script }" );
}
/**
- * @param OutputPage $out
+ * Show protection long extracts for this page
+ *
+ * @param $out OutputPage
* @access private
*/
function showLogExtract( &$out ) {
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
index 5719e3e8..13c19965 100644
--- a/includes/ProxyTools.php
+++ b/includes/ProxyTools.php
@@ -1,6 +1,7 @@
<?php
/**
* Functions for dealing with proxies
+ *
* @file
*/
@@ -67,15 +68,15 @@ function wfGetAgent() {
* @return string
*/
function wfGetIP() {
- global $wgIP, $wgUsePrivateIPs, $wgCommandLineMode;
+ global $wgUsePrivateIPs, $wgCommandLineMode;
+ static $ip = false;
# Return cached result
- if ( !empty( $wgIP ) ) {
- return $wgIP;
+ if ( !empty( $ip ) ) {
+ return $ip;
}
$ipchain = array();
- $ip = false;
/* collect the originating ips */
# Client connecting to this webserver
@@ -111,12 +112,14 @@ function wfGetIP() {
}
}
+ # Allow extensions to improve our guess
+ wfRunHooks( 'GetIP', array( &$ip ) );
+
if( !$ip ) {
throw new MWException( "Unable to determine IP" );
}
wfDebug( "IP: $ip\n" );
- $wgIP = $ip;
return $ip;
}
@@ -183,9 +186,12 @@ function wfProxyCheck() {
/**
* Convert a network specification in CIDR notation to an integer network and a number of bits
+ *
+ * @deprecated Call IP::parseCIDR() directly, will be removed in 1.19
* @return array(string, int)
*/
function wfParseCIDR( $range ) {
+ wfDeprecated( __FUNCTION__ );
return IP::parseCIDR( $range );
}
diff --git a/includes/QueryPage.php b/includes/QueryPage.php
index 827264be..d9fb3bb2 100644
--- a/includes/QueryPage.php
+++ b/includes/QueryPage.php
@@ -228,8 +228,6 @@ class QueryPage {
return false;
}
- $querycache = $dbr->tableName( 'querycache' );
-
if ( $ignoreErrors ) {
$ignoreW = $dbw->ignoreErrors( true );
$ignoreR = $dbr->ignoreErrors( true );
@@ -264,13 +262,9 @@ class QueryPage {
if ( count( $vals ) ) {
if ( !$dbw->insert( 'querycache', $vals, __METHOD__ ) ) {
// Set result to false to indicate error
- $dbr->freeResult( $res );
$res = false;
}
}
- if ( $res ) {
- $dbr->freeResult( $res );
- }
if ( $ignoreErrors ) {
$dbw->ignoreErrors( $ignoreW );
$dbr->ignoreErrors( $ignoreR );
@@ -349,7 +343,7 @@ class QueryPage {
$this->preprocessResults( $dbr, $res );
- $wgOut->addHTML( XML::openElement( 'div', array('class' => 'mw-spcontent') ) );
+ $wgOut->addHTML( Xml::openElement( 'div', array('class' => 'mw-spcontent') ) );
# Top header and navigation
if( $shownavigation ) {
@@ -364,7 +358,7 @@ class QueryPage {
# No results to show, so don't bother with "showing X of Y" etc.
# -- just let the user know and give up now
$wgOut->addHTML( '<p>' . wfMsgHtml( 'specialpage-empty' ) . '</p>' );
- $wgOut->addHTML( XML::closeElement( 'div' ) );
+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
return;
}
}
@@ -384,7 +378,7 @@ class QueryPage {
$wgOut->addHTML( '<p>' . $paging . '</p>' );
}
- $wgOut->addHTML( XML::closeElement( 'div' ) );
+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
return $num;
}
@@ -488,11 +482,12 @@ class QueryPage {
$sql = $this->getSQL() . $this->getOrder();
$sql = $dbr->limitResult( $sql, $limit, 0 );
$res = $dbr->query( $sql, 'QueryPage::doFeed' );
- while( $obj = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $obj ) {
$item = $this->feedResult( $obj );
- if( $item ) $feed->outItem( $item );
+ if( $item ) {
+ $feed->outItem( $item );
+ }
}
- $dbr->freeResult( $res );
$feed->outFooter();
return true;
@@ -539,10 +534,10 @@ class QueryPage {
}
function feedTitle() {
- global $wgContLanguageCode, $wgSitename;
+ global $wgLanguageCode, $wgSitename;
$page = SpecialPage::getPage( $this->getName() );
$desc = $page->getDescription();
- return "$wgSitename - $desc [$wgContLanguageCode]";
+ return "$wgSitename - $desc [$wgLanguageCode]";
}
function feedDesc() {
@@ -574,8 +569,9 @@ abstract class WantedQueryPage extends QueryPage {
*/
function preprocessResults( $db, $res ) {
$batch = new LinkBatch;
- while ( $row = $db->fetchObject( $res ) )
+ foreach ( $res as $row ) {
$batch->add( $row->namespace, $row->title );
+ }
$batch->execute();
// Back to start for display
@@ -585,6 +581,17 @@ abstract class WantedQueryPage extends QueryPage {
}
/**
+ * Should formatResult() always check page existence, even if
+ * the results are fresh? This is a (hopefully temporary)
+ * kluge for Special:WantedFiles, which may contain false
+ * positives for files that exist e.g. in a shared repo (bug
+ * 6220).
+ */
+ function forceExistenceCheck() {
+ return false;
+ }
+
+ /**
* Format an individual result
*
* @param $skin Skin to use for UI elements
@@ -594,9 +601,9 @@ abstract class WantedQueryPage extends QueryPage {
public function formatResult( $skin, $result ) {
$title = Title::makeTitleSafe( $result->namespace, $result->title );
if( $title instanceof Title ) {
- if( $this->isCached() ) {
- $pageLink = $title->exists()
- ? '<s>' . $skin->link( $title ) . '</s>'
+ if( $this->isCached() || $this->forceExistenceCheck() ) {
+ $pageLink = $title->isKnown()
+ ? '<del>' . $skin->link( $title ) . '</del>'
: $skin->link(
$title,
null,
diff --git a/includes/RawPage.php b/includes/RawPage.php
index 2b610318..ff935c2a 100644
--- a/includes/RawPage.php
+++ b/includes/RawPage.php
@@ -1,7 +1,10 @@
<?php
/**
- * Copyright (C) 2004 Gabriel Wicke <wicke@wikidev.net>
+ * Raw page text accessor
+ *
+ * Copyright © 2004 Gabriel Wicke <wicke@wikidev.net>
* http://wikidev.net/
+ *
* Based on HistoryPage and SpecialExport
*
* License: GPL (http://www.gnu.org/copyleft/gpl.html)
@@ -20,15 +23,15 @@ class RawPage {
var $mSmaxage, $mMaxage;
var $mContentType, $mExpandTemplates;
- function __construct( &$article, $request = false ) {
+ function __construct( Article $article, $request = false ) {
global $wgRequest, $wgInputEncoding, $wgSquidMaxage, $wgJsMimeType, $wgGroupPermissions;
- $allowedCTypes = array('text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit');
- $this->mArticle =& $article;
- $this->mTitle =& $article->mTitle;
+ $allowedCTypes = array( 'text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit' );
+ $this->mArticle = $article;
+ $this->mTitle = $article->mTitle;
if( $request === false ) {
- $this->mRequest =& $wgRequest;
+ $this->mRequest = $wgRequest;
} else {
$this->mRequest = $request;
}
@@ -54,7 +57,7 @@ class RawPage {
break;
case 'prev':
# output previous revision, or nothing if there isn't one
- if( ! $oldid ) {
+ if( !$oldid ) {
# get the current revision so we can get the penultimate one
$this->mArticle->getTouched();
$oldid = $this->mArticle->mLatest;
@@ -73,8 +76,12 @@ class RawPage {
if( $gen == 'css' ) {
$this->mGen = $gen;
- if( is_null( $smaxage ) ) $smaxage = $wgSquidMaxage;
- if($ctype == '') $ctype = 'text/css';
+ if( is_null( $smaxage ) ) {
+ $smaxage = $wgSquidMaxage;
+ }
+ if( $ctype == '' ) {
+ $ctype = 'text/css';
+ }
} elseif( $gen == 'js' ) {
$this->mGen = $gen;
if( is_null( $smaxage ) ) $smaxage = $wgSquidMaxage;
@@ -85,9 +92,9 @@ class RawPage {
$this->mCharset = $wgInputEncoding;
# Force caching for CSS and JS raw content, default: 5 minutes
- if( is_null($smaxage) and ($ctype=='text/css' or $ctype==$wgJsMimeType) ) {
+ if( is_null( $smaxage ) && ( $ctype == 'text/css' || $ctype == $wgJsMimeType ) ) {
global $wgForcedRawSMaxage;
- $this->mSmaxage = intval($wgForcedRawSMaxage);
+ $this->mSmaxage = intval( $wgForcedRawSMaxage );
} else {
$this->mSmaxage = intval( $smaxage );
}
@@ -95,13 +102,13 @@ class RawPage {
# Output may contain user-specific data;
# vary generated content for open sessions and private wikis
- if( $this->mGen or !$wgGroupPermissions['*']['read'] ) {
+ if( $this->mGen || !$wgGroupPermissions['*']['read'] ) {
$this->mPrivateCache = $this->mSmaxage == 0 || session_id() != '';
} else {
$this->mPrivateCache = false;
}
- if( $ctype == '' or ! in_array( $ctype, $allowedCTypes ) ) {
+ if( $ctype == '' || !in_array( $ctype, $allowedCTypes ) ) {
$this->mContentType = 'text/x-wiki';
} else {
$this->mContentType = $ctype;
@@ -109,44 +116,30 @@ class RawPage {
}
function view() {
- global $wgOut, $wgScript, $wgRequest;
-
- if( $wgRequest->isPathInfoBad() ) {
- # Internet Explorer will ignore the Content-Type header if it
- # thinks it sees a file extension it recognizes. Make sure that
- # all raw requests are done through the script node, which will
- # have eg '.php' and should remain safe.
- #
- # We used to redirect to a canonical-form URL as a general
- # backwards-compatibility / good-citizen nice thing. However
- # a lot of servers are set up in buggy ways, resulting in
- # redirect loops which hang the browser until the CSS load
- # times out.
- #
- # Just return a 403 Forbidden and get it over with.
- wfHttpError( 403, 'Forbidden',
- 'Invalid file extension found in PATH_INFO or QUERY_STRING. ' .
- 'Raw pages must be accessed through the primary script entry point.' );
+ global $wgOut, $wgRequest;
+
+ if( !$wgRequest->checkUrlExtension() ) {
+ $wgOut->disable();
return;
}
- header( "Content-type: ".$this->mContentType.'; charset='.$this->mCharset );
+ header( 'Content-type: ' . $this->mContentType . '; charset=' . $this->mCharset );
# allow the client to cache this for 24 hours
$mode = $this->mPrivateCache ? 'private' : 'public';
- header( 'Cache-Control: '.$mode.', s-maxage='.$this->mSmaxage.', max-age='.$this->mMaxage );
-
+ header( 'Cache-Control: ' . $mode . ', s-maxage=' . $this->mSmaxage . ', max-age=' . $this->mMaxage );
+
global $wgUseFileCache;
- if( $wgUseFileCache and HTMLFileCache::useFileCache() ) {
+ if( $wgUseFileCache && HTMLFileCache::useFileCache() ) {
$cache = new HTMLFileCache( $this->mTitle, 'raw' );
if( $cache->isFileCacheGood( /* Assume up to date */ ) ) {
$cache->loadFromFileCache();
$wgOut->disable();
return;
} else {
- ob_start( array(&$cache, 'saveToFileCache' ) );
+ ob_start( array( &$cache, 'saveToFileCache' ) );
}
}
-
+
$text = $this->getRawText();
if( !wfRunHooks( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) {
@@ -158,11 +151,12 @@ class RawPage {
}
function getRawText() {
- global $wgUser, $wgOut, $wgRequest;
+ global $wgUser, $wgOut;
if( $this->mGen ) {
$sk = $wgUser->getSkin();
- if( !StubObject::isRealObject( $wgOut ) )
+ if( !StubObject::isRealObject( $wgOut ) ) {
$wgOut->_unstub( 2 );
+ }
$sk->initPage( $wgOut );
if( $this->mGen == 'css' ) {
return $sk->generateUserStylesheet();
@@ -183,8 +177,9 @@ class RawPage {
$key = $this->mTitle->getDBkey();
$text = wfMsgForContentNoTrans( $key );
# If the message doesn't exist, return a blank
- if( wfEmptyMsg( $key, $text ) )
+ if( wfEmptyMsg( $key, $text ) ) {
$text = '';
+ }
$found = true;
} else {
// Get it from the DB
@@ -193,11 +188,12 @@ class RawPage {
$lastmod = wfTimestamp( TS_RFC2822, $rev->getTimestamp() );
header( "Last-modified: $lastmod" );
- if( !is_null($this->mSection ) ) {
+ if( !is_null( $this->mSection ) ) {
global $wgParser;
- $text = $wgParser->getSection ( $rev->getText(), $this->mSection );
- } else
+ $text = $wgParser->getSection( $rev->getText(), $this->mSection );
+ } else {
$text = $rev->getText();
+ }
$found = true;
}
}
@@ -209,7 +205,7 @@ class RawPage {
# 404s aren't generally cached and it would create
# extra hits when user CSS/JS are on and the user doesn't
# have the pages.
- header( "HTTP/1.0 404 Not Found" );
+ header( 'HTTP/1.0 404 Not Found' );
}
// Special-case for empty CSS/JS
@@ -221,22 +217,24 @@ class RawPage {
//
// Give it a comment...
if( strlen( $text ) == 0 &&
- ($this->mContentType == 'text/css' ||
+ ( $this->mContentType == 'text/css' ||
$this->mContentType == 'text/javascript' ) ) {
- return "/* Empty */";
+ return '/* Empty */';
}
return $this->parseArticleText( $text );
}
function parseArticleText( $text ) {
- if( $text === '' )
+ if( $text === '' ) {
return '';
- else
+ } else {
if( $this->mExpandTemplates ) {
global $wgParser;
return $wgParser->preprocess( $text, $this->mTitle, new ParserOptions() );
- } else
+ } else {
return $text;
+ }
+ }
}
}
diff --git a/includes/RecentChange.php b/includes/RecentChange.php
index 51b608d8..803420f6 100644
--- a/includes/RecentChange.php
+++ b/includes/RecentChange.php
@@ -2,6 +2,7 @@
/**
* Utility class for creating new RC entries
+ *
* mAttribs:
* rc_id id of the row in the recentchanges table
* rc_timestamp time the entry was made
@@ -73,7 +74,6 @@ class RecentChange {
$res = $dbr->select( 'recentchanges', '*', array( 'rc_id' => $rcid ), __METHOD__ );
if( $res && $dbr->numRows( $res ) > 0 ) {
$row = $dbr->fetchObject( $res );
- $dbr->freeResult( $res );
return self::newFromRow( $row );
} else {
return null;
@@ -115,6 +115,10 @@ class RecentChange {
$this->mExtra = $extra;
}
+ /**
+ *
+ * @return Title
+ */
public function &getTitle() {
if( $this->mTitle === false ) {
$this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
@@ -208,24 +212,27 @@ class RecentChange {
}
/**
- * Send some text to UDP
+ * Send some text to UDP.
+ * @see RecentChange::cleanupForIRC
* @param $line String: text to send
- * @param $prefix String
- * @param $address String: address
+ * @param $address String: defaults to $wgRC2UDPAddress.
+ * @param $prefix String: defaults to $wgRC2UDPPrefix.
+ * @param $port Int: defaults to $wgRC2UDPPort. (Since 1.17)
* @return Boolean: success
*/
- public static function sendToUDP( $line, $address = '', $prefix = '' ) {
+ public static function sendToUDP( $line, $address = '', $prefix = '', $port = '' ) {
global $wgRC2UDPAddress, $wgRC2UDPPrefix, $wgRC2UDPPort;
# Assume default for standard RC case
$address = $address ? $address : $wgRC2UDPAddress;
$prefix = $prefix ? $prefix : $wgRC2UDPPrefix;
+ $port = $port ? $port : $wgRC2UDPPort;
# Notify external application via UDP
if( $address ) {
$conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
if( $conn ) {
$line = $prefix . $line;
wfDebug( __METHOD__ . ": sending UDP line: $line\n" );
- socket_sendto( $conn, $line, strlen($line), 0, $address, $wgRC2UDPPort );
+ socket_sendto( $conn, $line, strlen($line), 0, $address, $port );
socket_close( $conn );
return true;
} else {
@@ -249,7 +256,7 @@ class RecentChange {
*
* @param $change Mixed: RecentChange or corresponding rc_id
* @param $auto Boolean: for automatic patrol
- * @return See doMarkPatrolled(), or null if $change is not an existing rc_id
+ * @return Array See doMarkPatrolled(), or null if $change is not an existing rc_id
*/
public static function markPatrolled( $change, $auto = false ) {
$change = $change instanceof RecentChange
@@ -576,7 +583,7 @@ class RecentChange {
/**
* Get an attribute value
*
- * @param $name Attribute name
+ * @param $name String Attribute name
* @return mixed
*/
public function getAttribute( $name ) {
@@ -667,7 +674,7 @@ class RecentChange {
$flag .= ($rc_new ? "N" : "") . ($rc_minor ? "M" : "") . ($rc_bot ? "B" : "");
}
- if ( $wgRC2UDPInterwikiPrefix === true ) {
+ if ( $wgRC2UDPInterwikiPrefix === true && $wgLocalInterwiki !== false ) {
$prefix = $wgLocalInterwiki;
} elseif ( $wgRC2UDPInterwikiPrefix ) {
$prefix = $wgRC2UDPInterwikiPrefix;
diff --git a/includes/Revision.php b/includes/Revision.php
index 8d2c7e9d..9cc49350 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -82,6 +82,9 @@ class Revision {
if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
// Pre-1.5 ar_text row
$attribs['text'] = self::getRevisionText( $row, 'ar_' );
+ if ( $attribs['text'] === false ) {
+ throw new MWException( 'Unable to load text from archive row (possibly bug 22624)' );
+ }
}
return new self( $attribs );
}
@@ -290,7 +293,7 @@ class Revision {
* @param $row Mixed: either a database row or an array
* @access private
*/
- function Revision( $row ) {
+ function __construct( $row ) {
if( is_object( $row ) ) {
$this->mId = intval( $row->rev_id );
$this->mPage = intval( $row->rev_page );
@@ -314,8 +317,7 @@ class Revision {
if( isset( $row->page_latest ) ) {
$this->mCurrent = ( $row->rev_id == $row->page_latest );
- $this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
- $this->mTitle->resetArticleID( $this->mPage );
+ $this->mTitle = Title::newFromRow( $row );
} else {
$this->mCurrent = false;
$this->mTitle = null;
@@ -601,6 +603,7 @@ class Revision {
/**
* Alias for getText(Revision::FOR_THIS_USER)
*
+ * @deprecated
* @return String
*/
public function revText() {
@@ -798,7 +801,7 @@ class Revision {
* Insert a new revision into the database, returning the new revision ID
* number on success and dies horribly on failure.
*
- * @param $dbw DatabaseBase (master connection)
+ * @param $dbw DatabaseBase: (master connection)
* @return Integer
*/
public function insertOn( $dbw ) {
@@ -996,7 +999,6 @@ class Revision {
public static function userCanBitfield( $bitfield, $field ) {
if( $bitfield & $field ) { // aspect is deleted
global $wgUser;
- $permission = '';
if ( $bitfield & self::DELETED_RESTRICTED ) {
$permission = 'suppressrevision';
} elseif ( $field & self::DELETED_TEXT ) {
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index 26837b3c..a6c64264 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -2,7 +2,7 @@
/**
* XHTML sanitizer for MediaWiki
*
- * Copyright (C) 2002-2005 Brion Vibber <brion@pobox.com> et al
+ * Copyright © 2002-2005 Brion Vibber <brion@pobox.com> et al
* http://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
@@ -40,10 +40,11 @@ define( 'MW_CHAR_REFS_REGEX',
* Allows some... latitude.
* Used in Sanitizer::fixTagAttributes and Sanitizer::decodeTagAttributes
*/
-$attrib = '[A-Za-z0-9]';
+$attribFirst = '[:A-Z_a-z0-9]';
+$attrib = '[:A-Z_a-z-.0-9]';
$space = '[\x09\x0a\x0d\x20]';
define( 'MW_ATTRIBS_REGEX',
- "/(?:^|$space)((?:xml:|xmlns:)?$attrib+)
+ "/(?:^|$space)({$attribFirst}{$attrib}*)
($space*=$space*
(?:
# The attribute value: quoted or alone
@@ -367,7 +368,8 @@ class Sanitizer {
'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's',
'strike', 'strong', 'tt', 'var', 'div', 'center',
'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
- 'ruby', 'rt' , 'rb' , 'rp', 'p', 'span', 'u', 'abbr'
+ 'ruby', 'rt' , 'rb' , 'rp', 'p', 'span', 'abbr', 'dfn',
+ 'kbd', 'samp'
);
$htmlsingle = array(
'br', 'hr', 'li', 'dt', 'dd'
@@ -389,6 +391,12 @@ class Sanitizer {
'li',
);
+ global $wgAllowImageTag;
+ if ( $wgAllowImageTag ) {
+ $htmlsingle[] = 'img';
+ $htmlsingleonly[] = 'img';
+ }
+
$htmlsingleallowed = array_unique( array_merge( $htmlsingle, $tabletags ) );
$htmlelementsStatic = array_unique( array_merge( $htmlsingle, $htmlpairsStatic, $htmlnest ) );
@@ -620,7 +628,7 @@ class Sanitizer {
* @todo Check for unique id attribute :P
*/
static function validateAttributes( $attribs, $whitelist ) {
- global $wgAllowRdfaAttributes, $wgAllowMicrodataAttributes;
+ global $wgAllowRdfaAttributes, $wgAllowMicrodataAttributes, $wgHtml5;
$whitelist = array_flip( $whitelist );
$hrefExp = '/^(' . wfUrlProtocols() . ')[^\s]+$/';
@@ -636,7 +644,8 @@ class Sanitizer {
continue;
}
- if( !isset( $whitelist[$attribute] ) ) {
+ # Allow any attribute beginning with "data-", if in HTML5 mode
+ if ( !($wgHtml5 && preg_match( '/^data-/i', $attribute )) && !isset( $whitelist[$attribute] ) ) {
continue;
}
@@ -914,7 +923,9 @@ class Sanitizer {
*
* To ensure we don't have to bother escaping anything, we also strip ', ",
* & even if $wgExperimentalIds is true. TODO: Is this the best tactic?
- * We also strip # because it upsets IE6.
+ * We also strip # because it upsets IE, and % because it could be
+ * ambiguous if it's part of something that looks like a percent escape
+ * (which don't work reliably in fragments cross-browser).
*
* @see http://www.w3.org/TR/html401/types.html#type-name Valid characters
* in the id and
@@ -940,7 +951,7 @@ class Sanitizer {
if ( $wgHtml5 && $wgExperimentalHtmlIds && !in_array( 'legacy', $options ) ) {
$id = Sanitizer::decodeCharReferences( $id );
- $id = preg_replace( '/[ \t\n\r\f_\'"&#]+/', '_', $id );
+ $id = preg_replace( '/[ \t\n\r\f_\'"&#%]+/', '_', $id );
$id = trim( $id, '_' );
if ( $id === '' ) {
# Must have been all whitespace to start with.
@@ -988,17 +999,16 @@ class Sanitizer {
/**
* Given HTML input, escape with htmlspecialchars but un-escape entites.
- * This allows (generally harmless) entities like &nbsp; to survive.
+ * This allows (generally harmless) entities like &#160; to survive.
*
* @param $html String to escape
* @return String: escaped input
*/
static function escapeHtmlAllowEntities( $html ) {
+ $html = Sanitizer::decodeCharReferences( $html );
# It seems wise to escape ' as well as ", as a matter of course. Can't
# hurt.
$html = htmlspecialchars( $html, ENT_QUOTES );
- $html = str_replace( '&amp;', '&', $html );
- $html = Sanitizer::normalizeCharReferences( $html );
return $html;
}
@@ -1102,11 +1112,24 @@ class Sanitizer {
}
/**
+ * Normalizes whitespace in a section name, such as might be returned
+ * by Parser::stripSectionName(), for use in the id's that are used for
+ * section links.
+ *
+ * @param $section String
+ * @return String
+ */
+ static function normalizeSectionNameWhitespace( $section ) {
+ return trim( preg_replace( '/[ _]+/', ' ', $section ) );
+ }
+
+ /**
* Ensure that any entities and character references are legal
* for XML and XHTML specifically. Any stray bits will be
* &amp;-escaped to result in a valid text fragment.
*
- * a. any named char refs must be known in XHTML
+ * a. named char refs can only be &lt; &gt; &amp; &quot;, others are
+ * numericized (this way we're well-formed even without a DTD)
* b. any numeric char refs must be legal chars, not invalid or forbidden
* c. use &#x, not &#X
* d. fix or reject non-valid attributes
@@ -1145,9 +1168,10 @@ class Sanitizer {
/**
* If the named entity is defined in the HTML 4.0/XHTML 1.0 DTD,
- * return the named entity reference as is. If the entity is a
- * MediaWiki-specific alias, returns the HTML equivalent. Otherwise,
- * returns HTML-escaped text of pseudo-entity source (eg &amp;foo;)
+ * return the equivalent numeric entity reference (except for the core &lt;
+ * &gt; &amp; &quot;). If the entity is a MediaWiki-specific alias, returns
+ * the HTML equivalent. Otherwise, returns HTML-escaped text of
+ * pseudo-entity source (eg &amp;foo;)
*
* @param $name String
* @return String
@@ -1156,8 +1180,11 @@ class Sanitizer {
global $wgHtmlEntities, $wgHtmlEntityAliases;
if ( isset( $wgHtmlEntityAliases[$name] ) ) {
return "&{$wgHtmlEntityAliases[$name]};";
- } elseif( isset( $wgHtmlEntities[$name] ) ) {
+ } elseif ( in_array( $name,
+ array( 'lt', 'gt', 'amp', 'quot' ) ) ) {
return "&$name;";
+ } elseif ( isset( $wgHtmlEntities[$name] ) ) {
+ return "&#{$wgHtmlEntities[$name]};";
} else {
return "&amp;$name;";
}
@@ -1210,6 +1237,30 @@ class Sanitizer {
}
/**
+ * Decode any character references, numeric or named entities,
+ * in the next and normalize the resulting string. (bug 14952)
+ *
+ * This is useful for page titles, not for text to be displayed,
+ * MediaWiki allows HTML entities to escape normalization as a feature.
+ *
+ * @param $text String (already normalized, containing entities)
+ * @return String (still normalized, without entities)
+ */
+ public static function decodeCharReferencesAndNormalize( $text ) {
+ global $wgContLang;
+ $text = preg_replace_callback(
+ MW_CHAR_REFS_REGEX,
+ array( 'Sanitizer', 'decodeCharReferencesCallback' ),
+ $text, /* limit */ -1, $count );
+
+ if ( $count ) {
+ return $wgContLang->normalize( $text );
+ } else {
+ return $text;
+ }
+ }
+
+ /**
* @param $matches String
* @return String
*/
@@ -1342,10 +1393,10 @@ class Sanitizer {
'em' => $common,
'strong' => $common,
'cite' => $common,
- # dfn
+ 'dfn' => $common,
'code' => $common,
- # samp
- # kbd
+ 'samp' => $common,
+ 'kbd' => $common,
'var' => $common,
'abbr' => $common,
# acronym
@@ -1412,8 +1463,9 @@ class Sanitizer {
# 13.2
# Not usually allowed, but may be used for extension-style hooks
- # such as <math> when it is rasterized
- 'img' => array_merge( $common, array( 'alt' ) ),
+ # such as <math> when it is rasterized, or if $wgAllowImageTag is
+ # true
+ 'img' => array_merge( $common, array( 'alt', 'src', 'width', 'height' ) ),
# 15.2.1
'tt' => $common,
@@ -1495,7 +1547,7 @@ class Sanitizer {
$url = Sanitizer::decodeCharReferences( $url );
# Escape any control characters introduced by the above step
- $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url );
+ $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F\|]/e', "urlencode('\\0')", $url );
# Validate hostname portion
$matches = array();
diff --git a/includes/SeleniumWebSettings.php b/includes/SeleniumWebSettings.php
new file mode 100644
index 00000000..8afb26da
--- /dev/null
+++ b/includes/SeleniumWebSettings.php
@@ -0,0 +1,72 @@
+<?php
+/*
+ * Dynamically change configuration variables based on the test suite name and a cookie value.
+ * For details on how to configure a wiki for a Selenium test, see:
+ * http://www.mediawiki.org/wiki/SeleniumFramework#Test_Wiki_configuration
+ */
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( 1 );
+}
+
+$fname = 'SeleniumWebSettings.php';
+wfProfileIn( $fname );
+
+$cookiePrefix = $wgSitename . "-";
+$cookieName = $cookiePrefix . "Selenium";
+
+//if we find a request parameter containing the test name, set a cookie with the test name
+if ( isset( $_GET['setupTestSuite'] ) ) {
+ $setupTestSuiteName = $_GET['setupTestSuite'];
+
+ if ( preg_match( '/[^a-zA-Z0-9_-]/', $setupTestSuiteName ) || !isset( $wgSeleniumTestConfigs[$setupTestSuiteName] ) ) {
+ return;
+ }
+ if ( strlen( $setupTestSuiteName) > 0 ) {
+ $expire = time() + 600;
+ setcookie( $cookieName,
+ $setupTestSuiteName,
+ $expire,
+ $wgCookiePath,
+ $wgCookieDomain,
+ $wgCookieSecure,
+ true );
+ }
+}
+//clear the cookie based on a request param
+if ( isset( $_GET['clearTestSuite'] ) ) {
+ $expire = time() - 600;
+ setcookie( $cookieName,
+ '',
+ $expire,
+ $wgCookiePath,
+ $wgCookieDomain,
+ $wgCookieSecure,
+ true );
+}
+
+//if a cookie is found, run the appropriate callback to get the config params.
+if ( isset( $_COOKIE[$cookieName] ) ) {
+ $testSuiteName = $_COOKIE[$cookieName];
+ if ( !isset( $wgSeleniumTestConfigs[$testSuiteName] ) ) {
+ return;
+ }
+ $testIncludes = array(); //array containing all the includes needed for this test
+ $testGlobalConfigs = array(); //an array containg all the global configs needed for this test
+ $callback = $wgSeleniumTestConfigs[$testSuiteName];
+ call_user_func_array( $callback, array( &$testIncludes, &$testGlobalConfigs));
+
+ foreach ( $testIncludes as $includeFile ) {
+ $file = $IP . '/' . $includeFile;
+ require_once( $file );
+ }
+ foreach ( $testGlobalConfigs as $key => $value ) {
+ if ( is_array( $value ) ) {
+ $GLOBALS[$key] = array_merge( $GLOBALS[$key], $value );
+
+ } else {
+ $GLOBALS[$key] = $value;
+ }
+ }
+}
+
+wfProfileOut( $fname );
diff --git a/includes/Setup.php b/includes/Setup.php
index cd9146ab..5d348885 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -1,6 +1,8 @@
<?php
/**
* Include most things that's need to customize the site
+ *
+ * @file
*/
/**
@@ -29,6 +31,7 @@ if ( !isset( $wgVersion ) ) {
// Set various default paths sensibly...
if( $wgScript === false ) $wgScript = "$wgScriptPath/index$wgScriptExtension";
if( $wgRedirectScript === false ) $wgRedirectScript = "$wgScriptPath/redirect$wgScriptExtension";
+if( $wgLoadScript === false ) $wgLoadScript = "$wgScriptPath/load$wgScriptExtension";
if( $wgArticlePath === false ) {
if( $wgUsePathInfo ) {
@@ -39,6 +42,7 @@ if( $wgArticlePath === false ) {
}
if( $wgStylePath === false ) $wgStylePath = "$wgScriptPath/skins";
+if( $wgLocalStylePath === false ) $wgLocalStylePath = "$wgScriptPath/skins";
if( $wgStyleDirectory === false) $wgStyleDirectory = "$IP/skins";
if( $wgExtensionAssetsPath === false ) $wgExtensionAssetsPath = "$wgScriptPath/extensions";
@@ -53,9 +57,32 @@ if( $wgTmpDirectory === false ) $wgTmpDirectory = "{$wgUploadDirectory}/tmp";
if( $wgReadOnlyFile === false ) $wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR";
if( $wgFileCacheDirectory === false ) $wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
+if( $wgDeletedDirectory === false ) $wgDeletedDirectory = "{$wgUploadDirectory}/deleted";
+
+if( isset( $wgFileStore['deleted']['directory'] ) ) {
+ $wgDeletedDirectory = $wgFileStore['deleted']['directory'];
+}
+
+if( isset($wgFooterIcons["copyright"]) &&
+ isset($wgFooterIcons["copyright"]["copyright"]) &&
+ $wgFooterIcons["copyright"]["copyright"] === array() ) {
+ if ( isset( $wgCopyrightIcon ) && $wgCopyrightIcon ) {
+ $wgFooterIcons["copyright"]["copyright"] = $wgCopyrightIcon;
+ } elseif ( $wgRightsIcon || $wgRightsText ) {
+ $wgFooterIcons["copyright"]["copyright"] = array(
+ "url" => $wgRightsUrl,
+ "src" => $wgRightsIcon,
+ "alt" => $wgRightsText,
+ );
+ } else {
+ unset($wgFooterIcons["copyright"]["copyright"]);
+ }
+}
-if ( empty( $wgFileStore['deleted']['directory'] ) ) {
- $wgFileStore['deleted']['directory'] = "{$wgUploadDirectory}/deleted";
+if( isset($wgFooterIcons["poweredby"]) &&
+ isset($wgFooterIcons["poweredby"]["mediawiki"]) &&
+ $wgFooterIcons["poweredby"]["mediawiki"]["src"] === null ) {
+ $wgFooterIcons["poweredby"]["mediawiki"]["src"] = "$wgStylePath/common/images/poweredby_mediawiki_88x31.png";
}
/**
@@ -79,16 +106,23 @@ $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
* Initialise $wgLocalFileRepo from backwards-compatible settings
*/
if ( !$wgLocalFileRepo ) {
+ if( isset( $wgFileStore['deleted']['hash'] ) ) {
+ $deletedHashLevel = $wgFileStore['deleted']['hash'];
+ } else {
+ $deletedHashLevel = $wgHashedUploadDirectory ? 3 : 0;
+ }
$wgLocalFileRepo = array(
'class' => 'LocalRepo',
'name' => 'local',
'directory' => $wgUploadDirectory,
+ 'scriptDirUrl' => $wgScriptPath,
+ 'scriptExtension' => $wgScriptExtension,
'url' => $wgUploadBaseUrl ? $wgUploadBaseUrl . $wgUploadPath : $wgUploadPath,
'hashLevels' => $wgHashedUploadDirectory ? 2 : 0,
'thumbScriptUrl' => $wgThumbnailScriptPath,
'transformVia404' => !$wgGenerateThumbnailOnParse,
- 'deletedDir' => $wgFileStore['deleted']['directory'],
- 'deletedHashLevels' => $wgFileStore['deleted']['hash']
+ 'deletedDir' => $wgDeletedDirectory,
+ 'deletedHashLevels' => $deletedHashLevel
);
}
/**
@@ -157,7 +191,6 @@ require_once( "$IP/includes/Namespace.php" );
require_once( "$IP/includes/ProxyTools.php" );
require_once( "$IP/includes/ObjectCache.php" );
require_once( "$IP/includes/ImageFunctions.php" );
-require_once( "$IP/includes/StubObject.php" );
wfProfileOut( $fname.'-includes' );
wfProfileIn( $fname.'-misc1' );
@@ -165,7 +198,7 @@ wfProfileIn( $fname.'-misc1' );
wfMemoryLimit();
/**
- * Set up the timezone, suppressing the pseudo-security warning in PHP 5.1+
+ * Set up the timezone, suppressing the pseudo-security warning in PHP 5.1+
* that happens whenever you use a date function without the timezone being
* explicitly set. Inspired by phpMyAdmin's treatment of the problem.
*/
@@ -173,16 +206,21 @@ wfSuppressWarnings();
date_default_timezone_set( date_default_timezone_get() );
wfRestoreWarnings();
-$wgIP = false; # Load on demand
# Can't stub this one, it sets up $_GET and $_REQUEST in its constructor
$wgRequest = new WebRequest;
# Useful debug output
+global $wgCommandLineMode;
if ( $wgCommandLineMode ) {
wfDebug( "\n\nStart command line script $self\n" );
} else {
wfDebug( "Start request\n\n" );
- wfDebug( $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . "\n" );
+ # Output the REQUEST_URI. This is not supported by IIS in rewrite mode,
+ # so use an alternative
+ $requestUri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] :
+ ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ? $_SERVER['HTTP_X_ORIGINAL_URL'] :
+ $_SERVER['PHP_SELF'] );
+ wfDebug( "{$_SERVER['REQUEST_METHOD']} {$requestUri}\n" );
if ( $wgDebugPrintHttpHeaders ) {
$headerOut = "HTTP HEADERS:\n";
@@ -251,6 +289,12 @@ if ( !$wgHtml5Version && $wgHtml5 && $wgAllowRdfaAttributes ) {
else $wgHtml5Version = 'HTML+RDFa 1.0';
}
+if ( $wgInvalidateCacheOnLocalSettingsChange ) {
+ $wgCacheEpoch = max( $wgCacheEpoch, gmdate( 'YmdHis', @filemtime( "$IP/LocalSettings.php" ) ) );
+}
+
+# Blacklisted file extensions shouldn't appear on the "allowed" list
+$wgFileExtensions = array_diff ( $wgFileExtensions, $wgFileBlacklist );
wfProfileOut( $fname.'-misc1' );
wfProfileIn( $fname.'-memcached' );
@@ -291,13 +335,15 @@ $wgCookiePrefix = strtr($wgCookiePrefix, "=,; +.\"'\\[", "__________");
if( !wfIniGetBool( 'session.auto_start' ) )
session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' );
-if( !$wgCommandLineMode && ( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix.'Token'] ) ) ) {
- wfIncrStats( 'request_with_session' );
- wfSetupSession();
- $wgSessionStarted = true;
-} else {
- wfIncrStats( 'request_without_session' );
- $wgSessionStarted = false;
+if( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
+ if( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix.'Token'] ) ) {
+ wfIncrStats( 'request_with_session' );
+ wfSetupSession();
+ $wgSessionStarted = true;
+ } else {
+ wfIncrStats( 'request_without_session' );
+ $wgSessionStarted = false;
+ }
}
wfProfileOut( $fname.'-SetupSession' );
@@ -307,14 +353,13 @@ $wgContLang = new StubContLang;
// Now that variant lists may be available...
$wgRequest->interpolateTitle();
-
-$wgUser = new StubUser;
+$wgUser = $wgCommandLineMode ? new User : User::newFromSession();
$wgLang = new StubUserLang;
$wgOut = new StubObject( 'wgOut', 'OutputPage' );
$wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
$wgMessageCache = new StubObject( 'wgMessageCache', 'MessageCache',
- array( $messageMemc, $wgUseDatabaseMessages, $wgMsgCacheExpiry, wfWikiID() ) );
+ array( $messageMemc, $wgUseDatabaseMessages, $wgMsgCacheExpiry ) );
wfProfileOut( $fname.'-globals' );
wfProfileIn( $fname.'-User' );
@@ -337,9 +382,7 @@ wfProfileOut( $fname.'-User' );
wfProfileIn( $fname.'-misc2' );
$wgDeferredUpdateList = array();
-$wgPostCommitUpdateList = array();
-if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch';
if ( $wgAjaxUploadDestCheck ) $wgAjaxExportList[] = 'SpecialUpload::ajaxGetExistsWarning';
# Placeholders in case of DB error
diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php
index b6d83670..f4a4576a 100644
--- a/includes/SiteConfiguration.php
+++ b/includes/SiteConfiguration.php
@@ -194,10 +194,9 @@ class SiteConfiguration {
/**
* Retrieves a configuration setting for a given wiki, forced to a boolean.
- * @param $settingName String ID of the setting name to retrieve
+ * @param $setting String ID of the setting name to retrieve
* @param $wiki String Wiki ID of the wiki in question.
* @param $suffix String The suffix of the wiki in question.
- * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
* @param $wikiTags Array The tags assigned to the wiki.
* @return bool The value of the setting requested.
*/
@@ -216,7 +215,7 @@ class SiteConfiguration {
/**
* Retrieves the value of a given setting, and places it in a variable passed by reference.
- * @param $settingName String ID of the setting name to retrieve
+ * @param $setting String ID of the setting name to retrieve
* @param $wiki String Wiki ID of the wiki in question.
* @param $suffix String The suffix of the wiki in question.
* @param $var Reference The variable to insert the value into.
@@ -232,7 +231,7 @@ class SiteConfiguration {
/**
* Retrieves the value of a given setting, and places it in its corresponding global variable.
- * @param $settingName String ID of the setting name to retrieve
+ * @param $setting String ID of the setting name to retrieve
* @param $wiki String Wiki ID of the wiki in question.
* @param $suffix String The suffix of the wiki in question.
* @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
@@ -294,8 +293,9 @@ class SiteConfiguration {
$ret = call_user_func_array( $this->siteParamsCallback, array( $this, $wiki ) );
# Validate the returned value
- if( !is_array( $ret ) )
+ if( !is_array( $ret ) ) {
return $default;
+ }
foreach( $default as $name => $def ){
if( !isset( $ret[$name] ) || ( is_array( $default[$name] ) && !is_array( $ret[$name] ) ) )
diff --git a/includes/SiteStats.php b/includes/SiteStats.php
index 16e3c5f2..11cef56f 100644
--- a/includes/SiteStats.php
+++ b/includes/SiteStats.php
@@ -104,23 +104,27 @@ class SiteStats {
*/
static function admins() {
wfDeprecated(__METHOD__);
- return self::numberingroup('sysop');
+ return self::numberingroup( 'sysop' );
}
/**
* Find the number of users in a given user group.
- * @param string $group Name of group
- * @return int
+ * @param $group String: name of group
+ * @return Integer
*/
- static function numberingroup($group) {
+ static function numberingroup( $group ) {
if ( !isset( self::$groupMemberCounts[$group] ) ) {
global $wgMemc;
$key = wfMemcKey( 'SiteStats', 'groupcounts', $group );
$hit = $wgMemc->get( $key );
if ( !$hit ) {
$dbr = wfGetDB( DB_SLAVE );
- $hit = $dbr->selectField( 'user_groups', 'COUNT(*)',
- array( 'ug_group' => $group ), __METHOD__ );
+ $hit = $dbr->selectField(
+ 'user_groups',
+ 'COUNT(*)',
+ array( 'ug_group' => $group ),
+ __METHOD__
+ );
$wgMemc->set( $key, $hit, 3600 );
}
self::$groupMemberCounts[$group] = $hit;
@@ -131,9 +135,9 @@ class SiteStats {
static function jobs() {
if ( !isset( self::$jobs ) ) {
$dbr = wfGetDB( DB_SLAVE );
- self::$jobs = $dbr->estimateRowCount('job');
+ self::$jobs = $dbr->estimateRowCount( 'job' );
/* Zero rows still do single row read for row that doesn't exist, but people are annoyed by that */
- if (self::$jobs == 1) {
+ if ( self::$jobs == 1 ) {
self::$jobs = 0;
}
}
@@ -144,7 +148,12 @@ class SiteStats {
wfProfileIn( __METHOD__ );
if( !isset( self::$pageCount[$ns] ) ) {
$dbr = wfGetDB( DB_SLAVE );
- $pageCount[$ns] = (int)$dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $ns ), __METHOD__ );
+ $pageCount[$ns] = (int)$dbr->selectField(
+ 'page',
+ 'COUNT(*)',
+ array( 'page_namespace' => $ns ),
+ __METHOD__
+ );
}
wfProfileOut( __METHOD__ );
return $pageCount[$ns];
@@ -156,7 +165,6 @@ class SiteStats {
$row === false
or $row->ss_total_pages < $row->ss_good_articles
or $row->ss_total_edits < $row->ss_total_pages
- or $row->ss_users < $row->ss_admins
) {
return false;
}
@@ -164,7 +172,7 @@ class SiteStats {
foreach( array( 'total_views', 'total_edits', 'good_articles',
'total_pages', 'users', 'admins', 'images' ) as $member ) {
if(
- $row->{"ss_$member"} > 2000000000
+ $row->{"ss_$member"} > 2000000000
or $row->{"ss_$member"} < 0
) {
return false;
@@ -226,15 +234,26 @@ class SiteStatsUpdate {
}
public static function cacheUpdate( $dbw ) {
- $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow') );
+ global $wgActiveUserDays;
+ $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow' ) );
# Get non-bot users than did some recent action other than making accounts.
# If account creation is included, the number gets inflated ~20+ fold on enwiki.
- $activeUsers = $dbr->selectField( 'recentchanges', 'COUNT( DISTINCT rc_user_text )',
- array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers' OR rc_log_type IS NULL" ),
- __METHOD__ );
- $dbw->update( 'site_stats',
- array( 'ss_active_users' => intval($activeUsers) ),
- array( 'ss_row_id' => 1 ), __METHOD__
+ $activeUsers = $dbr->selectField(
+ 'recentchanges',
+ 'COUNT( DISTINCT rc_user_text )',
+ array(
+ 'rc_user != 0',
+ 'rc_bot' => 0,
+ "rc_log_type != 'newusers' OR rc_log_type IS NULL",
+ "rc_timestamp >= '{$dbw->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays*24*3600 )}'",
+ ),
+ __METHOD__
+ );
+ $dbw->update(
+ 'site_stats',
+ array( 'ss_active_users' => intval( $activeUsers ) ),
+ array( 'ss_row_id' => 1 ),
+ __METHOD__
);
return $activeUsers;
}
@@ -245,7 +264,7 @@ class SiteStatsUpdate {
*/
class SiteStatsInit {
- // Db connection
+ // Database connection
private $db;
// Various stats
@@ -253,7 +272,7 @@ class SiteStatsInit {
/**
* Constructor
- * @param $useMaster bool Whether to use the master db
+ * @param $useMaster Boolean: whether to use the master DB
*/
public function __construct( $useMaster = false ) {
$this->db = wfGetDB( $useMaster ? DB_MASTER : DB_SLAVE );
@@ -261,7 +280,7 @@ class SiteStatsInit {
/**
* Count the total number of edits
- * @return int
+ * @return Integer
*/
public function edits() {
$this->mEdits = $this->db->selectField( 'revision', 'COUNT(*)', '', __METHOD__ );
@@ -270,18 +289,27 @@ class SiteStatsInit {
}
/**
- * Count pages in article space
- * @return int
+ * Count pages in article space(s)
+ * @return Integer
*/
public function articles() {
global $wgContentNamespaces;
- $this->mArticles = $this->db->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $wgContentNamespaces, 'page_is_redirect' => 0, 'page_len > 0' ), __METHOD__ );
+ $this->mArticles = $this->db->selectField(
+ 'page',
+ 'COUNT(*)',
+ array(
+ 'page_namespace' => $wgContentNamespaces,
+ 'page_is_redirect' => 0,
+ 'page_len > 0'
+ ),
+ __METHOD__
+ );
return $this->mArticles;
}
/**
* Count total pages
- * @return int
+ * @return Integer
*/
public function pages() {
$this->mPages = $this->db->selectField( 'page', 'COUNT(*)', '', __METHOD__ );
@@ -290,7 +318,7 @@ class SiteStatsInit {
/**
* Count total users
- * @return int
+ * @return Integer
*/
public function users() {
$this->mUsers = $this->db->selectField( 'user', 'COUNT(*)', '', __METHOD__ );
@@ -299,7 +327,7 @@ class SiteStatsInit {
/**
* Count views
- * @return int
+ * @return Integer
*/
public function views() {
$this->mViews = $this->db->selectField( 'page', 'SUM(page_counter)', '', __METHOD__ );
@@ -308,7 +336,7 @@ class SiteStatsInit {
/**
* Count total files
- * @return int
+ * @return Integer
*/
public function files() {
$this->mFiles = $this->db->selectField( 'image', 'COUNT(*)', '', __METHOD__ );
@@ -318,9 +346,9 @@ class SiteStatsInit {
/**
* Do all updates and commit them. More or less a replacement
* for the original initStats, but without the calls to wfOut()
- * @param $update bool Whether to update the current stats or write fresh
- * @param $noViews bool When true, do not update the number of page views
- * @param $activeUsers Whether to update the number of active users
+ * @param $update Boolean: whether to update the current stats or write fresh
+ * @param $noViews Boolean: when true, do not update the number of page views
+ * @param $activeUsers Boolean: whether to update the number of active users
*/
public static function doAllAndCommit( $update, $noViews = false, $activeUsers = false ) {
// Grab the object and count everything
@@ -332,18 +360,21 @@ class SiteStatsInit {
$counter->files();
// Only do views if we don't want to not count them
- if( !$noViews )
+ if( !$noViews ) {
$counter->views();
+ }
// Update/refresh
- if( $update )
+ if( $update ) {
$counter->update();
- else
+ } else {
$counter->refresh();
+ }
// Count active users if need be
- if( $activeUsers )
+ if( $activeUsers ) {
SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) );
+ }
}
/**
@@ -368,15 +399,16 @@ class SiteStatsInit {
/**
* Return three arrays of params for the db queries
- * @return array
+ * @return Array
*/
private function getDbParams() {
- $values = array( 'ss_total_edits' => $this->mEdits,
- 'ss_good_articles' => $this->mArticles,
- 'ss_total_pages' => $this->mPages,
- 'ss_users' => $this->mUsers,
- 'ss_admins' => SiteStats::numberingroup( 'sysop' ), // @todo make this go away
- 'ss_images' => $this->mFiles );
+ $values = array(
+ 'ss_total_edits' => $this->mEdits,
+ 'ss_good_articles' => $this->mArticles,
+ 'ss_total_pages' => $this->mPages,
+ 'ss_users' => $this->mUsers,
+ 'ss_images' => $this->mFiles
+ );
$conds = array( 'ss_row_id' => 1 );
$views = array( 'ss_total_views' => $this->mViews );
return array( $values, $conds, $views );
diff --git a/includes/Skin.php b/includes/Skin.php
index 18867cbe..01b3b4fe 100644
--- a/includes/Skin.php
+++ b/includes/Skin.php
@@ -40,20 +40,24 @@ class Skin extends Linker {
static function getSkinNames() {
global $wgValidSkinNames;
static $skinsInitialised = false;
+
if ( !$skinsInitialised ) {
# 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 );
# while code from www.php.net
- while( false !== ( $file = $skinDir->read() ) ) {
+ while ( false !== ( $file = $skinDir->read() ) ) {
// Skip non-PHP files, hidden files, and '.dep' includes
$matches = array();
- if( preg_match( '/^([^.]*)\.php$/', $file, $matches ) ) {
+
+ if ( preg_match( '/^([^.]*)\.php$/', $file, $matches ) ) {
$aSkin = $matches[1];
$wgValidSkinNames[strtolower( $aSkin )] = $aSkin;
}
@@ -73,10 +77,13 @@ class Skin extends Linker {
*/
public static function getUsableSkins() {
global $wgSkipSkins;
+
$usableSkins = self::getSkinNames();
+
foreach ( $wgSkipSkins as $skip ) {
unset( $usableSkins[$skip] );
}
+
return $usableSkins;
}
@@ -89,15 +96,16 @@ class Skin extends Linker {
*/
static function normalizeKey( $key ) {
global $wgDefaultSkin;
+
$skinNames = Skin::getSkinNames();
- if( $key == '' ) {
+ if ( $key == '' ) {
// Don't return the default immediately;
// in a misconfiguration we need to fall back.
$key = $wgDefaultSkin;
}
- if( isset( $skinNames[$key] ) ) {
+ if ( isset( $skinNames[$key] ) ) {
return $key;
}
@@ -109,14 +117,16 @@ class Skin extends Linker {
2 => 'cologneblue'
);
- if( isset( $fallback[$key] ) ) {
+ if ( isset( $fallback[$key] ) ) {
$key = $fallback[$key];
}
- if( isset( $skinNames[$key] ) ) {
+ if ( isset( $skinNames[$key] ) ) {
return $key;
+ } else if ( isset( $skinNames[$wgDefaultSkin] ) ) {
+ return $wgDefaultSkin;
} else {
- return 'monobook';
+ return 'vector';
}
}
@@ -138,20 +148,21 @@ class Skin extends Linker {
if ( !class_exists( $className ) ) {
// Preload base classes to work around APC/PHP5 bug
$deps = "{$wgStyleDirectory}/{$skinName}.deps.php";
- if( file_exists( $deps ) ) {
+
+ if ( file_exists( $deps ) ) {
include_once( $deps );
}
require_once( "{$wgStyleDirectory}/{$skinName}.php" );
# Check if we got if not failback to default skin
- if( !class_exists( $className ) ) {
+ if ( !class_exists( $className ) ) {
# DO NOT die if the class isn't found. This breaks maintenance
# scripts and can cause a user account to be unrecoverable
# except by SQL manipulation if a previously valid skin name
# is no longer valid.
wfDebug( "Skin class does not exist: $className\n" );
- $className = 'SkinMonobook';
- require_once( "{$wgStyleDirectory}/MonoBook.php" );
+ $className = 'SkinVector';
+ require_once( "{$wgStyleDirectory}/Vector.php" );
}
}
$skin = new $className;
@@ -174,12 +185,14 @@ class Skin extends Linker {
if ( $wgOut->isQuickbarSuppressed() ) {
return 0;
}
+
$q = $wgUser->getOption( 'quickbar', 0 );
+
return $q;
}
function initPage( OutputPage $out ) {
- global $wgFavicon, $wgAppleTouchIcon;
+ global $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI;
wfProfileIn( __METHOD__ );
@@ -187,11 +200,11 @@ class Skin extends Linker {
# should not matter, but Konqueror (3.5.9 at least) incorrectly
# uses whichever one appears later in the HTML source. Make sure
# apple-touch-icon is specified first to avoid this.
- if( false !== $wgAppleTouchIcon ) {
+ if ( false !== $wgAppleTouchIcon ) {
$out->addLink( array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
}
- if( false !== $wgFavicon ) {
+ if ( false !== $wgFavicon ) {
$out->addLink( array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
}
@@ -201,7 +214,19 @@ class Skin extends Linker {
'type' => 'application/opensearchdescription+xml',
'href' => wfScript( 'opensearch_desc' ),
'title' => wfMsgForContent( 'opensearch-desc' ),
- ));
+ ) );
+
+ if ( $wgEnableAPI ) {
+ # Real Simple Discovery link, provides auto-discovery information
+ # for the MediaWiki API (and potentially additional custom API
+ # support such as WordPress or Twitter-compatible APIs for a
+ # blogging extension, etc)
+ $out->addLink( array(
+ 'rel' => 'EditURI',
+ 'type' => 'application/rsd+xml',
+ 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ) ),
+ ) );
+ }
$this->addMetadataLinks( $out );
@@ -231,28 +256,41 @@ class Skin extends Linker {
}
$lb = new LinkBatch( $titles );
+ $lb->setCaller( __METHOD__ );
$lb->execute();
}
/**
- * Adds metadata links (Creative Commons/Dublin Core/copyright) to the HTML
- * output.
+ * Adds metadata links below to the HTML output.
+ * <ol>
+ * <li>Creative Commons
+ * <br />See http://wiki.creativecommons.org/Extend_Metadata.
+ * </li>
+ * <li>Dublin Core</li>
+ * <li>Use hreflang to specify canonical and alternate links
+ * <br />See http://www.google.com/support/webmasters/bin/answer.py?answer=189077
+ * </li>
+ * <li>Copyright</li>
+ * <ol>
+ *
* @param $out Object: instance of OutputPage
*/
function addMetadataLinks( OutputPage $out ) {
global $wgEnableDublinCoreRdf, $wgEnableCreativeCommonsRdf;
+ global $wgDisableLangConversion, $wgCanonicalLanguageLinks, $wgContLang;
global $wgRightsPage, $wgRightsUrl;
- if( $out->isArticleRelated() ) {
+ if ( $out->isArticleRelated() ) {
# note: buggy CC software only reads first "meta" link
- if( $wgEnableCreativeCommonsRdf ) {
+ if ( $wgEnableCreativeCommonsRdf ) {
$out->addMetadataLink( array(
'title' => 'Creative Commons',
'type' => 'application/rdf+xml',
'href' => $this->mTitle->getLocalURL( 'action=creativecommons' ) )
);
}
- if( $wgEnableDublinCoreRdf ) {
+
+ if ( $wgEnableDublinCoreRdf ) {
$out->addMetadataLink( array(
'title' => 'Dublin Core',
'type' => 'application/rdf+xml',
@@ -260,17 +298,43 @@ class Skin extends Linker {
);
}
}
+
+ if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks
+ && $wgContLang->hasVariants() ) {
+
+ $urlvar = $wgContLang->getURLVariant();
+
+ if ( !$urlvar ) {
+ $variants = $wgContLang->getVariants();
+ foreach ( $variants as $_v ) {
+ $out->addLink( array(
+ 'rel' => 'alternate',
+ 'hreflang' => $_v,
+ 'href' => $this->mTitle->getLocalURL( '', $_v ) )
+ );
+ }
+ } else {
+ $out->addLink( array(
+ 'rel' => 'canonical',
+ 'href' => $this->mTitle->getFullURL() )
+ );
+ }
+ }
+
$copyright = '';
- if( $wgRightsPage ) {
+ if ( $wgRightsPage ) {
$copy = Title::newFromText( $wgRightsPage );
- if( $copy ) {
+
+ if ( $copy ) {
$copyright = $copy->getLocalURL();
}
}
- if( !$copyright && $wgRightsUrl ) {
+
+ if ( !$copyright && $wgRightsUrl ) {
$copyright = $wgRightsUrl;
}
- if( $copyright ) {
+
+ if ( $copyright ) {
$out->addLink( array(
'rel' => 'copyright',
'href' => $copyright )
@@ -290,7 +354,7 @@ class Skin extends Linker {
/**
* Set the title
- * @param Title $t The title to use
+ * @param $t Title object to use
*/
public function setTitle( $t ) {
$this->mTitle = $t;
@@ -330,7 +394,7 @@ class Skin extends Linker {
$out->out( $afterContent );
- $out->out( $this->bottomScripts() );
+ $out->out( $this->bottomScripts( $out ) );
$out->out( wfReportTime() );
@@ -339,67 +403,30 @@ class Skin extends Linker {
}
static function makeVariablesScript( $data ) {
- if( $data ) {
- $r = array();
- foreach ( $data as $name => $value ) {
- $encValue = Xml::encodeJsVar( $value );
- $r[] = "$name=$encValue";
- }
- $js = 'var ' . implode( ",\n", $r ) . ';';
- return Html::inlineScript( "\n$js\n" );
+ if ( $data ) {
+ return Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript( ResourceLoader::makeConfigSetScript( $data ) )
+ );
} else {
return '';
- }
+ }
}
/**
* Make a <script> tag containing global variables
* @param $skinName string Name of the skin
* The odd calling convention is for backwards compatibility
- * @TODO @FIXME Make this not depend on $wgTitle!
+ * @todo FIXME: Make this not depend on $wgTitle!
+ *
+ * Do not add things here which can be evaluated in ResourceLoaderStartupScript - in other words, without state.
+ * You will only be adding bloat to the page and causing page caches to have to be purged on configuration changes.
*/
static function makeGlobalVariablesScript( $skinName ) {
- if ( is_array( $skinName ) ) {
- # Weird back-compat stuff.
- $skinName = $skinName['skinname'];
- }
- global $wgScript, $wgTitle, $wgStylePath, $wgUser, $wgScriptExtension;
- global $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgLang;
- global $wgOut, $wgArticle;
- global $wgBreakFrames, $wgRequest, $wgVariantArticlePath, $wgActionPaths;
- global $wgUseAjax, $wgAjaxWatch;
- global $wgVersion, $wgEnableAPI, $wgEnableWriteAPI;
- global $wgRestrictionTypes;
- global $wgMWSuggestTemplate, $wgDBname, $wgEnableMWSuggest;
- global $wgSitename;
-
+ global $wgTitle, $wgUser, $wgRequest, $wgArticle, $wgOut, $wgUseAjax, $wgEnableMWSuggest;
+
$ns = $wgTitle->getNamespace();
$nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $wgTitle->getNsText();
- $separatorTransTable = $wgContLang->separatorTransformTable();
- $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
- $compactSeparatorTransTable = array(
- implode( "\t", array_keys( $separatorTransTable ) ),
- implode( "\t", $separatorTransTable ),
- );
- $digitTransTable = $wgContLang->digitTransformTable();
- $digitTransTable = $digitTransTable ? $digitTransTable : array();
- $compactDigitTransTable = array(
- implode( "\t", array_keys( $digitTransTable ) ),
- implode( "\t", $digitTransTable ),
- );
-
- $mainPage = Title::newFromText( wfMsgForContent( 'mainpage' ) );
$vars = array(
- 'skin' => $skinName,
- 'stylepath' => $wgStylePath,
- 'wgUrlProtocols' => wfUrlProtocols(),
- 'wgArticlePath' => $wgArticlePath,
- 'wgScriptPath' => $wgScriptPath,
- 'wgScriptExtension' => $wgScriptExtension,
- 'wgScript' => $wgScript,
- 'wgVariantArticlePath' => $wgVariantArticlePath,
- 'wgActionPaths' => (object)$wgActionPaths,
- 'wgServer' => $wgServer,
'wgCanonicalNamespace' => $nsname,
'wgCanonicalSpecialPageName' => $ns == NS_SPECIAL ?
SpecialPage::resolveAlias( $wgTitle->getDBkey() ) : false, # bug 21115
@@ -410,55 +437,21 @@ class Skin extends Linker {
'wgArticleId' => $wgTitle->getArticleId(),
'wgIsArticle' => $wgOut->isArticle(),
'wgUserName' => $wgUser->isAnon() ? null : $wgUser->getName(),
- 'wgUserGroups' => $wgUser->isAnon() ? null : $wgUser->getEffectiveGroups(),
- 'wgUserLanguage' => $wgLang->getCode(),
- 'wgContentLanguage' => $wgContLang->getCode(),
- 'wgBreakFrames' => $wgOut->getFrameOptions() == 'DENY',
+ 'wgUserGroups' => $wgUser->getEffectiveGroups(),
'wgCurRevisionId' => isset( $wgArticle ) ? $wgArticle->getLatest() : 0,
- 'wgVersion' => $wgVersion,
- 'wgEnableAPI' => $wgEnableAPI,
- 'wgEnableWriteAPI' => $wgEnableWriteAPI,
- 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
- 'wgDigitTransformTable' => $compactDigitTransTable,
- 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
- 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
- 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
- 'wgSiteName' => $wgSitename,
'wgCategories' => $wgOut->getCategories(),
+ 'wgBreakFrames' => $wgOut->getFrameOptions() == 'DENY',
);
- if ( $wgContLang->hasVariants() ) {
- $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
- }
-
- // if on upload page output the extension list & js_upload
- if( SpecialPage::resolveAlias( $wgTitle->getDBkey() ) == 'Upload' ) {
- global $wgFileExtensions, $wgAjaxUploadInterface;
- $vars['wgFileExtensions'] = $wgFileExtensions;
- }
-
- if( $wgUseAjax && $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ) {
- $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
- $vars['wgDBname'] = $wgDBname;
- $vars['wgSearchNamespaces'] = SearchEngine::userNamespaces( $wgUser );
- $vars['wgMWSuggestMessages'] = array( wfMsg( 'search-mwsuggest-enabled' ), wfMsg( 'search-mwsuggest-disabled' ) );
- }
-
- foreach( $wgRestrictionTypes as $type ) {
+ foreach ( $wgTitle->getRestrictionTypes() as $type ) {
$vars['wgRestriction' . ucfirst( $type )] = $wgTitle->getRestrictions( $type );
}
-
- if ( $wgOut->isArticleRelated() && $wgUseAjax && $wgAjaxWatch && $wgUser->isLoggedIn() ) {
- $msgs = (object)array();
- foreach ( array( 'watch', 'unwatch', 'watching', 'unwatching',
- 'tooltip-ca-watch', 'tooltip-ca-unwatch' ) as $msgName ) {
- $msgs->{$msgName . 'Msg'} = wfMsg( $msgName );
- }
- $vars['wgAjaxWatch'] = $msgs;
+ if ( $wgUseAjax && $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ) {
+ $vars['wgSearchNamespaces'] = SearchEngine::userNamespaces( $wgUser );
}
-
+
// Allow extensions to add their custom variables to the global JS variables
wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars ) );
-
+
return self::makeVariablesScript( $vars );
}
@@ -475,18 +468,19 @@ class Skin extends Linker {
public function userCanPreview( $action ) {
global $wgRequest, $wgUser;
- if( $action != 'submit' ) {
+ if ( $action != 'submit' ) {
return false;
}
- if( !$wgRequest->wasPosted() ) {
+ if ( !$wgRequest->wasPosted() ) {
return false;
}
- if( !$this->mTitle->userCanEditCssSubpage() ) {
+ if ( !$this->mTitle->userCanEditCssSubpage() ) {
return false;
}
- if( !$this->mTitle->userCanEditJsSubpage() ) {
+ if ( !$this->mTitle->userCanEditJsSubpage() ) {
return false;
}
+
return $wgUser->matchEditToken(
$wgRequest->getVal( 'wpEditToken' ) );
}
@@ -506,185 +500,126 @@ class Skin extends Linker {
* @return string
*/
public function generateUserJs( $skinName = null ) {
- global $wgStylePath;
-
- wfProfileIn( __METHOD__ );
- if( !$skinName ) {
- $skinName = $this->getSkinName();
- }
-
- $s = "/* generated javascript */\n";
- $s .= "var skin = '" . Xml::escapeJsString( $skinName ) . "';\n";
- $s .= "var stylepath = '" . Xml::escapeJsString( $wgStylePath ) . "';";
- $s .= "\n\n/* MediaWiki:Common.js */\n";
- $commonJs = wfMsgExt( 'common.js', 'content' );
- if ( !wfEmptyMsg( 'common.js', $commonJs ) ) {
- $s .= $commonJs;
- }
-
- $s .= "\n\n/* MediaWiki:" . ucfirst( $skinName ) . ".js */\n";
- // avoid inclusion of non defined user JavaScript (with custom skins only)
- // by checking for default message content
- $msgKey = ucfirst( $skinName ) . '.js';
- $userJS = wfMsgExt( $msgKey, 'content' );
- if ( !wfEmptyMsg( $msgKey, $userJS ) ) {
- $s .= $userJS;
- }
-
- wfProfileOut( __METHOD__ );
- return $s;
+
+ // Stub - see ResourceLoaderSiteModule, CologneBlue, Simple and Standard skins override this
+
+ return '';
}
/**
* Generate user stylesheet for action=raw&gen=css
*/
public function generateUserStylesheet() {
- wfProfileIn( __METHOD__ );
- $s = "/* generated user stylesheet */\n" .
- $this->reallyGenerateUserStylesheet();
- wfProfileOut( __METHOD__ );
- return $s;
+
+ // Stub - see ResourceLoaderUserModule, CologneBlue, Simple and Standard skins override this
+
+ return '';
}
/**
* Split for easier subclassing in SkinSimple, SkinStandard and SkinCologneBlue
+ * Anything in here won't be generated if $wgAllowUserCssPrefs is false.
*/
protected function reallyGenerateUserStylesheet() {
- global $wgUser;
- $s = '';
- if( ( $undopt = $wgUser->getOption( 'underline' ) ) < 2 ) {
- $underline = $undopt ? 'underline' : 'none';
- $s .= "a { text-decoration: $underline; }\n";
- }
- if( $wgUser->getOption( 'highlightbroken' ) ) {
- $s .= "a.new, #quickbar a.new { color: #CC2200; }\n";
- } else {
- $s .= <<<CSS
-a.new, #quickbar a.new,
-a.stub, #quickbar a.stub {
- color: inherit;
-}
-a.new:after, #quickbar a.new:after {
- content: "?";
- color: #CC2200;
-}
-a.stub:after, #quickbar a.stub:after {
- content: "!";
- color: #772233;
-}
-CSS;
- }
- if( $wgUser->getOption( 'justify' ) ) {
- $s .= "#article, #bodyContent, #mw_content { text-align: justify; }\n";
- }
- if( !$wgUser->getOption( 'showtoc' ) ) {
- $s .= "#toc { display: none; }\n";
- }
- if( !$wgUser->getOption( 'editsection' ) ) {
- $s .= ".editsection { display: none; }\n";
- }
- $fontstyle = $wgUser->getOption( 'editfont' );
- if ( $fontstyle !== 'default' ) {
- $s .= "textarea { font-family: $fontstyle; }\n";
- }
- return $s;
+
+ // Stub - see ResourceLoaderUserModule, CologneBlue, Simple and Standard skins override this
+
+ return '';
}
/**
* @private
*/
function setupUserCss( OutputPage $out ) {
- global $wgRequest, $wgContLang, $wgUser;
- global $wgAllowUserCss, $wgUseSiteCss, $wgSquidMaxage, $wgStylePath;
+ global $wgRequest;
+ global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs;
wfProfileIn( __METHOD__ );
$this->setupSkinUserCss( $out );
-
- $siteargs = array(
- 'action' => 'raw',
- 'maxage' => $wgSquidMaxage,
- );
-
// Add any extension CSS
foreach ( $out->getExtStyle() as $url ) {
$out->addStyle( $url );
}
- // If we use the site's dynamic CSS, throw that in, too
// Per-site custom styles
- if( $wgUseSiteCss ) {
- global $wgHandheldStyle;
- $query = wfArrayToCGI( array(
- 'usemsgcache' => 'yes',
- 'ctype' => 'text/css',
- 'smaxage' => $wgSquidMaxage
- ) + $siteargs );
- # Site settings must override extension css! (bug 15025)
- $out->addStyle( self::makeNSUrl( 'Common.css', $query, NS_MEDIAWIKI ) );
- $out->addStyle( self::makeNSUrl( 'Print.css', $query, NS_MEDIAWIKI ), 'print' );
- if( $wgHandheldStyle ) {
- $out->addStyle( self::makeNSUrl( 'Handheld.css', $query, NS_MEDIAWIKI ), 'handheld' );
- }
- $out->addStyle( self::makeNSUrl( $this->getSkinName() . '.css', $query, NS_MEDIAWIKI ) );
+ if ( $wgUseSiteCss ) {
+ $out->addModuleStyles( 'site' );
}
- if( $wgUser->isLoggedIn() ) {
- // Ensure that logged-in users' generated CSS isn't clobbered
- // by anons' publicly cacheable generated CSS.
- $siteargs['smaxage'] = '0';
- $siteargs['ts'] = $wgUser->mTouched;
- }
- // Per-user styles based on preferences
- $siteargs['gen'] = 'css';
- if( ( $us = $wgRequest->getVal( 'useskin', '' ) ) !== '' ) {
- $siteargs['useskin'] = $us;
- }
- $out->addStyle( self::makeUrl( '-', wfArrayToCGI( $siteargs ) ) );
-
- // Per-user custom style pages
- if( $wgAllowUserCss && $wgUser->isLoggedIn() ) {
- $action = $wgRequest->getVal( 'action' );
- # If we're previewing the CSS page, use it
- if( $this->mTitle->isCssSubpage() && $this->userCanPreview( $action ) ) {
+ // Per-user custom styles
+ if ( $wgAllowUserCss ) {
+ if ( $this->mTitle->isCssSubpage() && $this->userCanPreview( $wgRequest->getVal( 'action' ) ) ) {
// @FIXME: properly escape the cdata!
$out->addInlineStyle( $wgRequest->getText( 'wpTextbox1' ) );
} else {
- $out->addStyle( self::makeUrl(
- $this->userpage . '/' . $this->getSkinName() . '.css',
- 'action=raw&ctype=text/css' )
- );
+ $out->addModuleStyles( 'user' );
}
}
+ // Per-user preference styles
+ if ( $wgAllowUserCssPrefs ) {
+ $out->addModuleStyles( 'user.options' );
+ }
+
wfProfileOut( __METHOD__ );
}
/**
+ * Get the query to generate a dynamic stylesheet
+ *
+ * @return array
+ */
+ public static function getDynamicStylesheetQuery() {
+ global $wgSquidMaxage;
+
+ return array(
+ 'action' => 'raw',
+ 'maxage' => $wgSquidMaxage,
+ 'usemsgcache' => 'yes',
+ 'ctype' => 'text/css',
+ 'smaxage' => $wgSquidMaxage,
+ );
+ }
+
+ /**
* Add skin specific stylesheets
* @param $out OutputPage
*/
function setupSkinUserCss( OutputPage $out ) {
- $out->addStyle( 'common/shared.css' );
- $out->addStyle( 'common/oldshared.css' );
+ $out->addModuleStyles( 'mediawiki.legacy.shared' );
+ $out->addModuleStyles( 'mediawiki.legacy.oldshared' );
+ // TODO: When converting old skins to use ResourceLoader (or removing them) the following should be reconsidered
$out->addStyle( $this->getStylesheet() );
$out->addStyle( 'common/common_rtl.css', '', '', 'rtl' );
}
function getPageClasses( $title ) {
$numeric = 'ns-' . $title->getNamespace();
- if( $title->getNamespace() == NS_SPECIAL ) {
+
+ if ( $title->getNamespace() == NS_SPECIAL ) {
$type = 'ns-special';
- } elseif( $title->isTalkPage() ) {
+ } elseif ( $title->isTalkPage() ) {
$type = 'ns-talk';
} else {
$type = 'ns-subject';
}
+
$name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() );
+
return "$numeric $type $name";
}
/**
+ * This will be called by OutputPage::headElement when it is creating the
+ * <body> tag, skins can override it if they have a need to add in any
+ * body attributes or classes of their own.
+ */
+ function addToBodyAttributes( $out, &$bodyAttrs ) {
+ // does nothing by default
+ }
+
+ /**
* URL to the logo
*/
function getLogo() {
@@ -708,7 +643,7 @@ CSS;
$qb = $this->qbSetting();
$langlinks = $this->otherLanguages();
- if( $langlinks ) {
+ if ( $langlinks ) {
$rows = 2;
$borderhack = '';
} else {
@@ -722,16 +657,18 @@ CSS;
$shove = ( $qb != 0 );
$left = ( $qb == 1 || $qb == 3 );
- if( $wgContLang->isRTL() ) {
+
+ if ( $wgContLang->isRTL() ) {
$left = !$left;
}
- if( !$shove ) {
+ if ( !$shove ) {
$s .= "<td class='top' align='left' valign='top' rowspan='{$rows}'>\n" .
$this->logoText() . '</td>';
- } elseif( $left ) {
+ } elseif ( $left ) {
$s .= $this->getQuickbarCompensator( $rows );
}
+
$l = $wgContLang->alignStart();
$s .= "<td {$borderhack} align='$l' valign='top'>\n";
@@ -750,16 +687,19 @@ CSS;
if ( $shove && !$left ) { # Right
$s .= $this->getQuickbarCompensator( $rows );
}
+
$s .= "</tr>\n</table>\n</div>\n";
$s .= "\n<div id='article'>\n";
$notice = wfGetSiteNotice();
- if( $notice ) {
+
+ if ( $notice ) {
$s .= "\n<div id='siteNotice'>$notice</div>\n";
}
$s .= $this->pageTitle();
$s .= $this->pageSubtitle();
$s .= $this->getCategories();
+
wfProfileOut( __METHOD__ );
return $s;
}
@@ -768,7 +708,7 @@ CSS;
global $wgOut, $wgUseCategoryBrowser;
global $wgContLang, $wgUser;
- if( count( $wgOut->mCategoryLinks ) == 0 ) {
+ if ( count( $wgOut->mCategoryLinks ) == 0 ) {
return '';
}
@@ -784,6 +724,7 @@ CSS;
$allCats = $wgOut->getCategoryLinks();
$s = '';
$colon = wfMsgExt( 'colon-separator', 'escapenoentities' );
+
if ( !empty( $allCats['normal'] ) ) {
$t = $embed . implode( "{$pop} {$sep} {$embed}" , $allCats['normal'] ) . $pop;
@@ -796,12 +737,13 @@ CSS;
# Hidden categories
if ( isset( $allCats['hidden'] ) ) {
if ( $wgUser->getBoolOption( 'showhiddencats' ) ) {
- $class ='mw-hidden-cats-user-shown';
+ $class = 'mw-hidden-cats-user-shown';
} elseif ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
$class = 'mw-hidden-cats-ns-shown';
} else {
$class = 'mw-hidden-cats-hidden';
}
+
$s .= "<div id=\"mw-hidden-catlinks\" class=\"$class\">" .
wfMsgExt( 'hidden-categories', array( 'parsemag', 'escapenoentities' ), count( $allCats['hidden'] ) ) .
$colon . $embed . implode( "$pop $sep $embed", $allCats['hidden'] ) . $pop .
@@ -810,14 +752,14 @@ CSS;
# optional 'dmoz-like' category browser. Will be shown under the list
# of categories an article belong to
- if( $wgUseCategoryBrowser ) {
+ if ( $wgUseCategoryBrowser ) {
$s .= '<br /><hr />';
# get a big array of the parents tree
$parenttree = $this->mTitle->getParentCategoryTree();
# Skin object passed by reference cause it can not be
# accessed under the method subfunction drawCategoryBrowser
- $tempout = explode( "\n", Skin::drawCategoryBrowser( $parenttree, $this ) );
+ $tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree, $this ) );
# Clean out bogus first entry and sort them
unset( $tempout[0] );
asort( $tempout );
@@ -836,18 +778,21 @@ CSS;
*/
function drawCategoryBrowser( $tree, &$skin ) {
$return = '';
- foreach( $tree as $element => $parent ) {
- if( empty( $parent ) ) {
+
+ foreach ( $tree as $element => $parent ) {
+ if ( empty( $parent ) ) {
# element start a new list
$return .= "\n";
} else {
# grab the others elements
- $return .= Skin::drawCategoryBrowser( $parent, $skin ) . ' &gt; ';
+ $return .= $this->drawCategoryBrowser( $parent, $skin ) . ' &gt; ';
}
+
# add our current element to the list
$eltitle = Title::newFromText( $element );
$return .= $skin->link( $eltitle, $eltitle->getText() );
}
+
return $return;
}
@@ -856,13 +801,14 @@ CSS;
$classes = 'catlinks';
- // Check what we're showing
global $wgOut, $wgUser;
+
+ // Check what we're showing
$allCats = $wgOut->getCategoryLinks();
$showHidden = $wgUser->getBoolOption( 'showhiddencats' ) ||
$this->mTitle->getNamespace() == NS_CATEGORY;
- if( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) {
+ if ( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) {
$classes .= ' catlinks-allhidden';
}
@@ -870,7 +816,7 @@ CSS;
}
function getQuickbarCompensator( $rows = 1 ) {
- return "<td width='152' rowspan='{$rows}'>&nbsp;</td>";
+ return "<td width='152' rowspan='{$rows}'>&#160;</td>";
}
/**
@@ -890,10 +836,10 @@ CSS;
protected function afterContentHook() {
$data = '';
- if( wfRunHooks( 'SkinAfterContent', array( &$data ) ) ) {
+ if ( wfRunHooks( '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 ) != '' ) {
+ if ( trim( $data ) != '' ) {
// Doing this here instead of in the skins to
// ensure that the div has the same ID in all
// skins
@@ -915,11 +861,13 @@ CSS;
*/
protected function generateDebugHTML() {
global $wgShowDebug, $wgOut;
+
if ( $wgShowDebug ) {
$listInternals = $this->formatDebugHTML( $wgOut->mDebugtext );
return "\n<hr />\n<strong>Debug data:</strong><ul style=\"font-family:monospace;\" id=\"mw-debug-html\">" .
$listInternals . "</ul>\n";
}
+
return '';
}
@@ -927,8 +875,8 @@ CSS;
$lines = explode( "\n", $debugText );
$curIdent = 0;
$ret = '<li>';
- foreach( $lines as $line ) {
- $m = array();
+
+ foreach ( $lines as $line ) {
$display = ltrim( $line );
$ident = strlen( $line ) - strlen( $display );
$diff = $ident - $curIdent;
@@ -956,7 +904,9 @@ CSS;
$curIdent = $ident;
}
+
$ret .= str_repeat( '</li></ul>', $curIdent ) . '</li>';
+
return $ret;
}
@@ -971,11 +921,13 @@ CSS;
/**
* This gets called shortly before the </body> tag.
+ * @param $out OutputPage object
* @return String HTML-wrapped JS code to be put before </body>
*/
- function bottomScripts() {
- $bottomScriptText = "\n" . Html::inlineScript( 'if (window.runOnloadHook) runOnloadHook();' ) . "\n";
+ function bottomScripts( $out ) {
+ $bottomScriptText = "\n" . $out->getHeadScripts( $this );
wfRunHooks( 'SkinAfterBottomScripts', array( $this, &$bottomScriptText ) );
+
return $bottomScriptText;
}
@@ -1004,11 +956,14 @@ CSS;
$s[] = $this->printableLink();
$disclaimer = $this->disclaimerLink(); # may be empty
- if( $disclaimer ) {
+
+ if ( $disclaimer ) {
$s[] = $disclaimer;
}
+
$privacy = $this->privacyLink(); # may be empty too
- if( $privacy ) {
+
+ if ( $privacy ) {
$s[] = $privacy;
}
@@ -1016,13 +971,15 @@ CSS;
if ( $this->mTitle->getNamespace() == NS_FILE ) {
$name = $this->mTitle->getDBkey();
$image = wfFindFile( $this->mTitle );
- if( $image ) {
+
+ if ( $image ) {
$link = htmlspecialchars( $image->getURL() );
$style = $this->getInternalLinkAttributes( $link, $name );
$s[] = "<a href=\"{$link}\"{$style}>{$name}</a>";
}
}
}
+
if ( 'history' == $action || isset( $diff ) || isset( $oldid ) ) {
$s[] .= $this->link(
$this->mTitle,
@@ -1036,7 +993,7 @@ CSS;
if ( $wgUser->getNewtalk() ) {
# do not show "You have new messages" text when we are viewing our
# own talk page
- if( !$this->mTitle->equals( $wgUser->getTalkPage() ) ) {
+ if ( !$this->mTitle->equals( $wgUser->getTalkPage() ) ) {
$tl = $this->link(
$wgUser->getTalkPage(),
wfMsgHtml( 'newmessageslink' ),
@@ -1052,7 +1009,7 @@ CSS;
array( 'diff' => 'cur' ),
array( 'known', 'noclasses' )
);
- $s[] = '<strong>'. wfMsg( 'youhavenewmessages', $tl, $dl ) . '</strong>';
+ $s[] = '<strong>' . wfMsg( 'youhavenewmessages', $tl, $dl ) . '</strong>';
# disable caching
$wgOut->setSquidMaxage( 0 );
$wgOut->enableClientCache( false );
@@ -1060,26 +1017,30 @@ CSS;
}
$undelete = $this->getUndeleteLink();
- if( !empty( $undelete ) ) {
+
+ if ( !empty( $undelete ) ) {
$s[] = $undelete;
}
+
return $wgLang->pipeList( $s );
}
function getUndeleteLink() {
- global $wgUser, $wgContLang, $wgLang, $wgRequest;
+ global $wgUser, $wgLang, $wgRequest;
$action = $wgRequest->getVal( 'action', 'view' );
if ( $wgUser->isAllowed( 'deletedhistory' ) &&
( $this->mTitle->getArticleId() == 0 || $action == 'history' ) ) {
$n = $this->mTitle->isDeleted();
+
if ( $n ) {
if ( $wgUser->isAllowed( 'undelete' ) ) {
$msg = 'thisisdeleted';
} else {
$msg = 'viewdeleted';
}
+
return wfMsg(
$msg,
$this->link(
@@ -1092,6 +1053,7 @@ CSS;
);
}
}
+
return '';
}
@@ -1105,8 +1067,8 @@ CSS;
$s[] = "<a href=\"$printurl\" rel=\"alternate\">" . wfMsg( 'printableversion' ) . '</a>';
}
- if( $wgOut->isSyndicated() ) {
- foreach( $wgFeedClasses as $format => $class ) {
+ if ( $wgOut->isSyndicated() ) {
+ foreach ( $wgFeedClasses as $format => $class ) {
$feedurl = $wgRequest->escapeAppendQuery( "feed=$format" );
$s[] = "<a href=\"$feedurl\" rel=\"alternate\" type=\"application/{$format}+xml\""
. " class=\"feedlink\">" . wfMsgHtml( "feed-$format" ) . "</a>";
@@ -1129,36 +1091,43 @@ CSS;
global $wgOut;
$sub = $wgOut->getSubtitle();
+
if ( $sub == '' ) {
global $wgExtraSubtitle;
$sub = wfMsgExt( 'tagline', 'parsemag' ) . $wgExtraSubtitle;
}
+
$subpages = $this->subPageSubtitle();
$sub .= !empty( $subpages ) ? "</p><p class='subpages'>$subpages" : '';
$s = "<p class='subtitle'>{$sub}</p>\n";
+
return $s;
}
function subPageSubtitle() {
$subpages = '';
- if( !wfRunHooks( 'SkinSubPageSubtitle', array( &$subpages ) ) ) {
+
+ if ( !wfRunHooks( 'SkinSubPageSubtitle', array( &$subpages, $this ) ) ) {
return $subpages;
}
global $wgOut;
- if( $wgOut->isArticle() && MWNamespace::hasSubpages( $this->mTitle->getNamespace() ) ) {
+
+ if ( $wgOut->isArticle() && MWNamespace::hasSubpages( $this->mTitle->getNamespace() ) ) {
$ptext = $this->mTitle->getPrefixedText();
- if( preg_match( '/\//', $ptext ) ) {
+ if ( preg_match( '/\//', $ptext ) ) {
$links = explode( '/', $ptext );
array_pop( $links );
$c = 0;
$growinglink = '';
$display = '';
- foreach( $links as $link ) {
+
+ foreach ( $links as $link ) {
$growinglink .= $link;
$display .= $link;
$linkObj = Title::newFromText( $growinglink );
- if( is_object( $linkObj ) && $linkObj->exists() ) {
+
+ if ( is_object( $linkObj ) && $linkObj->exists() ) {
$getlink = $this->link(
$linkObj,
htmlspecialchars( $display ),
@@ -1166,12 +1135,15 @@ CSS;
array(),
array( 'known', 'noclasses' )
);
+
$c++;
- if( $c > 1 ) {
+
+ if ( $c > 1 ) {
$subpages .= wfMsgExt( 'pipe-separator', 'escapenoentities' );
} else {
$subpages .= '&lt; ';
}
+
$subpages .= $getlink;
$display = '';
} else {
@@ -1181,6 +1153,7 @@ CSS;
}
}
}
+
return $subpages;
}
@@ -1198,8 +1171,9 @@ CSS;
$logoutPage = $wgContLang->specialPage( 'Userlogout' );
$ret = '';
+
if ( $wgUser->isAnon() ) {
- if( $this->showIPinHeader() ) {
+ if ( $this->showIPinHeader() ) {
$name = wfGetIP();
$talkLink = $this->link( $wgUser->getTalkPage(),
@@ -1212,6 +1186,7 @@ CSS;
$returnTo = $this->mTitle->getPrefixedDBkey();
$query = array();
+
if ( $logoutPage != $returnTo ) {
$query['returnto'] = $returnTo;
}
@@ -1236,9 +1211,10 @@ CSS;
SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ),
array(), array( 'returnto' => $returnTo )
),
- $this->specialLink( 'preferences' ),
+ $this->specialLink( 'Preferences' ),
) );
}
+
$ret = $wgLang->pipeList( array(
$ret,
$this->link(
@@ -1261,6 +1237,7 @@ CSS;
function searchForm() {
global $wgRequest, $wgUseTwoButtonsSearchForm;
+
$search = $wgRequest->getText( 'search' );
$s = '<form id="searchform' . $this->searchboxes . '" name="search" class="inline" method="post" action="'
@@ -1269,8 +1246,8 @@ CSS;
. htmlspecialchars( substr( $search, 0, 256 ) ) . "\" />\n"
. '<input type="submit" name="go" value="' . wfMsg( 'searcharticle' ) . '" />';
- if( $wgUseTwoButtonsSearchForm ) {
- $s .= '&nbsp;<input type="submit" name="fulltext" value="' . wfMsg( 'searchbutton' ) . "\" />\n";
+ if ( $wgUseTwoButtonsSearchForm ) {
+ $s .= '&#160;<input type="submit" name="fulltext" value="' . wfMsg( 'searchbutton' ) . "\" />\n";
} else {
$s .= ' <a href="' . $this->escapeSearchLink() . '" rel="search">' . wfMsg( 'powersearch-legend' ) . "</a>\n";
}
@@ -1288,25 +1265,26 @@ CSS;
$s = array(
$this->mainPageLink(),
- $this->specialLink( 'recentchanges' )
+ $this->specialLink( 'Recentchanges' )
);
if ( $wgOut->isArticleRelated() ) {
$s[] = $this->editThisPage();
$s[] = $this->historyLink();
}
+
# Many people don't like this dropdown box
- #$s[] = $this->specialPagesList();
+ # $s[] = $this->specialPagesList();
- if( $this->variantLinks() ) {
+ if ( $this->variantLinks() ) {
$s[] = $this->variantLinks();
}
- if( $this->extensionTabLinks() ) {
+ if ( $this->extensionTabLinks() ) {
$s[] = $this->extensionTabLinks();
}
- // FIXME: Is using Language::pipeList impossible here? Do not quite understand the use of the newline
+ // @todo FIXME: Is using Language::pipeList impossible here? Do not quite understand the use of the newline
return implode( $s, wfMsgExt( 'pipe-separator', 'escapenoentities' ) . "\n" );
}
@@ -1321,13 +1299,13 @@ CSS;
$out = '';
$s = array();
wfRunHooks( 'SkinTemplateTabs', array( $this, &$tabs ) );
- foreach( $tabs as $tab ) {
+ foreach ( $tabs as $tab ) {
$s[] = Xml::element( 'a',
array( 'href' => $tab['href'] ),
$tab['text'] );
}
- if( count( $s ) ) {
+ if ( count( $s ) ) {
global $wgLang;
$out = wfMsgExt( 'pipe-separator' , 'escapenoentities' );
@@ -1343,13 +1321,17 @@ CSS;
*/
function variantLinks() {
$s = '';
+
/* show links to different language variants */
global $wgDisableLangConversion, $wgLang, $wgContLang;
+
$variants = $wgContLang->getVariants();
- if( !$wgDisableLangConversion && sizeof( $variants ) > 1 ) {
- foreach( $variants as $code ) {
+
+ if ( !$wgDisableLangConversion && sizeof( $variants ) > 1 ) {
+ foreach ( $variants as $code ) {
$varname = $wgContLang->getVariantname( $code );
- if( $varname == 'disable' ) {
+
+ if ( $varname == 'disable' ) {
continue;
}
$s = $wgLang->pipeList( array(
@@ -1358,6 +1340,7 @@ CSS;
) );
}
}
+
return $s;
}
@@ -1368,31 +1351,33 @@ CSS;
$s = '';
if ( $wgOut->isArticleRelated() ) {
$element[] = '<strong>' . $this->editThisPage() . '</strong>';
+
if ( $wgUser->isLoggedIn() ) {
$element[] = $this->watchThisPage();
}
+
$element[] = $this->talkLink();
$element[] = $this->historyLink();
$element[] = $this->whatLinksHere();
$element[] = $this->watchPageLinksLink();
- if( $wgUseTrackbacks ) {
+ if ( $wgUseTrackbacks ) {
$element[] = $this->trackbackLink();
}
if (
$this->mTitle->getNamespace() == NS_USER ||
$this->mTitle->getNamespace() == NS_USER_TALK
- )
- {
+ ) {
$id = User::idFromName( $this->mTitle->getText() );
$ip = User::isIP( $this->mTitle->getText() );
# Both anons and non-anons have contributions list
- if( $id || $ip ) {
+ if ( $id || $ip ) {
$element[] = $this->userContribsLink();
}
- if( $this->showEmailUser( $id ) ) {
+
+ if ( $this->showEmailUser( $id ) ) {
$element[] = $this->emailUserLink();
}
}
@@ -1401,17 +1386,21 @@ CSS;
if ( $this->mTitle->getArticleId() ) {
$s .= "\n<br />";
+
// Delete/protect/move links for privileged users
- if( $wgUser->isAllowed( 'delete' ) ) {
+ if ( $wgUser->isAllowed( 'delete' ) ) {
$s .= $this->deleteThisPage();
}
- if( $wgUser->isAllowed( 'protect' ) ) {
+
+ if ( $wgUser->isAllowed( 'protect' ) ) {
$s .= $sep . $this->protectThisPage();
}
- if( $wgUser->isAllowed( 'move' ) ) {
+
+ if ( $wgUser->isAllowed( 'move' ) ) {
$s .= $sep . $this->moveThisPage();
}
}
+
$s .= "<br />\n" . $this->otherLanguages();
}
@@ -1424,34 +1413,40 @@ CSS;
$oldid = $wgRequest->getVal( 'oldid' );
$diff = $wgRequest->getVal( 'diff' );
+
if ( !$wgOut->isArticle() ) {
return '';
}
- if( !$wgArticle instanceof Article ) {
+
+ if ( !$wgArticle instanceof Article ) {
return '';
}
+
if ( isset( $oldid ) || isset( $diff ) ) {
return '';
}
+
if ( 0 == $wgArticle->getID() ) {
return '';
}
$s = '';
+
if ( !$wgDisableCounters ) {
$count = $wgLang->formatNum( $wgArticle->getCount() );
+
if ( $count ) {
$s = wfMsgExt( 'viewcount', array( 'parseinline' ), $count );
}
}
- if( $wgMaxCredits != 0 ) {
+ if ( $wgMaxCredits != 0 ) {
$s .= ' ' . Credits::getCredits( $wgArticle, $wgMaxCredits, $wgShowCreditsIfMax );
} else {
$s .= $this->lastModified();
}
- if( $wgPageShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' ) ) {
+ if ( $wgPageShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' ) ) {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
'watchlist',
@@ -1478,6 +1473,7 @@ CSS;
if ( $type == 'detect' ) {
$diff = $wgRequest->getVal( 'diff' );
$isCur = $wgArticle && $wgArticle->isCurrent();
+
if ( is_null( $diff ) && !$isCur && wfMsgForContent( 'history_copyright' ) !== '-' ) {
$type = 'history';
} else {
@@ -1492,43 +1488,58 @@ CSS;
}
$out = '';
- if( $wgRightsPage ) {
+
+ if ( $wgRightsPage ) {
$title = Title::newFromText( $wgRightsPage );
$link = $this->linkKnown( $title, $wgRightsText );
- } elseif( $wgRightsUrl ) {
+ } elseif ( $wgRightsUrl ) {
$link = $this->makeExternalLink( $wgRightsUrl, $wgRightsText );
- } elseif( $wgRightsText ) {
+ } elseif ( $wgRightsText ) {
$link = $wgRightsText;
} else {
# Give up now
return $out;
}
+
// Allow for site and per-namespace customization of copyright notice.
- if( isset( $wgArticle ) ) {
- wfRunHooks( 'SkinCopyrightFooter', array( $wgArticle->getTitle(), $type, &$msg, &$link ) );
+ $forContent = true;
+
+ if ( isset( $wgArticle ) ) {
+ wfRunHooks( 'SkinCopyrightFooter', array( $wgArticle->getTitle(), $type, &$msg, &$link, &$forContent ) );
+ }
+
+ if ( $forContent ) {
+ $out .= wfMsgForContent( $msg, $link );
+ } else {
+ $out .= wfMsg( $msg, $link );
}
- $out .= wfMsgForContent( $msg, $link );
return $out;
}
function getCopyrightIcon() {
global $wgRightsUrl, $wgRightsText, $wgRightsIcon, $wgCopyrightIcon;
+
$out = '';
+
if ( isset( $wgCopyrightIcon ) && $wgCopyrightIcon ) {
$out = $wgCopyrightIcon;
} elseif ( $wgRightsIcon ) {
$icon = htmlspecialchars( $wgRightsIcon );
+
if ( $wgRightsUrl ) {
$url = htmlspecialchars( $wgRightsUrl );
- $out .= '<a href="'.$url.'">';
+ $out .= '<a href="' . $url . '">';
}
+
$text = htmlspecialchars( $wgRightsText );
$out .= "<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />";
+
if ( $wgRightsUrl ) {
$out .= '</a>';
}
}
+
return $out;
}
@@ -1538,18 +1549,22 @@ CSS;
*/
function getPoweredBy() {
global $wgStylePath;
+
$url = htmlspecialchars( "$wgStylePath/common/images/poweredby_mediawiki_88x31.png" );
- $img = '<a href="http://www.mediawiki.org/"><img src="' . $url . '" height="31" width="88" alt="Powered by MediaWiki" /></a>';
- return $img;
+ $text = '<a href="http://www.mediawiki.org/"><img src="' . $url . '" height="31" width="88" alt="Powered by MediaWiki" /></a>';
+ wfRunHooks( 'SkinGetPoweredBy', array( &$text, $this ) );
+ return $text;
}
function lastModified() {
global $wgLang, $wgArticle;
- if( $this->mRevisionId && $this->mRevisionId != $wgArticle->getLatest() ) {
+
+ if ( $this->mRevisionId && $this->mRevisionId != $wgArticle->getLatest() ) {
$timestamp = Revision::getTimestampFromId( $wgArticle->getTitle(), $this->mRevisionId );
} else {
$timestamp = $wgArticle->getTimestamp();
}
+
if ( $timestamp ) {
$d = $wgLang->date( $timestamp, true );
$t = $wgLang->time( $timestamp, true );
@@ -1557,9 +1572,11 @@ CSS;
} else {
$s = '';
}
+
if ( wfGetLB()->getLaggedSlaveMode() ) {
$s .= ' <strong>' . wfMsg( 'laggedslavemode' ) . '</strong>';
}
+
return $s;
}
@@ -1576,6 +1593,7 @@ CSS;
$logourl = $this->getLogo();
$s = "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
+
return $s;
}
@@ -1583,8 +1601,10 @@ CSS;
* Show a drop-down box of special pages
*/
function specialPagesList() {
- global $wgUser, $wgContLang, $wgServer, $wgRedirectScript;
+ global $wgContLang, $wgServer, $wgRedirectScript;
+
$pages = array_merge( SpecialPage::getRegularPages(), SpecialPage::getRestrictedPages() );
+
foreach ( $pages as $name => $page ) {
$pages[$name] = $page->getDescription();
}
@@ -1603,13 +1623,38 @@ CSS;
$p = $wgContLang->specialPage( $name );
$s .= "<option value=\"{$p}\">{$desc}</option>\n";
}
+
$s .= "</select>\n";
$s .= "<input type='submit' value=\"{$go}\" name='redirect' />\n";
$s .= "</form>\n";
+
return $s;
}
/**
+ * Renders a $wgFooterIcons icon acording to the method's arguments
+ * @param $icon Array: The icon to build the html for, see $wgFooterIcons for the format of this array
+ * @param $withImage Boolean: Whether to use the icon's image or output a text-only footericon
+ */
+ function makeFooterIcon( $icon, $withImage = 'withImage' ) {
+ if ( is_string( $icon ) ) {
+ $html = $icon;
+ } else { // Assuming array
+ $url = isset($icon["url"]) ? $icon["url"] : null;
+ unset( $icon["url"] );
+ if ( isset( $icon["src"] ) && $withImage === 'withImage' ) {
+ $html = Html::element( 'img', $icon ); // do this the lazy way, just pass icon data as an attribute array
+ } else {
+ $html = htmlspecialchars( $icon["alt"] );
+ }
+ if ( $url ) {
+ $html = Html::rawElement( 'a', array( "href" => $url ), $html );
+ }
+ }
+ return $html;
+ }
+
+ /**
* Gets the link to the wiki's main page.
* @return string
*/
@@ -1621,12 +1666,13 @@ CSS;
array(),
array( 'known', 'noclasses' )
);
+
return $s;
}
- private function footerLink( $desc, $page ) {
+ public function footerLink( $desc, $page ) {
// if the link description has been set to "-" in the default language,
- if ( wfMsgForContent( $desc ) == '-') {
+ if ( wfMsgForContent( $desc ) == '-' ) {
// then it is disabled, for all languages.
return '';
} else {
@@ -1634,6 +1680,7 @@ CSS;
// language (which may or may not be the same as the default language),
// but we make the link target be the one site-wide page.
$title = Title::newFromText( wfMsgForContent( $page ) );
+
return $this->linkKnown(
$title,
wfMsgExt( $desc, array( 'parsemag', 'escapenoentities' ) )
@@ -1668,9 +1715,9 @@ CSS;
if ( !$wgOut->isArticleRelated() ) {
$s = wfMsg( 'protectedpage' );
} else {
- if( $this->mTitle->quickUserCan( 'edit' ) && $this->mTitle->exists() ) {
+ if ( $this->mTitle->quickUserCan( 'edit' ) && $this->mTitle->exists() ) {
$t = wfMsg( 'editthispage' );
- } elseif( $this->mTitle->quickUserCan( 'create' ) && !$this->mTitle->exists() ) {
+ } elseif ( $this->mTitle->quickUserCan( 'create' ) && !$this->mTitle->exists() ) {
$t = wfMsg( 'create-this-page' );
} else {
$t = wfMsg( 'viewsource' );
@@ -1684,6 +1731,7 @@ CSS;
array( 'known', 'noclasses' )
);
}
+
return $s;
}
@@ -1699,7 +1747,7 @@ CSS;
$options = array( 'action' => 'edit' );
- if( $this->mRevisionId && ! $wgArticle->isCurrent() ) {
+ if ( $this->mRevisionId && ! $wgArticle->isCurrent() ) {
$options['oldid'] = intval( $this->mRevisionId );
}
@@ -1710,6 +1758,7 @@ CSS;
global $wgUser, $wgRequest;
$diff = $wgRequest->getVal( 'diff' );
+
if ( $this->mTitle->getArticleId() && ( !$diff ) && $wgUser->isAllowed( 'delete' ) ) {
$t = wfMsg( 'deletethispage' );
@@ -1723,6 +1772,7 @@ CSS;
} else {
$s = '';
}
+
return $s;
}
@@ -1730,7 +1780,8 @@ CSS;
global $wgUser, $wgRequest;
$diff = $wgRequest->getVal( 'diff' );
- if ( $this->mTitle->getArticleId() && ( ! $diff ) && $wgUser->isAllowed('protect') ) {
+
+ if ( $this->mTitle->getArticleId() && ( ! $diff ) && $wgUser->isAllowed( 'protect' ) ) {
if ( $this->mTitle->isProtected() ) {
$text = wfMsg( 'unprotectthispage' );
$query = array( 'action' => 'unprotect' );
@@ -1749,6 +1800,7 @@ CSS;
} else {
$s = '';
}
+
return $s;
}
@@ -1777,6 +1829,7 @@ CSS;
} else {
$s = wfMsg( 'notanarticle' );
}
+
return $s;
}
@@ -1843,6 +1896,7 @@ CSS;
function watchPageLinksLink() {
global $wgOut;
+
if ( !$wgOut->isArticleRelated() ) {
return '(' . wfMsg( 'notanarticle' ) . ')';
} else {
@@ -1869,34 +1923,42 @@ CSS;
}
$a = $wgOut->getLanguageLinks();
+
if ( 0 == count( $a ) ) {
return '';
}
$s = wfMsg( 'otherlanguages' ) . wfMsg( 'colon-separator' );
$first = true;
- if( $wgContLang->isRTL() ) {
+
+ if ( $wgContLang->isRTL() ) {
$s .= '<span dir="LTR">';
}
- foreach( $a as $l ) {
+
+ foreach ( $a as $l ) {
if ( !$first ) {
$s .= wfMsgExt( 'pipe-separator', 'escapenoentities' );
}
+
$first = false;
$nt = Title::newFromText( $l );
$url = $nt->escapeFullURL();
$text = $wgContLang->getLanguageName( $nt->getInterwiki() );
+ $title = htmlspecialchars( $nt->getText() );
if ( $text == '' ) {
$text = $l;
}
+
$style = $this->getExternalLinkAttributes();
- $s .= "<a href=\"{$url}\"{$style}>{$text}</a>";
+ $s .= "<a href=\"{$url}\" title=\"{$title}\"{$style}>{$text}</a>";
}
- if( $wgContLang->isRTL() ) {
+
+ if ( $wgContLang->isRTL() ) {
$s .= '</span>';
}
+
return $s;
}
@@ -1908,7 +1970,7 @@ CSS;
$linkOptions = array();
- if( $this->mTitle->isTalkPage() ) {
+ if ( $this->mTitle->isTalkPage() ) {
$link = $this->mTitle->getSubjectPage();
switch( $link->getNamespace() ) {
case NS_MAIN:
@@ -1923,7 +1985,7 @@ CSS;
case NS_FILE:
$text = wfMsg( 'imagepage' );
# Make link known if image exists, even if the desc. page doesn't.
- if( wfFindFile( $link ) )
+ if ( wfFindFile( $link ) )
$linkOptions[] = 'known';
break;
case NS_MEDIAWIKI:
@@ -1961,9 +2023,9 @@ CSS;
# __NEWSECTIONLINK___ changes behaviour here
# If it is present, the link points to this page, otherwise
# it points to the talk page
- if( $this->mTitle->isTalkPage() ) {
+ if ( $this->mTitle->isTalkPage() ) {
$title = $this->mTitle;
- } elseif( $wgOut->showNewSectionLink() ) {
+ } elseif ( $wgOut->showNewSectionLink() ) {
$title = $this->mTitle;
} else {
$title = $this->mTitle->getTalkPage();
@@ -1981,10 +2043,52 @@ CSS;
);
}
+ function getUploadLink() {
+ global $wgUploadNavigationUrl;
+
+ if ( $wgUploadNavigationUrl ) {
+ # Using an empty class attribute to avoid automatic setting of "external" class
+ return $this->makeExternalLink( $wgUploadNavigationUrl, wfMsgHtml( 'upload' ), false, null, array( 'class' => '' ) );
+ } else {
+ return $this->link(
+ SpecialPage::getTitleFor( 'Upload' ),
+ wfMsgHtml( 'upload' ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ );
+ }
+ }
+
+ /**
+ * Return a fully resolved style path url to images or styles stored in the common folder.
+ * This method returns a url resolved using the configured skin style path
+ * and includes the style version inside of the url.
+ * @param $name String: The name or path of the common file to return the full path for.
+ * @return String The fully resolved style path url including styleversion
+ */
+ function getCommonStylePath( $name ) {
+ global $wgStylePath, $wgStyleVersion;
+ return "{$wgStylePath}/common/$name?{$wgStyleVersion}";
+ }
+
+ /**
+ * Return a fully resolved style path url to images or styles stored in the curent skins's folder.
+ * This method returns a url resolved using the configured skin style path
+ * and includes the style version inside of the url.
+ * @param $name String: The name or path of the skin resource file to return the full path for.
+ * @return String The fully resolved style path url including styleversion
+ */
+ function getSkinStylePath( $name ) {
+ global $wgStylePath, $wgStyleVersion;
+ return "{$wgStylePath}/{$this->stylename}/$name?{$wgStyleVersion}";
+ }
+
/* these are used extensively in SkinTemplate, but also some other places */
static function makeMainPageUrl( $urlaction = '' ) {
$title = Title::newMainPage();
self::checkTitle( $title, '' );
+
return $title->getLocalURL( $urlaction );
}
@@ -2007,6 +2111,7 @@ CSS;
static function makeUrl( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
self::checkTitle( $title, $name );
+
return $title->getLocalURL( $urlaction );
}
@@ -2026,6 +2131,7 @@ CSS;
static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
$title = Title::makeTitleSafe( $namespace, $name );
self::checkTitle( $title, $name );
+
return $title->getLocalURL( $urlaction );
}
@@ -2033,9 +2139,10 @@ CSS;
static function makeUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
self::checkTitle( $title, $name );
+
return array(
'href' => $title->getLocalURL( $urlaction ),
- 'exists' => $title->getArticleID() != 0 ? true : false
+ 'exists' => $title->getArticleID() != 0,
);
}
@@ -2045,6 +2152,7 @@ CSS;
static function makeKnownUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
self::checkTitle( $title, $name );
+
return array(
'href' => $title->getLocalURL( $urlaction ),
'exists' => true
@@ -2053,9 +2161,9 @@ CSS;
# make sure we have some title to operate on
static function checkTitle( &$title, $name ) {
- if( !is_object( $title ) ) {
+ if ( !is_object( $title ) ) {
$title = Title::newFromText( $name );
- if( !is_object( $title ) ) {
+ if ( !is_object( $title ) ) {
$title = Title::newFromText( '--error: link target missing--' );
}
}
@@ -2088,6 +2196,7 @@ CSS;
if ( $wgEnableSidebarCache ) {
$parserMemc->set( $key, $bar, $wgSidebarCacheExpiry );
}
+
wfProfileOut( __METHOD__ );
return $bar;
}
@@ -2095,34 +2204,55 @@ CSS;
* Add content from a sidebar system message
* Currently only used for MediaWiki:Sidebar (but may be used by Extensions)
*
+ * This is just a wrapper around addToSidebarPlain() for backwards compatibility
+ *
* @param &$bar array
* @param $message String
*/
function addToSidebar( &$bar, $message ) {
- $lines = explode( "\n", wfMsgForContent( $message ) );
+ $this->addToSidebarPlain( $bar, wfMsgForContent( $message ) );
+ }
+
+ /**
+ * Add content from plain text
+ * @since 1.17
+ * @param &$bar array
+ * @param $text string
+ */
+ function addToSidebarPlain( &$bar, $text ) {
+ $lines = explode( "\n", $text );
+ $wikiBar = array(); # We need to handle the wikitext on a different variable, to avoid trying to do an array operation on text, which would be a fatal error.
+
$heading = '';
- foreach( $lines as $line ) {
- if( strpos( $line, '*' ) !== 0 ) {
+
+ foreach ( $lines as $line ) {
+ if ( strpos( $line, '*' ) !== 0 ) {
continue;
}
- if( strpos( $line, '**') !== 0 ) {
+
+ if ( strpos( $line, '**' ) !== 0 ) {
$heading = trim( $line, '* ' );
- if( !array_key_exists( $heading, $bar ) ) {
+ if ( !array_key_exists( $heading, $bar ) ) {
$bar[$heading] = array();
}
} else {
- if( strpos( $line, '|' ) !== false ) { // sanity check
- $line = array_map( 'trim', explode( '|', trim( $line, '* ' ), 2 ) );
+ $line = trim( $line, '* ' );
+
+ if ( strpos( $line, '|' ) !== false ) { // sanity check
+ $line = array_map( 'trim', explode( '|', $line, 2 ) );
$link = wfMsgForContent( $line[0] );
- if( $link == '-' ) {
+
+ if ( $link == '-' ) {
continue;
}
$text = wfMsgExt( $line[1], 'parsemag' );
- if( wfEmptyMsg( $line[1], $text ) ) {
+
+ if ( wfEmptyMsg( $line[1], $text ) ) {
$text = $line[1];
}
- if( wfEmptyMsg( $line[0], $link ) ) {
+
+ if ( wfEmptyMsg( $line[0], $link ) ) {
$link = $line[0];
}
@@ -2130,6 +2260,7 @@ CSS;
$href = $link;
} else {
$title = Title::newFromText( $link );
+
if ( $title ) {
$title = $title->fixSpecialName();
$href = $title->getLocalURL();
@@ -2144,11 +2275,26 @@ CSS;
'id' => 'n-' . strtr( $line[1], ' ', '-' ),
'active' => false
);
+ } else if ( ( substr( $line, 0, 2 ) == '{{' ) && ( substr( $line, -2 ) == '}}' ) ) {
+ global $wgParser, $wgTitle;
+
+ $line = substr( $line, 2, strlen( $line ) - 4 );
+
+ $options = new ParserOptions();
+ $options->setEditSection( false );
+ $options->setInterfaceMessage( true );
+ $wikiBar[$heading] = $wgParser->parse( wfMsgForContentNoTrans( $line ) , $wgTitle, $options )->getText();
} else {
continue;
}
}
}
+
+ if ( count( $wikiBar ) > 0 ) {
+ $bar = array_merge( $bar, $wikiBar );
+ }
+
+ return $bar;
}
/**
@@ -2162,4 +2308,62 @@ CSS;
public function commonPrintStylesheet() {
return true;
}
+
+ /**
+ * Gets new talk page messages for the current user.
+ * @return MediaWiki message or if no new talk page messages, nothing
+ */
+ function getNewtalks() {
+ global $wgUser, $wgOut;
+
+ $newtalks = $wgUser->getNewMessageLinks();
+ $ntl = '';
+
+ if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
+ $userTitle = $this->mUser->getUserPage();
+ $userTalkTitle = $userTitle->getTalkPage();
+
+ if ( !$userTalkTitle->equals( $this->mTitle ) ) {
+ $newMessagesLink = $this->link(
+ $userTalkTitle,
+ wfMsgHtml( 'newmessageslink' ),
+ array(),
+ array( 'redirect' => 'no' ),
+ array( 'known', 'noclasses' )
+ );
+
+ $newMessagesDiffLink = $this->link(
+ $userTalkTitle,
+ wfMsgHtml( 'newmessagesdifflink' ),
+ array(),
+ array( 'diff' => 'cur' ),
+ array( 'known', 'noclasses' )
+ );
+
+ $ntl = wfMsg(
+ 'youhavenewmessages',
+ $newMessagesLink,
+ $newMessagesDiffLink
+ );
+ # Disable Squid cache
+ $wgOut->setSquidMaxage( 0 );
+ }
+ } elseif ( count( $newtalks ) ) {
+ // _>" " for BC <= 1.16
+ $sep = str_replace( '_', ' ', wfMsgHtml( 'newtalkseparator' ) );
+ $msgs = array();
+
+ foreach ( $newtalks as $newtalk ) {
+ $msgs[] = Xml::element(
+ 'a',
+ array( 'href' => $newtalk['link'] ), $newtalk['wiki']
+ );
+ }
+ $parts = implode( $sep, $msgs );
+ $ntl = wfMsgHtml( 'youhavenewmessagesmulti', $parts );
+ $wgOut->setSquidMaxage( 0 );
+ }
+
+ return $ntl;
+ }
}
diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php
index e5fdb274..cfb67250 100644
--- a/includes/SkinTemplate.php
+++ b/includes/SkinTemplate.php
@@ -1,22 +1,28 @@
<?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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
if ( ! defined( 'MEDIAWIKI' ) )
die( 1 );
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
* Wrapper object for MediaWiki's localization functions,
* to be passed to the template engine.
@@ -101,8 +107,7 @@ class SkinTemplate extends Skin {
* @param $out OutputPage
*/
function setupSkinUserCss( OutputPage $out ){
- $out->addStyle( 'common/shared.css', 'screen' );
- $out->addStyle( 'common/commonPrint.css', 'print' );
+ $out->addModuleStyles( array( 'mediawiki.legacy.shared', 'mediawiki.legacy.commonPrint' ) );
}
/**
@@ -110,7 +115,7 @@ class SkinTemplate extends Skin {
* and eventually it spits out some HTML. Should have interface
* roughly equivalent to PHPTAL 0.7.
*
- * @param $callback string (or file)
+ * @param $classname string (or file)
* @param $repository string: subdirectory where we keep template files
* @param $cache_dir string
* @return object
@@ -127,16 +132,19 @@ class SkinTemplate extends Skin {
*/
function outputPage( OutputPage $out ) {
global $wgArticle, $wgUser, $wgLang, $wgContLang;
- global $wgScript, $wgStylePath, $wgContLanguageCode;
+ global $wgScript, $wgStylePath, $wgLanguageCode;
global $wgMimeType, $wgJsMimeType, $wgOutputEncoding, $wgRequest;
global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces, $wgHtml5Version;
global $wgDisableCounters, $wgLogo, $wgHideInterlanguageLinks;
global $wgMaxCredits, $wgShowCreditsIfMax;
global $wgPageShowWatchingUsers;
global $wgUseTrackbacks, $wgUseSiteJs, $wgDebugComments;
- global $wgArticlePath, $wgScriptPath, $wgServer;
+ global $wgArticlePath, $wgScriptPath, $wgServer, $wgProfiler;
wfProfileIn( __METHOD__ );
+ if ( is_object( $wgProfiler ) ) {
+ $wgProfiler->setTemplated( true );
+ }
$oldid = $wgRequest->getVal( 'oldid' );
$diff = $wgRequest->getVal( 'diff' );
@@ -209,8 +217,8 @@ class SkinTemplate extends Skin {
$tpl->setRef( 'xhtmldefaultnamespace', $wgXhtmlDefaultNamespace );
$tpl->set( 'xhtmlnamespaces', $wgXhtmlNamespaces );
$tpl->set( 'html5version', $wgHtml5Version );
- $tpl->set( 'headlinks', $out->getHeadLinks() );
- $tpl->set( 'csslinks', $out->buildCssLinks() );
+ $tpl->set( 'headlinks', $out->getHeadLinks( $this ) );
+ $tpl->set( 'csslinks', $out->buildCssLinks( $this ) );
if( $wgUseTrackbacks && $out->isArticleRelated() ) {
$tpl->set( 'trackbackhtml', $out->getTitle()->trackbackRDF() );
@@ -294,11 +302,13 @@ class SkinTemplate extends Skin {
$tpl->setRef( 'scriptpath', $wgScriptPath );
$tpl->setRef( 'serverurl', $wgServer );
$tpl->setRef( 'logopath', $wgLogo );
- $tpl->setRef( 'lang', $wgContLanguageCode );
- $tpl->set( 'dir', $wgContLang->getDir() );
- $tpl->set( 'rtl', $wgContLang->isRTL() );
+
+ $lang = wfUILang();
+ $tpl->set( 'lang', $lang->getCode() );
+ $tpl->set( 'dir', $lang->getDir() );
+ $tpl->set( 'rtl', $lang->isRTL() );
+
$tpl->set( 'capitalizeallnouns', $wgLang->capitalizeAllNouns() ? ' capitalize-all-nouns' : '' );
- $tpl->set( 'langname', $wgContLang->getLanguageName( $wgContLanguageCode ) );
$tpl->set( 'showjumplinks', $wgUser->getOption( 'showjumplinks' ) );
$tpl->set( 'username', $wgUser->isAnon() ? null : $this->username );
$tpl->setRef( 'userpage', $this->userpage );
@@ -308,8 +318,8 @@ class SkinTemplate extends Skin {
// Users can have their language set differently than the
// content of the wiki. For these users, tell the web browser
// that interface elements are in a different language.
- $tpl->set( 'userlangattributes', '');
- $tpl->set( 'specialpageattributes', '');
+ $tpl->set( 'userlangattributes', '' );
+ $tpl->set( 'specialpageattributes', '' );
$lang = $wgLang->getCode();
$dir = $wgLang->getDir();
@@ -320,59 +330,17 @@ class SkinTemplate extends Skin {
// The content of SpecialPages should be presented in the
// user's language. Content of regular pages should not be touched.
- if($this->mTitle->isSpecialPage()) {
+ if( $this->mTitle->isSpecialPage() ) {
$tpl->set( 'specialpageattributes', $attrs );
}
}
- $newtalks = $wgUser->getNewMessageLinks();
- $ntl = '';
-
- if( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
- $usertitle = $this->mUser->getUserPage();
- $usertalktitle = $usertitle->getTalkPage();
-
- if( !$usertalktitle->equals( $this->mTitle ) ) {
- $newmessageslink = $this->link(
- $usertalktitle,
- wfMsgHtml( 'newmessageslink' ),
- array(),
- array( 'redirect' => 'no' ),
- array( 'known', 'noclasses' )
- );
-
- $newmessagesdifflink = $this->link(
- $usertalktitle,
- wfMsgHtml( 'newmessagesdifflink' ),
- array(),
- array( 'diff' => 'cur' ),
- array( 'known', 'noclasses' )
- );
+ $newtalks = $this->getNewtalks();
- $ntl = wfMsg(
- 'youhavenewmessages',
- $newmessageslink,
- $newmessagesdifflink
- );
- # Disable Cache
- $out->setSquidMaxage( 0 );
- }
- } else if( count( $newtalks ) ) {
- // _>" " for BC <= 1.16
- $sep = str_replace( '_', ' ', wfMsgHtml( 'newtalkseparator' ) );
- $msgs = array();
- foreach( $newtalks as $newtalk ) {
- $msgs[] = Xml::element('a',
- array( 'href' => $newtalk['link'] ), $newtalk['wiki'] );
- }
- $parts = implode( $sep, $msgs );
- $ntl = wfMsgHtml( 'youhavenewmessagesmulti', $parts );
- $out->setSquidMaxage( 0 );
- }
wfProfileOut( __METHOD__ . '-stuff2' );
wfProfileIn( __METHOD__ . '-stuff3' );
- $tpl->setRef( 'newtalk', $ntl );
+ $tpl->setRef( 'newtalk', $newtalks );
$tpl->setRef( 'skin', $this );
$tpl->set( 'logo', $this->logoText() );
if ( $out->isArticle() and ( !isset( $oldid ) or isset( $diff ) ) and
@@ -390,7 +358,6 @@ class SkinTemplate extends Skin {
if( $wgPageShowWatchingUsers ) {
$dbr = wfGetDB( DB_SLAVE );
- $watchlist = $dbr->tableName( 'watchlist' );
$res = $dbr->select( 'watchlist',
array( 'COUNT(*) AS n' ),
array( 'wl_title' => $dbr->strencode( $this->mTitle->getDBkey() ), 'wl_namespace' => $this->mTitle->getNamespace() ),
@@ -444,6 +411,36 @@ class SkinTemplate extends Skin {
$tpl->set( 'privacy', $this->privacyLink() );
$tpl->set( 'about', $this->aboutLink() );
+ $tpl->set( 'footerlinks', array(
+ 'info' => array(
+ 'lastmod',
+ 'viewcount',
+ 'numberofwatchingusers',
+ 'credits',
+ 'copyright',
+ ),
+ 'places' => array(
+ 'privacy',
+ 'about',
+ 'disclaimer',
+ ),
+ ) );
+
+ global $wgFooterIcons;
+ $tpl->set( 'footericons', $wgFooterIcons );
+ foreach ( $tpl->data["footericons"] as $footerIconsKey => &$footerIconsBlock ) {
+ if ( count($footerIconsBlock) > 0 ) {
+ foreach ( $footerIconsBlock as &$footerIcon ) {
+ if ( isset($footerIcon["src"]) ) {
+ if ( !isset($footerIcon["width"]) ) $footerIcon["width"] = 88;
+ if ( !isset($footerIcon["height"]) ) $footerIcon["height"] = 31;
+ }
+ }
+ } else {
+ unset($tpl->data["footericons"][$footerIconsKey]);
+ }
+ }
+
if ( $wgDebugComments ) {
$tpl->setRef( 'debug', $out->mDebugtext );
} else {
@@ -452,9 +449,14 @@ class SkinTemplate extends Skin {
$tpl->set( 'reporttime', wfReportTime() );
$tpl->set( 'sitenotice', wfGetSiteNotice() );
- $tpl->set( 'bottomscripts', $this->bottomScripts() );
+ $tpl->set( 'bottomscripts', $this->bottomScripts( $out ) );
$printfooter = "<div class=\"printfooter\">\n" . $this->printSource() . "</div>\n";
+ global $wgBetterDirectionality;
+ if ( $wgBetterDirectionality ) {
+ $realBodyAttribs = array( 'lang' => $wgLanguageCode, 'dir' => $wgContLang->getDir() );
+ $out->mBodytext = Html::rawElement( 'div', $realBodyAttribs, $out->mBodytext );
+ }
$out->mBodytext .= $printfooter . $this->generateDebugHTML();
$tpl->setRef( 'bodytext', $out->mBodytext );
@@ -472,6 +474,7 @@ class SkinTemplate extends Skin {
'href' => $nt->getFullURL(),
'text' => ( $wgContLang->getLanguageName( $nt->getInterwiki() ) != '' ?
$wgContLang->getLanguageName( $nt->getInterwiki() ) : $l ),
+ 'title' => $nt->getText(),
'class' => $class
);
}
@@ -525,7 +528,7 @@ class SkinTemplate extends Skin {
* an error object of the appropriate type.
* For the base class, assume strings all around.
*
- * @param mixed $str
+ * @param $str Mixed
* @private
*/
function printOrError( $str ) {
@@ -603,7 +606,9 @@ class SkinTemplate extends Skin {
$personal_urls['logout'] = array(
'text' => wfMsg( 'userlogout' ),
'href' => self::makeSpecialUrl( 'Userlogout',
- $title->isSpecial( 'Preferences' ) ? '' : $returnto
+ // userlogout link must always contain an & character, otherwise we might not be able
+ // to detect a buggy precaching proxy (bug 17790)
+ $title->isSpecial( 'Preferences' ) ? 'noreturnto' : $returnto
),
'active' => false
);
@@ -612,6 +617,21 @@ class SkinTemplate extends Skin {
$loginlink = $wgUser->isAllowed( 'createaccount' )
? 'nav-login-createaccount'
: 'login';
+
+ # anonlogin & login are the same
+ $login_url = array(
+ 'text' => wfMsg( $loginlink ),
+ 'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
+ 'active' => $title->isSpecial( 'Userlogin' )
+ );
+ global $wgProto, $wgSecureLogin;
+ if( $wgProto === 'http' && $wgSecureLogin ) {
+ $title = SpecialPage::getTitleFor( 'Userlogin' );
+ $https_url = preg_replace( '/^http:/', 'https:', $title->getFullURL() );
+ $login_url['href'] = $https_url;
+ $login_url['class'] = 'link-https'; # FIXME class depends on skin
+ }
+
if( $this->showIPinHeader() ) {
$href = &$this->userpageUrlDetails['href'];
$personal_urls['anonuserpage'] = array(
@@ -628,17 +648,9 @@ class SkinTemplate extends Skin {
'class' => $usertalkUrlDetails['exists'] ? false : 'new',
'active' => ( $pageurl == $href )
);
- $personal_urls['anonlogin'] = array(
- 'text' => wfMsg( $loginlink ),
- 'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
- 'active' => $title->isSpecial( 'Userlogin' )
- );
+ $personal_urls['anonlogin'] = $login_url;
} else {
- $personal_urls['login'] = array(
- 'text' => wfMsg( $loginlink ),
- 'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
- 'active' => $title->isSpecial( 'Userlogin' )
- );
+ $personal_urls['login'] = $login_url;
}
}
@@ -685,7 +697,7 @@ class SkinTemplate extends Skin {
self::checkTitle( $title, $name );
return array(
'href' => $title->getLocalURL( $urlaction ),
- 'exists' => $title->getArticleID() != 0 ? true : false
+ 'exists' => $title->getArticleID() != 0,
);
}
@@ -695,7 +707,7 @@ class SkinTemplate extends Skin {
self::checkTitle( $title, $name );
return array(
'href' => $title->getLocalURL( $urlaction ),
- 'exists' => $title->getArticleID() != 0 ? true : false
+ 'exists' => $title->getArticleID() != 0,
);
}
@@ -712,6 +724,7 @@ class SkinTemplate extends Skin {
$action = $wgRequest->getVal( 'action', 'view' );
$section = $wgRequest->getVal( 'section' );
$content_actions = array();
+ $userCanRead = $this->mTitle->userCanRead();
$prevent_active_tabs = false;
wfRunHooks( 'SkinTemplatePreventOtherActiveTabs', array( &$this, &$prevent_active_tabs ) );
@@ -725,7 +738,7 @@ class SkinTemplate extends Skin {
$subjpage,
$nskey,
!$this->mTitle->isTalkPage() && !$prevent_active_tabs,
- '', true
+ '', $userCanRead
);
$content_actions['talk'] = $this->tabAction(
@@ -733,16 +746,16 @@ class SkinTemplate extends Skin {
'talk',
$this->mTitle->isTalkPage() && !$prevent_active_tabs,
'',
- true
+ $userCanRead
);
wfProfileIn( __METHOD__ . '-edit' );
- if ( $this->mTitle->quickUserCan( 'edit' ) && ( $this->mTitle->exists() || $this->mTitle->quickUserCan( 'create' ) ) ) {
+ if ( $userCanRead && $this->mTitle->quickUserCan( 'edit' ) && ( $this->mTitle->exists() || $this->mTitle->quickUserCan( 'create' ) ) ) {
$istalk = $this->mTitle->isTalkPage();
$istalkclass = $istalk?' istalk':'';
$content_actions['edit'] = array(
'class' => ( ( ( $action == 'edit' or $action == 'submit' ) and $section != 'new' ) ? 'selected' : '' ) . $istalkclass,
- 'text' => $this->mTitle->exists()
+ 'text' => ( $this->mTitle->exists() || ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !wfEmptyMsg( $this->mTitle->getText() ) ) )
? wfMsg( 'edit' )
: wfMsg( 'create' ),
'href' => $this->mTitle->getLocalUrl( $this->editUrlOptions() )
@@ -758,7 +771,7 @@ class SkinTemplate extends Skin {
);
}
}
- } elseif ( $this->mTitle->isKnown() ) {
+ } elseif ( $this->mTitle->hasSourceText() && $userCanRead ) {
$content_actions['viewsource'] = array(
'class' => ($action == 'edit') ? 'selected' : false,
'text' => wfMsg( 'viewsource' ),
@@ -768,7 +781,7 @@ class SkinTemplate extends Skin {
wfProfileOut( __METHOD__ . '-edit' );
wfProfileIn( __METHOD__ . '-live' );
- if ( $this->mTitle->exists() ) {
+ if ( $this->mTitle->exists() && $userCanRead ) {
$content_actions['history'] = array(
'class' => ($action == 'history') ? 'selected' : false,
@@ -812,7 +825,8 @@ class SkinTemplate extends Skin {
} else {
//article doesn't exist or is deleted
if( $wgUser->isAllowed( 'deletedhistory' ) && $wgUser->isAllowed( 'deletedtext' ) ) {
- if( $n = $this->mTitle->isDeleted() ) {
+ $n = $this->mTitle->isDeleted();
+ if( $n ) {
$undelTitle = SpecialPage::getTitleFor( 'Undelete' );
$content_actions['undelete'] = array(
'class' => false,
@@ -906,7 +920,7 @@ class SkinTemplate extends Skin {
*/
function buildNavUrls() {
global $wgUseTrackbacks, $wgOut, $wgUser, $wgRequest;
- global $wgEnableUploads, $wgUploadNavigationUrl;
+ global $wgUploadNavigationUrl;
wfProfileIn( __METHOD__ );
@@ -916,7 +930,7 @@ class SkinTemplate extends Skin {
$nav_urls['mainpage'] = array( 'href' => self::makeMainPageUrl() );
if( $wgUploadNavigationUrl ) {
$nav_urls['upload'] = array( 'href' => $wgUploadNavigationUrl );
- } elseif( $wgEnableUploads && $wgUser->isAllowed( 'upload' ) ) {
+ } elseif( UploadBase::isEnabled() && UploadBase::isAllowed( $wgUser ) === true ) {
$nav_urls['upload'] = array( 'href' => self::makeSpecialUrl( 'Upload' ) );
} else {
$nav_urls['upload'] = false;
@@ -969,8 +983,9 @@ class SkinTemplate extends Skin {
}
if( $this->mTitle->getNamespace() == NS_USER || $this->mTitle->getNamespace() == NS_USER_TALK ) {
- $id = User::idFromName( $this->mTitle->getText() );
- $ip = User::isIP( $this->mTitle->getText() );
+ $rootUser = strtok( $this->mTitle->getText(), '/' );
+ $id = User::idFromName( $rootUser );
+ $ip = User::isIP( $rootUser );
} else {
$id = 0;
$ip = false;
@@ -978,7 +993,7 @@ class SkinTemplate extends Skin {
if( $id || $ip ) { # both anons and non-anons have contribs list
$nav_urls['contributions'] = array(
- 'href' => self::makeSpecialUrlSubpage( 'Contributions', $this->mTitle->getText() )
+ 'href' => self::makeSpecialUrlSubpage( 'Contributions', $rootUser )
);
if( $id ) {
@@ -986,7 +1001,7 @@ class SkinTemplate extends Skin {
$nav_urls['log'] = array(
'href' => $logPage->getLocalUrl(
array(
- 'user' => $this->mTitle->getText()
+ 'user' => $rootUser
)
)
);
@@ -996,7 +1011,7 @@ class SkinTemplate extends Skin {
if ( $wgUser->isAllowed( 'block' ) ) {
$nav_urls['blockip'] = array(
- 'href' => self::makeSpecialUrlSubpage( 'Blockip', $this->mTitle->getText() )
+ 'href' => self::makeSpecialUrlSubpage( 'Blockip', $rootUser )
);
} else {
$nav_urls['blockip'] = false;
@@ -1009,7 +1024,7 @@ class SkinTemplate extends Skin {
$nav_urls['emailuser'] = false;
if( $this->showEmailUser( $id ) ) {
$nav_urls['emailuser'] = array(
- 'href' => self::makeSpecialUrlSubpage( 'Emailuser', $this->mTitle->getText() )
+ 'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser )
);
}
wfProfileOut( __METHOD__ );
diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php
index 80e2f7ed..12ad517a 100644
--- a/includes/SpecialPage.php
+++ b/includes/SpecialPage.php
@@ -70,6 +70,10 @@ class SpecialPage {
*/
var $mAllowedRedirectParams = array();
/**
+ * Query parameteres added by redirects
+ */
+ var $mAddedRedirectParams = array();
+ /**
* List of special pages, followed by parameters.
* If the only parameter is a string, that is the page name.
* Otherwise, it is an array. The format is one of:
@@ -88,8 +92,8 @@ class SpecialPage {
'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ),
'Fewestrevisions' => array( 'SpecialPage', 'Fewestrevisions' ),
'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ),
- 'Protectedpages' => array( 'SpecialPage', 'Protectedpages' ),
- 'Protectedtitles' => array( 'SpecialPage', 'Protectedtitles' ),
+ 'Protectedpages' => 'SpecialProtectedpages',
+ 'Protectedtitles' => 'SpecialProtectedtitles',
'Shortpages' => array( 'SpecialPage', 'Shortpages' ),
'Uncategorizedcategories' => array( 'SpecialPage', 'Uncategorizedcategories' ),
'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ),
@@ -107,7 +111,7 @@ class SpecialPage {
# List of pages
'Allpages' => 'SpecialAllpages',
'Prefixindex' => 'SpecialPrefixindex',
- 'Categories' => array( 'SpecialPage', 'Categories' ),
+ 'Categories' => 'SpecialCategories',
'Disambiguations' => array( 'SpecialPage', 'Disambiguations' ),
'Listredirects' => array( 'SpecialPage', 'Listredirects' ),
@@ -116,20 +120,23 @@ class SpecialPage {
'CreateAccount' => array( 'SpecialRedirectToSpecial', 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) ),
# Users and rights
- 'Blockip' => array( 'SpecialPage', 'Blockip', 'block' ),
- 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ),
+ 'Blockip' => 'IPBlockForm',
+ 'Ipblocklist' => 'IPUnblockForm',
+ 'Unblock' => array( 'SpecialRedirectToSpecial', 'Unblock', 'Ipblocklist', false, array( 'uselang', 'ip', 'id' ), array( 'action' => 'unblock' ) ),
'Resetpass' => 'SpecialResetpass',
'DeletedContributions' => 'DeletedContributionsPage',
'Preferences' => 'SpecialPreferences',
'Contributions' => 'SpecialContributions',
'Listgrouprights' => 'SpecialListGroupRights',
'Listusers' => array( 'SpecialPage', 'Listusers' ),
+ 'Listadmins' => array( 'SpecialRedirectToSpecial', 'Listadmins', 'Listusers', 'sysop' ),
+ 'Listbots' => array( 'SpecialRedirectToSpecial', 'Listbots', 'Listusers', 'bot' ),
'Activeusers' => 'SpecialActiveUsers',
'Userrights' => 'UserrightsPage',
# Recent changes and logs
'Newimages' => array( 'IncludableSpecialPage', 'Newimages' ),
- 'Log' => array( 'SpecialPage', 'Log' ),
+ 'Log' => 'SpecialLog',
'Watchlist' => array( 'SpecialPage', 'Watchlist' ),
'Newpages' => 'SpecialNewpages',
'Recentchanges' => 'SpecialRecentchanges',
@@ -138,17 +145,18 @@ class SpecialPage {
# Media reports and uploads
'Listfiles' => array( 'SpecialPage', 'Listfiles' ),
- 'Filepath' => array( 'SpecialPage', 'Filepath' ),
+ 'Filepath' => 'SpecialFilepath',
'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ),
'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ),
'Upload' => 'SpecialUpload',
+ 'UploadStash' => 'SpecialUploadStash',
# Wiki data and tools
'Statistics' => 'SpecialStatistics',
'Allmessages' => 'SpecialAllmessages',
'Version' => 'SpecialVersion',
- 'Lockdb' => array( 'SpecialPage', 'Lockdb', 'siteadmin' ),
- 'Unlockdb' => array( 'SpecialPage', 'Unlockdb', 'siteadmin' ),
+ 'Lockdb' => 'SpecialLockdb',
+ 'Unlockdb' => 'SpecialUnlockdb',
# Redirecting special pages
'LinkSearch' => array( 'SpecialPage', 'LinkSearch' ),
@@ -164,28 +172,28 @@ class SpecialPage {
'Mostrevisions' => array( 'SpecialPage', 'Mostrevisions' ),
# Page tools
+ 'ComparePages' => 'SpecialComparePages',
'Export' => 'SpecialExport',
'Import' => 'SpecialImport',
- 'Undelete' => array( 'SpecialPage', 'Undelete', 'deletedhistory' ),
+ 'Undelete' => 'UndeleteForm',
'Whatlinkshere' => 'SpecialWhatlinkshere',
- 'MergeHistory' => array( 'SpecialPage', 'MergeHistory', 'mergehistory' ),
+ 'MergeHistory' => 'SpecialMergeHistory',
# Other
'Booksources' => 'SpecialBookSources',
# Unlisted / redirects
'Blankpage' => 'SpecialBlankpage',
- 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ),
- 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ),
- 'Listadmins' => array( 'SpecialRedirectToSpecial', 'Listadmins', 'Listusers', 'sysop' ),
- 'Listbots' => array( 'SpecialRedirectToSpecial', 'Listbots', 'Listusers', 'bot' ),
- 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ),
- 'Mycontributions' => array( 'SpecialMycontributions' ),
- 'Mypage' => array( 'SpecialMypage' ),
- 'Mytalk' => array( 'SpecialMytalk' ),
+ 'Blockme' => 'SpecialBlockme',
+ 'Emailuser' => 'SpecialEmailUser',
+ 'Movepage' => 'MovePageForm',
+ 'Mycontributions' => 'SpecialMycontributions',
+ 'Mypage' => 'SpecialMypage',
+ 'Mytalk' => 'SpecialMytalk',
+ 'Myuploads' => 'SpecialMyuploads',
'Revisiondelete' => 'SpecialRevisionDelete',
- 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ),
- 'Userlogout' => array( 'UnlistedSpecialPage', 'Userlogout' ),
+ 'Specialpages' => 'SpecialSpecialpages',
+ 'Userlogout' => 'SpecialUserlogout',
);
static public $mAliases;
@@ -254,6 +262,9 @@ class SpecialPage {
/**
* Given a special page alias, return the special page name.
* Returns false if there is no such alias.
+ *
+ * @param $alias String
+ * @return String or false
*/
static function resolveAlias( $alias ) {
global $wgContLang;
@@ -261,6 +272,7 @@ class SpecialPage {
if ( !self::$mListInitialised ) self::initList();
if ( is_null( self::$mAliases ) ) self::initAliasList();
$caseFoldedAlias = $wgContLang->caseFold( $alias );
+ $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
if ( isset( self::$mAliases[$caseFoldedAlias] ) ) {
return self::$mAliases[$caseFoldedAlias];
} else {
@@ -272,6 +284,9 @@ class SpecialPage {
* Given a special page name with a possible subpage, return an array
* where the first element is the special page name and the second is the
* subpage.
+ *
+ * @param $alias String
+ * @return Array
*/
static function resolveAliasWithSubpage( $alias ) {
$bits = explode( '/', $alias, 2 );
@@ -289,10 +304,11 @@ class SpecialPage {
* method for adding special pages in extensions. It's now suggested that you add
* an associative record to $wgSpecialPages. This avoids autoloading SpecialPage.
*
- * @param SpecialPage $page
- * @static
+ * @param $page SpecialPage
+ * Deprecated in 1.7, warnings in 1.17, might be removed in 1.20
*/
static function addPage( &$page ) {
+ wfDeprecated( __METHOD__ );
if ( !self::$mListInitialised ) {
self::initList();
}
@@ -302,9 +318,8 @@ class SpecialPage {
/**
* Add a page to a certain display group for Special:SpecialPages
*
- * @param mixed $page (SpecialPage or string)
- * @param string $group
- * @static
+ * @param $page Mixed: SpecialPage or string
+ * @param $group String
*/
static function setGroup( $page, $group ) {
global $wgSpecialPageGroups;
@@ -315,8 +330,7 @@ class SpecialPage {
/**
* Add a page to a certain display group for Special:SpecialPages
*
- * @param SpecialPage $page
- * @static
+ * @param $page SpecialPage
*/
static function getGroup( &$page ) {
global $wgSpecialPageGroups;
@@ -340,8 +354,6 @@ class SpecialPage {
* Remove a special page from the list
* Formerly used to disable expensive or dangerous special pages. The
* preferred method is now to add a SpecialPage_initList hook.
- *
- * @static
*/
static function removePage( $name ) {
if ( !self::$mListInitialised ) {
@@ -352,8 +364,9 @@ class SpecialPage {
/**
* Check if a given name exist as a special page or as a special page alias
- * @param $name string: name of a special page
- * @return boolean: true if a special page exists with this name
+ *
+ * @param $name String: name of a special page
+ * @return Boolean: true if a special page exists with this name
*/
static function exists( $name ) {
global $wgContLang;
@@ -376,8 +389,9 @@ class SpecialPage {
/**
* Find the object with a given name and return it (or NULL)
- * @static
- * @param string $name
+ *
+ * @param $name String
+ * @return SpecialPage object or null if the page doesn't exist
*/
static function getPage( $name ) {
if ( !self::$mListInitialised ) {
@@ -401,6 +415,8 @@ class SpecialPage {
/**
* Get a special page with a given localised name, or NULL if there
* is no such special page.
+ *
+ * @return SpecialPage object or null if the page doesn't exist
*/
static function getPageByAlias( $alias ) {
$realName = self::resolveAlias( $alias );
@@ -414,7 +430,8 @@ class SpecialPage {
/**
* Return categorised listable special pages which are available
* for the current user, and everyone.
- * @static
+ *
+ * @return Associative array mapping page's name to its SpecialPage object
*/
static function getUsablePages() {
global $wgUser;
@@ -439,7 +456,8 @@ class SpecialPage {
/**
* Return categorised listable special pages for all users
- * @static
+ *
+ * @return Associative array mapping page's name to its SpecialPage object
*/
static function getRegularPages() {
if ( !self::$mListInitialised ) {
@@ -459,7 +477,8 @@ class SpecialPage {
/**
* Return categorised listable special pages which are available
* for the current user, but not for everyone
- * @static
+ *
+ * @return Associative array mapping page's name to its SpecialPage object
*/
static function getRestrictedPages() {
global $wgUser;
@@ -554,7 +573,7 @@ class SpecialPage {
$page->including( $including );
// Execute special page
- $profName = 'Special:' . $page->getName();
+ $profName = 'Special:' . $page->name();
wfProfileIn( $profName );
$page->execute( $par );
wfProfileOut( $profName );
@@ -566,7 +585,8 @@ class SpecialPage {
* Just like executePath() except it returns the HTML instead of outputting it
* Returns false if there was no such special page, or a title object if it was
* a redirect.
- * @static
+ *
+ * @return String: HTML fragment
*/
static function capturePath( &$title ) {
global $wgOut, $wgTitle;
@@ -588,10 +608,10 @@ class SpecialPage {
/**
* Get the local name for a specified canonical name
*
- * @param $name
- * @param mixed $subpage Boolean false, or string
+ * @param $name String
+ * @param $subpage Mixed: boolean false, or string
*
- * @return string
+ * @return String
*/
static function getLocalNameFor( $name, $subpage = false ) {
global $wgContLang;
@@ -603,15 +623,17 @@ class SpecialPage {
$found = false;
foreach ( $aliases as $n => $values ) {
if ( strcasecmp( $name, $n ) === 0 ) {
- wfWarn( "Found alias defined for $n when searching for special page aliases
-for $name. Case mismatch?" );
+ wfWarn( "Found alias defined for $n when searching for" .
+ "special page aliases for $name. Case mismatch?" );
$name = $values[0];
$found = true;
break;
}
}
- if ( !$found ) wfWarn( "Did not find alias for special page '$name'.
-Perhaps no page aliases are defined for it?" );
+ if ( !$found ) {
+ wfWarn( "Did not find alias for special page '$name'. " .
+ "Perhaps no aliases are defined for it?" );
+ }
}
if ( $subpage !== false && !is_null( $subpage ) ) {
$name = "$name/$subpage";
@@ -621,6 +643,8 @@ Perhaps no page aliases are defined for it?" );
/**
* Get a localised Title object for a specified special page name
+ *
+ * @return Title object
*/
static function getTitleFor( $name, $subpage = false ) {
$name = self::getLocalNameFor( $name, $subpage );
@@ -633,6 +657,8 @@ Perhaps no page aliases are defined for it?" );
/**
* Get a localised Title object for a page name with a possibly unvalidated subpage
+ *
+ * @return Title object or null if the page doesn't exist
*/
static function getSafeTitleFor( $name, $subpage = false ) {
$name = self::getLocalNameFor( $name, $subpage );
@@ -645,6 +671,7 @@ Perhaps no page aliases are defined for it?" );
/**
* Get a title for a given alias
+ *
* @return Title or null if there is no such alias
*/
static function getTitleForAlias( $alias ) {
@@ -666,18 +693,28 @@ Perhaps no page aliases are defined for it?" );
* If you override execute(), you can recover the default behaviour with userCanExecute()
* and displayRestrictionError()
*
- * @param string $name Name of the special page, as seen in links and URLs
- * @param string $restriction User right required, e.g. "block" or "delete"
- * @param boolean $listed Whether the page is listed in Special:Specialpages
- * @param string $function Function called by execute(). By default it is constructed from $name
- * @param string $file File which is included by execute(). It is also constructed from $name by default
+ * @param $name String: name of the special page, as seen in links and URLs
+ * @param $restriction String: user right required, e.g. "block" or "delete"
+ * @param $listed Boolean: whether the page is listed in Special:Specialpages
+ * @param $function Callback: function called by execute(). By default it is constructed from $name
+ * @param $file String: file which is included by execute(). It is also constructed from $name by default
+ * @param $includable Boolean: whether the page can be included in normal pages
*/
- function SpecialPage( $name = '', $restriction = '', $listed = true, $function = false, $file = 'default', $includable = false ) {
+ public function __construct( $name = '', $restriction = '', $listed = true, $function = false, $file = 'default', $includable = false ) {
+ $this->init( $name, $restriction, $listed, $function, $file, $includable );
+ }
+
+ /**
+ * Do the real work for the constructor, mainly so __call() can intercept
+ * calls to SpecialPage()
+ * @see __construct() for param docs
+ */
+ private function init( $name, $restriction, $listed, $function, $file, $includable ) {
$this->mName = $name;
$this->mRestriction = $restriction;
$this->mListed = $listed;
$this->mIncludable = $includable;
- if ( $function == false ) {
+ if ( !$function ) {
$this->mFunction = 'wfSpecial'.$name;
} else {
$this->mFunction = $function;
@@ -689,6 +726,29 @@ Perhaps no page aliases are defined for it?" );
}
}
+ /**
+ * Use PHP's magic __call handler to get calls to the old PHP4 constructor
+ * because PHP E_STRICT yells at you for having __construct() and SpecialPage()
+ *
+ * @param $name String Name of called method
+ * @param $a Array Arguments to the method
+ * @deprecated Call isn't deprecated, but SpecialPage::SpecialPage() is
+ */
+ public function __call( $fName, $a ) {
+ // Sometimes $fName is SpecialPage, sometimes it's specialpage. <3 PHP
+ if( strtolower( $fName ) == 'specialpage' ) {
+ // Debug messages now, warnings in 1.19 or 1.20?
+ wfDebug( "Deprecated SpecialPage::SpecialPage() called, use __construct();\n" );
+ $name = isset( $a[0] ) ? $a[0] : '';
+ $restriction = isset( $a[1] ) ? $a[1] : '';
+ $listed = isset( $a[2] ) ? $a[2] : true;
+ $function = isset( $a[3] ) ? $a[3] : false;
+ $file = isset( $a[4] ) ? $a[4] : 'default';
+ $includable = isset( $a[5] ) ? $a[5] : false;
+ $this->init( $name, $restriction, $listed, $function, $file, $includable );
+ }
+ }
+
/**#@+
* Accessor
*
@@ -731,7 +791,7 @@ Perhaps no page aliases are defined for it?" );
* Can be overridden by subclasses with more complicated permissions
* schemes.
*
- * @return bool Should the page be displayed with the restricted-access
+ * @return Boolean: should the page be displayed with the restricted-access
* pages?
*/
public function isRestricted() {
@@ -745,8 +805,8 @@ Perhaps no page aliases are defined for it?" );
* special page (as defined by $mRestriction). Can be overridden by sub-
* classes with more complicated permissions schemes.
*
- * @param User $user The user to check
- * @return bool Does the user have permission to view the page?
+ * @param $user User: the user to check
+ * @return Boolean: does the user have permission to view the page?
*/
public function userCanExecute( $user ) {
return $user->isAllowed( $this->mRestriction );
@@ -774,7 +834,7 @@ Perhaps no page aliases are defined for it?" );
* Default execute method
* Checks user permissions, calls the function given in mFunction
*
- * This may be overridden by subclasses.
+ * This must be overridden by subclasses; it will be made abstract in a future version
*/
function execute( $par ) {
global $wgUser;
@@ -800,7 +860,7 @@ Perhaps no page aliases are defined for it?" );
* May be overriden, i.e. by extensions to stick with the naming conventions
* for message keys: 'extensionname-xxx'
*
- * @param string message key of the summary
+ * @param $summaryMessageKey String: message key of the summary
*/
function outputHeader( $summaryMessageKey = '' ) {
global $wgOut, $wgContLang;
@@ -812,22 +872,29 @@ Perhaps no page aliases are defined for it?" );
}
$out = wfMsgNoTrans( $msg );
if ( ! wfEmptyMsg( $msg, $out ) and $out !== '' and ! $this->including() ) {
- $wgOut->wrapWikiMsg( "<div class='mw-specialpage-summary'>\n$1</div>", $msg );
+ $wgOut->wrapWikiMsg( "<div class='mw-specialpage-summary'>\n$1\n</div>", $msg );
}
}
- # Returns the name that goes in the <h1> in the special page itself, and also the name that
- # will be listed in Special:Specialpages
- #
- # Derived classes can override this, but usually it is easier to keep the default behaviour.
- # Messages can be added at run-time, see MessageCache.php
+ /**
+ * Returns the name that goes in the \<h1\> in the special page itself, and
+ * also the name that will be listed in Special:Specialpages
+ *
+ * Derived classes can override this, but usually it is easier to keep the
+ * default behaviour. Messages can be added at run-time, see
+ * MessageCache.php.
+ *
+ * @return String
+ */
function getDescription() {
return wfMsg( strtolower( $this->mName ) );
}
/**
* Get a self-referential title object
+ *
+ * @return Title object
*/
function getTitle( $subpage = false ) {
return self::getTitleFor( $this->mName, $subpage );
@@ -852,7 +919,7 @@ Perhaps no page aliases are defined for it?" );
* Return part of the request string for a special redirect page
* This allows passing, e.g. action=history to Special:Mypage, etc.
*
- * @return string
+ * @return String
*/
function getRedirectQuery() {
global $wgRequest;
@@ -861,7 +928,11 @@ Perhaps no page aliases are defined for it?" );
if( ( $val = $wgRequest->getVal( $arg, null ) ) !== null )
$params[] = $arg . '=' . $val;
}
-
+
+ foreach( $this->mAddedRedirectParams as $arg => $val ) {
+ $params[] = $arg . '=' . $val;
+ }
+
return count( $params ) ? implode( '&', $params ) : false;
}
}
@@ -872,8 +943,8 @@ Perhaps no page aliases are defined for it?" );
*/
class UnlistedSpecialPage extends SpecialPage
{
- function UnlistedSpecialPage( $name, $restriction = '', $function = false, $file = 'default' ) {
- SpecialPage::SpecialPage( $name, $restriction, false, $function, $file );
+ function __construct( $name, $restriction = '', $function = false, $file = 'default' ) {
+ parent::__construct( $name, $restriction, false, $function, $file );
}
}
@@ -883,8 +954,8 @@ class UnlistedSpecialPage extends SpecialPage
*/
class IncludableSpecialPage extends SpecialPage
{
- function IncludableSpecialPage( $name, $restriction = '', $listed = true, $function = false, $file = 'default' ) {
- SpecialPage::SpecialPage( $name, $restriction, $listed, $function, $file, true );
+ function __construct( $name, $restriction = '', $listed = true, $function = false, $file = 'default' ) {
+ parent::__construct( $name, $restriction, $listed, $function, $file, true );
}
}
@@ -895,11 +966,12 @@ class IncludableSpecialPage extends SpecialPage
class SpecialRedirectToSpecial extends UnlistedSpecialPage {
var $redirName, $redirSubpage;
- function __construct( $name, $redirName, $redirSubpage = false, $redirectParams = array() ) {
+ function __construct( $name, $redirName, $redirSubpage = false, $allowedRedirectParams = array(), $addedRedirectParams = array() ) {
parent::__construct( $name );
$this->redirName = $redirName;
$this->redirSubpage = $redirSubpage;
- $this->mAllowedRedirectParams = $redirectParams;
+ $this->mAllowedRedirectParams = $allowedRedirectParams;
+ $this->mAddedRedirectParams = $addedRedirectParams;
}
function getRedirect( $subpage ) {
@@ -925,7 +997,8 @@ class SpecialRedirectToSpecial extends UnlistedSpecialPage {
class SpecialMypage extends UnlistedSpecialPage {
function __construct() {
parent::__construct( 'Mypage' );
- $this->mAllowedRedirectParams = array( 'action' , 'preload' , 'editintro', 'section' );
+ $this->mAllowedRedirectParams = array( 'action' , 'preload' , 'editintro',
+ 'section', 'oldid', 'diff', 'dir' );
}
function getRedirect( $subpage ) {
@@ -945,7 +1018,8 @@ class SpecialMypage extends UnlistedSpecialPage {
class SpecialMytalk extends UnlistedSpecialPage {
function __construct() {
parent::__construct( 'Mytalk' );
- $this->mAllowedRedirectParams = array( 'action' , 'preload' , 'editintro', 'section' );
+ $this->mAllowedRedirectParams = array( 'action' , 'preload' , 'editintro',
+ 'section', 'oldid', 'diff', 'dir' );
}
function getRedirect( $subpage ) {
@@ -974,3 +1048,18 @@ class SpecialMycontributions extends UnlistedSpecialPage {
return SpecialPage::getTitleFor( 'Contributions', $wgUser->getName() );
}
}
+
+/**
+ * Redirect to Special:Listfiles?user=$wgUser
+ */
+class SpecialMyuploads extends UnlistedSpecialPage {
+ function __construct() {
+ parent::__construct( 'Myuploads' );
+ $this->mAllowedRedirectParams = array( 'limit' );
+ }
+
+ function getRedirect( $subpage ) {
+ global $wgUser;
+ return SpecialPage::getTitleFor( 'Listfiles', $wgUser->getName() );
+ }
+}
diff --git a/includes/SquidPurgeClient.php b/includes/SquidPurgeClient.php
index 65da5c1a..1b315e5f 100644
--- a/includes/SquidPurgeClient.php
+++ b/includes/SquidPurgeClient.php
@@ -265,7 +265,7 @@ class SquidPurgeClient {
$this->markDown();
return;
}
- list( $all, $major, $minor, $status, $reason ) = $m;
+ list( , , , $status, $reason ) = $m;
$status = intval( $status );
if ( $status !== 200 && $status !== 404 ) {
$this->log( "unexpected status code: $status $reason" );
@@ -356,12 +356,12 @@ class SquidPurgeClientPool {
}
foreach ( $readSockets as $key => $socket ) {
- list( $clientIndex, $i ) = explode( '/', $key );
+ list( $clientIndex, ) = explode( '/', $key );
$client = $this->clients[$clientIndex];
$client->doReads();
}
foreach ( $writeSockets as $key => $socket ) {
- list( $clientIndex, $i ) = explode( '/', $key );
+ list( $clientIndex, ) = explode( '/', $key );
$client = $this->clients[$clientIndex];
$client->doWrites();
}
diff --git a/includes/SquidUpdate.php b/includes/SquidUpdate.php
index 66517719..31f7aa68 100644
--- a/includes/SquidUpdate.php
+++ b/includes/SquidUpdate.php
@@ -26,6 +26,7 @@ class SquidUpdate {
}
static function newFromLinksTo( &$title ) {
+ global $wgMaxSquidPurgeTitles;
wfProfileIn( __METHOD__ );
# Get a list of URLs linking to this page
@@ -38,14 +39,12 @@ class SquidUpdate {
'pl_from=page_id' ),
__METHOD__ );
$blurlArr = $title->getSquidURLs();
- if ( $dbr->numRows( $res ) <= $this->mMaxTitles ) {
- while ( $BL = $dbr->fetchObject ( $res ) )
- {
+ if ( $dbr->numRows( $res ) <= $wgMaxSquidPurgeTitles ) {
+ foreach ( $res as $BL ) {
$tobj = Title::makeTitle( $BL->page_namespace, $BL->page_title ) ;
$blurlArr[] = $tobj->getInternalURL();
}
}
- $dbr->freeResult ( $res ) ;
wfProfileOut( __METHOD__ );
return new SquidUpdate( $blurlArr );
diff --git a/includes/Status.php b/includes/Status.php
index a07a4b81..f049980f 100644
--- a/includes/Status.php
+++ b/includes/Status.php
@@ -24,6 +24,8 @@ class Status {
/**
* Factory function for fatal errors
+ *
+ * @param $message String: message name
*/
static function newFatal( $message /*, parameters...*/ ) {
$params = func_get_args();
@@ -33,25 +35,52 @@ class Status {
return $result;
}
+ /**
+ * Factory function for good results
+ *
+ * @param $value Mixed
+ */
static function newGood( $value = null ) {
$result = new self;
$result->value = $value;
return $result;
}
+ /**
+ * Change operation result
+ *
+ * @param $ok Boolean: whether to operation completed
+ * @param $value Mixed
+ */
function setResult( $ok, $value = null ) {
$this->ok = $ok;
$this->value = $value;
}
+ /**
+ * Returns whether the operation completed and didn't have any error or
+ * warnings
+ *
+ * @return Boolean
+ */
function isGood() {
return $this->ok && !$this->errors;
}
+ /**
+ * Returns whether the operation completed
+ *
+ * @return Boolean
+ */
function isOK() {
return $this->ok;
}
+ /**
+ * Add a new warning
+ *
+ * @param $message String: message name
+ */
function warning( $message /*, parameters... */ ) {
$params = array_slice( func_get_args(), 1 );
$this->errors[] = array(
@@ -63,6 +92,8 @@ class Status {
/**
* Add an error, do not set fatal flag
* This can be used for non-fatal errors
+ *
+ * @param $message String: message name
*/
function error( $message /*, parameters... */ ) {
$params = array_slice( func_get_args(), 1 );
@@ -73,7 +104,10 @@ class Status {
}
/**
- * Add an error and set OK to false, indicating that the operation as a whole was fatal
+ * Add an error and set OK to false, indicating that the operation
+ * as a whole was fatal
+ *
+ * @param $message String: message name
*/
function fatal( $message /*, parameters... */ ) {
$params = array_slice( func_get_args(), 1 );
@@ -128,9 +162,11 @@ class Status {
/**
* Get the error list as a wikitext formatted list
- * @param string $shortContext A short enclosing context message name, to be used
- * when there is a single error
- * @param string $longContext A long enclosing context message name, for a list
+ *
+ * @param $shortContext String: a short enclosing context message name, to
+ * be used when there is a single error
+ * @param $longContext String: a long enclosing context message name, for a list
+ * @return String
*/
function getWikiText( $shortContext = false, $longContext = false ) {
if ( count( $this->errors ) == 0 ) {
@@ -143,19 +179,15 @@ class Status {
}
}
if ( count( $this->errors ) == 1 ) {
- $params = array_map( 'wfEscapeWikiText', $this->cleanParams( $this->errors[0]['params'] ) );
- $s = wfMsgReal( $this->errors[0]['message'], $params, true, false, false );
+ $s = $this->getWikiTextForError( $this->errors[0], $this->errors[0] );
if ( $shortContext ) {
$s = wfMsgNoTrans( $shortContext, $s );
} elseif ( $longContext ) {
$s = wfMsgNoTrans( $longContext, "* $s\n" );
}
} else {
- $s = '';
- foreach ( $this->errors as $error ) {
- $params = array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) );
- $s .= '* ' . wfMsgReal( $error['message'], $params, true, false, false ) . "\n";
- }
+ $s = '* '. implode("\n* ",
+ $this->getWikiTextArray( $this->errors ) ) . "\n";
if ( $longContext ) {
$s = wfMsgNoTrans( $longContext, $s );
} elseif ( $shortContext ) {
@@ -166,7 +198,45 @@ class Status {
}
/**
+ * Return the wiki text for a single error.
+ * @param $error Mixed With an array & two values keyed by
+ * 'message' and 'params', use those keys-value pairs.
+ * Otherwise, if its an array, just use the first value as the
+ * message and the remaining items as the params.
+ *
+ * @return String
+ */
+ protected function getWikiTextForError( $error ) {
+ if ( is_array( $error ) ) {
+ if ( isset( $error['message'] ) && isset( $error['params'] ) ) {
+ return wfMsgReal( $error['message'],
+ array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) ),
+ true, false, false );
+ } else {
+ $message = array_shift($error);
+ return wfMsgReal( $message,
+ array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ),
+ true, false, false );
+ }
+ } else {
+ return wfMsgReal( $error, array(), true, false, false);
+ }
+ }
+
+ /**
+ * Return an array with the wikitext for each item in the array.
+ * @param $errors Array
+ * @return Array
+ */
+ function getWikiTextArray( $errors ) {
+ return array_map( array( $this, 'getWikiTextForError' ), $errors );
+ }
+
+ /**
* Merge another status object into this one
+ *
+ * @param $other Other Status object
+ * @param $overwriteValue Boolean: whether to override the "value" member
*/
function merge( $other, $overwriteValue = false ) {
$this->errors = array_merge( $this->errors, $other->errors );
@@ -178,20 +248,48 @@ class Status {
$this->failCount += $other->failCount;
}
+ /**
+ * Get the list of errors (but not warnings)
+ *
+ * @return Array
+ */
function getErrorsArray() {
+ return $this->getStatusArray( "error" );
+ }
+
+ /**
+ * Get the list of warnings (but not errors)
+ *
+ * @return Array
+ */
+ function getWarningsArray() {
+ return $this->getStatusArray( "warning" );
+ }
+
+ /**
+ * Returns a list of status messages of the given type
+ * @param $type String
+ *
+ * @return Array
+ */
+ protected function getStatusArray( $type ) {
$result = array();
foreach ( $this->errors as $error ) {
- if ( $error['type'] == 'error' )
- if( $error['params'] )
+ if ( $error['type'] === $type ) {
+ if( $error['params'] ) {
$result[] = array_merge( array( $error['message'] ), $error['params'] );
- else
+ } else {
$result[] = $error['message'];
+ }
+ }
}
return $result;
}
-
/**
* Returns true if the specified message is present as a warning or error
+ *
+ * @param $msg String: message name
+ * @return Boolean
*/
function hasMessage( $msg ) {
foreach ( $this->errors as $error ) {
@@ -201,4 +299,30 @@ class Status {
}
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.
+ *
+ * Return true if the replacement was done, false otherwise.
+ */
+ 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;
+ }
+
+ /**
+ * Backward compatibility function for WikiError -> Status migration
+ *
+ * @return String
+ */
+ public function getMessage() {
+ return $this->getWikiText();
+ }
}
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index 6db66ba8..5f460ee3 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -1,5 +1,9 @@
<?php
-/** */
+/**
+ * Functions related to the output of file content
+ *
+ * @file
+ */
/** */
function wfStreamFile( $fname, $headers = array() ) {
@@ -37,8 +41,8 @@ function wfStreamFile( $fname, $headers = array() ) {
return;
}
- global $wgContLanguageCode;
- header( "Content-Disposition: inline;filename*=utf-8'$wgContLanguageCode'" . urlencode( basename( $fname ) ) );
+ global $wgLanguageCode;
+ header( "Content-Disposition: inline;filename*=utf-8'$wgLanguageCode'" . urlencode( basename( $fname ) ) );
foreach ( $headers as $header ) {
header( $header );
@@ -91,8 +95,8 @@ function wfGetType( $filename, $safe = true ) {
*/
if ( $safe ) {
global $wgFileBlacklist, $wgCheckFileExtensions, $wgStrictFileExtensions,
- $wgFileExtensions, $wgVerifyMimeType, $wgMimeTypeBlacklist, $wgRequest;
- list( $partName, $extList ) = UploadBase::splitExtensions( $filename );
+ $wgFileExtensions, $wgVerifyMimeType, $wgMimeTypeBlacklist;
+ list( , $extList ) = UploadBase::splitExtensions( $filename );
if ( UploadBase::checkFileExtensionList( $extList, $wgFileBlacklist ) ) {
return 'unknown/unknown';
}
diff --git a/includes/StringUtils.php b/includes/StringUtils.php
index 0be88df5..c1e617a0 100644
--- a/includes/StringUtils.php
+++ b/includes/StringUtils.php
@@ -36,7 +36,11 @@ class StringUtils {
* This implementation is slower than hungryDelimiterReplace but uses far less
* memory. The delimiters are literal strings, not regular expressions.
*
- * @param string $flags Regular expression flags
+ * @param $startDelim String: start delimiter
+ * @param $endDelim String: end delimiter
+ * @param $callback Callback: function to call on each match
+ * @param $subject String
+ * @param $flags String: regular expression flags
*/
# If the start delimiter ends with an initial substring of the end delimiter,
# e.g. in the case of C-style comments, the behaviour differs from the model
@@ -115,17 +119,18 @@ class StringUtils {
return $output;
}
- /*
+ /**
* Perform an operation equivalent to
*
* preg_replace( "!$startDelim(.*)$endDelim!$flags", $replace, $subject )
*
- * @param string $startDelim Start delimiter regular expression
- * @param string $endDelim End delimiter regular expression
- * @param string $replace Replacement string. May contain $1, which will be
- * replaced by the text between the delimiters
- * @param string $subject String to search
- * @return string The string with the matches replaced
+ * @param $startDelim String: start delimiter regular expression
+ * @param $endDelim String: end delimiter regular expression
+ * @param $replace String: replacement string. May contain $1, which will be
+ * replaced by the text between the delimiters
+ * @param $subject String to search
+ * @param $flags String: regular expression flags
+ * @return String: The string with the matches replaced
*/
static function delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags = '' ) {
$replacer = new RegexlikeReplacer( $replace );
@@ -136,8 +141,8 @@ class StringUtils {
/**
* More or less "markup-safe" explode()
* Ignores any instances of the separator inside <...>
- * @param string $separator
- * @param string $text
+ * @param $separator String
+ * @param $text String
* @return array
*/
static function explodeMarkup( $separator, $text ) {
@@ -163,8 +168,8 @@ class StringUtils {
* Escape a string to make it suitable for inclusion in a preg_replace()
* replacement parameter.
*
- * @param string $string
- * @return string
+ * @param $string String
+ * @return String
*/
static function escapeRegexReplacement( $string ) {
$string = str_replace( '\\', '\\\\', $string );
diff --git a/includes/StubObject.php b/includes/StubObject.php
index 52fbeb24..678b2744 100644
--- a/includes/StubObject.php
+++ b/includes/StubObject.php
@@ -21,10 +21,10 @@ class StubObject {
/**
* Constructor.
*
- * @param String $global name of the global variable.
- * @param String $class name of the class of the real object.
- * @param Array $param array of parameters to pass to contructor of the real
- * object.
+ * @param $global String: name of the global variable.
+ * @param $class String: name of the class of the real object.
+ * @param $params Array: parameters to pass to contructor of the real
+ * object.
*/
function __construct( $global = null, $class = null, $params = array() ) {
$this->mGlobal = $global;
@@ -36,8 +36,8 @@ class StubObject {
* Returns a bool value whetever $obj is a stub object. Can be used to break
* a infinite loop when unstubbing an object.
*
- * @param Object $obj object to check.
- * @return bool true if $obj is not an instance of StubObject class.
+ * @param $obj Object to check.
+ * @return Boolean: true if $obj is not an instance of StubObject class.
*/
static function isRealObject( $obj ) {
return is_object( $obj ) && !($obj instanceof StubObject);
@@ -50,8 +50,8 @@ class StubObject {
* This function will also call the function with the same name in the real
* object.
*
- * @param String $name name of the function called.
- * @param Array $args array of arguments.
+ * @param $name String: name of the function called
+ * @param $args Array: arguments
*/
function _call( $name, $args ) {
$this->_unstub( $name, 5 );
@@ -69,8 +69,8 @@ class StubObject {
* Function called by PHP if no function with that name exists in this
* object.
*
- * @param String $name name of the function called
- * @param Array $args array of arguments
+ * @param $name String: name of the function called
+ * @param $args Array: arguments
*/
function __call( $name, $args ) {
return $this->_call( $name, $args );
@@ -82,9 +82,9 @@ class StubObject {
* This is public, for the convenience of external callers wishing to access
* properties, e.g. eval.php
*
- * @param String $name name of the method called in this object.
- * @param Integer $level level to go in the stact trace to get the function
- * who called this function.
+ * @param $name String: name of the method called in this object.
+ * @param $level Integer: level to go in the stact trace to get the function
+ * who called this function.
*/
function _unstub( $name = '_unstub', $level = 2 ) {
static $recursionLevel = 0;
@@ -100,7 +100,7 @@ class StubObject {
throw new MWException( "Unstub loop detected on call of \${$this->mGlobal}->$name from $caller\n" );
}
wfDebug( "Unstubbing \${$this->mGlobal} on call of \${$this->mGlobal}::$name from $caller\n" );
- $obj = $GLOBALS[$this->mGlobal] = $this->_newObject();
+ $GLOBALS[$this->mGlobal] = $this->_newObject();
--$recursionLevel;
wfProfileOut( $fname );
}
@@ -122,8 +122,8 @@ class StubContLang extends StubObject {
}
function _newObject() {
- global $wgContLanguageCode;
- $obj = Language::factory( $wgContLanguageCode );
+ global $wgLanguageCode;
+ $obj = Language::factory( $wgLanguageCode );
$obj->initEncoding();
$obj->initContLang();
return $obj;
@@ -146,7 +146,7 @@ class StubUserLang extends StubObject {
}
function _newObject() {
- global $wgContLanguageCode, $wgRequest, $wgUser, $wgContLang;
+ global $wgLanguageCode, $wgRequest, $wgUser, $wgContLang;
$code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) );
// BCP 47 - letter case MUST NOT carry meaning
$code = strtolower( $code );
@@ -154,10 +154,10 @@ class StubUserLang extends StubObject {
# Validate $code
if( empty( $code ) || !Language::isValidCode( $code ) || ( $code === 'qqq' ) ) {
wfDebug( "Invalid user language code\n" );
- $code = $wgContLanguageCode;
+ $code = $wgLanguageCode;
}
- if( $code === $wgContLanguageCode ) {
+ if( $code === $wgLanguageCode ) {
return $wgContLang;
} else {
$obj = Language::factory( $code );
@@ -165,30 +165,3 @@ class StubUserLang extends StubObject {
}
}
}
-
-/**
- * Stub object for the user. The initialisation of the will depend of
- * $wgCommandLineMode. If it's true, it will be an anonymous user and if it's
- * false, the user will be loaded from credidentails provided by cookies. This
- * object have to be in $wgUser global.
- */
-class StubUser extends StubObject {
-
- function __construct() {
- parent::__construct( 'wgUser' );
- }
-
- function __call( $name, $args ) {
- return $this->_call( $name, $args );
- }
-
- function _newObject() {
- global $wgCommandLineMode;
- if( $wgCommandLineMode ) {
- $user = new User;
- } else {
- $user = User::newFromSession();
- }
- return $user;
- }
-}
diff --git a/includes/Title.php b/includes/Title.php
index be41a85a..5ae2f1a0 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -4,24 +4,34 @@
* @file
*/
+/**
+ * @todo: determine if it is really necessary to load this. Appears to be left over from pre-autoloader versions, and
+ * is only really needed to provide access to constant UTF8_REPLACEMENT, which actually resides in UtfNormalDefines.php
+ * and is loaded by UtfNormalUtil.php, which is loaded by UtfNormal.php.
+ */
if ( !class_exists( 'UtfNormal' ) ) {
- require_once( dirname(__FILE__) . '/normal/UtfNormal.php' );
+ require_once( dirname( __FILE__ ) . '/normal/UtfNormal.php' );
}
-define ( 'GAID_FOR_UPDATE', 1 );
+/**
+ * @deprecated This used to be a define, but was moved to
+ * Title::GAID_FOR_UPDATE in 1.17. This will probably be removed in 1.18
+ */
+define( 'GAID_FOR_UPDATE', Title::GAID_FOR_UPDATE );
/**
* Represents a title within MediaWiki.
* Optionally may contain an interwiki designation or namespace.
* @note This class can fetch various kinds of data from the database;
* however, it does so inefficiently.
+ *
+ * @internal documentation reviewed 15 Mar 2010
*/
class Title {
/** @name Static cache variables */
- //@{
- static private $titleCache=array();
- static private $interwikiCache=array();
- //@}
+ // @{
+ static private $titleCache = array();
+ // @}
/**
* Title::newFromText maintains a cache to avoid expensive re-normalization of
@@ -30,51 +40,60 @@ class Title {
*/
const CACHE_MAX = 1000;
+ /**
+ * Used to be GAID_FOR_UPDATE define. Used with getArticleId() and friends
+ * to use the master DB
+ */
+ const GAID_FOR_UPDATE = 1;
+
/**
* @name Private member variables
* Please use the accessor functions instead.
* @private
*/
- //@{
-
- var $mTextform = ''; ///< Text form (spaces not underscores) of the main part
- var $mUrlform = ''; ///< URL-encoded form of the main part
- var $mDbkeyform = ''; ///< Main part with underscores
- var $mUserCaseDBKey; ///< DB key with the initial letter in the case specified by the user
- var $mNamespace = NS_MAIN; ///< Namespace index, i.e. one of the NS_xxxx constants
- var $mInterwiki = ''; ///< Interwiki prefix (or null string)
- var $mFragment; ///< Title fragment (i.e. the bit after the #)
- var $mArticleID = -1; ///< Article ID, fetched from the link cache on demand
- var $mLatestID = false; ///< ID of most recent revision
- var $mRestrictions = array(); ///< Array of groups allowed to edit this article
+ // @{
+
+ var $mTextform = ''; // /< Text form (spaces not underscores) of the main part
+ var $mUrlform = ''; // /< URL-encoded form of the main part
+ var $mDbkeyform = ''; // /< Main part with underscores
+ var $mUserCaseDBKey; // /< DB key with the initial letter in the case specified by the user
+ var $mNamespace = NS_MAIN; // /< Namespace index, i.e. one of the NS_xxxx constants
+ var $mInterwiki = ''; // /< Interwiki prefix (or null string)
+ var $mFragment; // /< Title fragment (i.e. the bit after the #)
+ var $mArticleID = -1; // /< Article ID, fetched from the link cache on demand
+ var $mLatestID = false; // /< ID of most recent revision
+ var $mRestrictions = array(); // /< Array of groups allowed to edit this article
var $mOldRestrictions = false;
var $mCascadeRestriction; ///< Cascade restrictions on this page to included templates and images?
+ var $mCascadingRestrictions; // Caching the results of getCascadeProtectionSources
var $mRestrictionsExpiry = array(); ///< When do the restrictions on this page expire?
var $mHasCascadingRestrictions; ///< Are cascading restrictions in effect on this page?
var $mCascadeSources; ///< Where are the cascading restrictions coming from on this page?
var $mRestrictionsLoaded = false; ///< Boolean for initialisation on demand
var $mPrefixedText; ///< Text form including namespace/interwiki, initialised on demand
+ var $mTitleProtection; ///< Cached value for getTitleProtection (create protection)
# Don't change the following default, NS_MAIN is hardcoded in several
# places. See bug 696.
- var $mDefaultNamespace = NS_MAIN; ///< Namespace index when there is no namespace
+ var $mDefaultNamespace = NS_MAIN; // /< Namespace index when there is no namespace
# Zero except in {{transclusion}} tags
- var $mWatched = null; ///< Is $wgUser watching this page? null if unfilled, accessed through userIsWatching()
- var $mLength = -1; ///< The page length, 0 for special pages
- var $mRedirect = null; ///< Is the article at this title a redirect?
- var $mNotificationTimestamp = array(); ///< Associative array of user ID -> timestamp/false
- var $mBacklinkCache = null; ///< Cache of links to this title
- //@}
+ var $mWatched = null; // /< Is $wgUser watching this page? null if unfilled, accessed through userIsWatching()
+ var $mLength = -1; // /< The page length, 0 for special pages
+ var $mRedirect = null; // /< Is the article at this title a redirect?
+ var $mNotificationTimestamp = array(); // /< Associative array of user ID -> timestamp/false
+ var $mBacklinkCache = null; // /< Cache of links to this title
+ // @}
/**
* Constructor
* @private
*/
- /* private */ function __construct() {}
+ /* private */ function __construct() { }
/**
* Create a new Title from a prefixed DB key
+ *
* @param $key \type{\string} The database key, which has underscores
* instead of spaces, possibly including namespace and
* interwiki prefixes
@@ -83,26 +102,27 @@ class Title {
public static function newFromDBkey( $key ) {
$t = new Title();
$t->mDbkeyform = $key;
- if( $t->secureAndSplit() )
+ if ( $t->secureAndSplit() ) {
return $t;
- else
+ } else {
return null;
+ }
}
/**
* Create a new Title from text, such as what one would find in a link. De-
* codes any HTML entities in the text.
*
- * @param $text string The link text; spaces, prefixes, and an
+ * @param $text string The link text; spaces, prefixes, and an
* initial ':' indicating the main namespace are accepted.
- * @param $defaultNamespace int The namespace to use if none is speci-
+ * @param $defaultNamespace int The namespace to use if none is speci-
* fied by a prefix. If you want to force a specific namespace even if
* $text might begin with a namespace prefix, use makeTitle() or
* makeTitleSafe().
* @return Title The new object, or null on an error.
*/
public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
- if( is_object( $text ) ) {
+ if ( is_object( $text ) ) {
throw new MWException( 'Title::newFromText given an object' );
}
@@ -114,26 +134,26 @@ class Title {
*
* In theory these are value objects and won't get changed...
*/
- if( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) {
+ if ( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) {
return Title::$titleCache[$text];
}
/**
- * Convert things like &eacute; &#257; or &#x3017; into real text...
+ * Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
*/
- $filteredText = Sanitizer::decodeCharReferences( $text );
+ $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
$t = new Title();
$t->mDbkeyform = str_replace( ' ', '_', $filteredText );
$t->mDefaultNamespace = $defaultNamespace;
static $cachedcount = 0 ;
- if( $t->secureAndSplit() ) {
- if( $defaultNamespace == NS_MAIN ) {
- if( $cachedcount >= self::CACHE_MAX ) {
+ if ( $t->secureAndSplit() ) {
+ if ( $defaultNamespace == NS_MAIN ) {
+ if ( $cachedcount >= self::CACHE_MAX ) {
# Avoid memory leaks on mass operations...
Title::$titleCache = array();
- $cachedcount=0;
+ $cachedcount = 0;
}
$cachedcount++;
Title::$titleCache[$text] =& $t;
@@ -156,6 +176,7 @@ class Title {
*
* Create a new Title from URL-encoded text. Ensures that
* the given title's length does not exceed the maximum.
+ *
* @param $url \type{\string} the title, as might be taken from a URL
* @return \type{Title} the new object, or NULL on an error
*/
@@ -171,7 +192,7 @@ class Title {
}
$t->mDbkeyform = str_replace( ' ', '_', $url );
- if( $t->secureAndSplit() ) {
+ if ( $t->secureAndSplit() ) {
return $t;
} else {
return null;
@@ -182,13 +203,13 @@ class Title {
* Create a new Title from an article ID
*
* @param $id \type{\int} the page_id corresponding to the Title to create
- * @param $flags \type{\int} use GAID_FOR_UPDATE to use master
+ * @param $flags \type{\int} use Title::GAID_FOR_UPDATE to use master
* @return \type{Title} the new object, or NULL on an error
*/
public static function newFromID( $id, $flags = 0 ) {
- $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+ $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
$row = $db->selectRow( 'page', '*', array( 'page_id' => $id ), __METHOD__ );
- if( $row !== false ) {
+ if ( $row !== false ) {
$title = Title::newFromRow( $row );
} else {
$title = null;
@@ -198,6 +219,7 @@ class Title {
/**
* Make an array of titles from an array of IDs
+ *
* @param $ids \type{\arrayof{\int}} Array of IDs
* @return \type{\arrayof{Title}} Array of Titles
*/
@@ -206,28 +228,37 @@ class Title {
return array();
}
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'page', array( 'page_namespace', 'page_title' ),
- 'page_id IN (' . $dbr->makeList( $ids ) . ')', __METHOD__ );
+
+ $res = $dbr->select(
+ 'page',
+ array(
+ 'page_namespace', 'page_title', 'page_id',
+ 'page_len', 'page_is_redirect', 'page_latest',
+ ),
+ array( 'page_id' => $ids ),
+ __METHOD__
+ );
$titles = array();
- foreach( $res as $row ) {
- $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
+ foreach ( $res as $row ) {
+ $titles[] = Title::newFromRow( $row );
}
return $titles;
}
/**
* Make a Title object from a DB row
+ *
* @param $row \type{Row} (needs at least page_title,page_namespace)
* @return \type{Title} corresponding Title
*/
public static function newFromRow( $row ) {
$t = self::makeTitle( $row->page_namespace, $row->page_title );
- $t->mArticleID = isset($row->page_id) ? intval($row->page_id) : -1;
- $t->mLength = isset($row->page_len) ? intval($row->page_len) : -1;
- $t->mRedirect = isset($row->page_is_redirect) ? (bool)$row->page_is_redirect : null;
- $t->mLatestID = isset($row->page_latest) ? $row->page_latest : false;
+ $t->mArticleID = isset( $row->page_id ) ? intval( $row->page_id ) : -1;
+ $t->mLength = isset( $row->page_len ) ? intval( $row->page_len ) : -1;
+ $t->mRedirect = isset( $row->page_is_redirect ) ? (bool)$row->page_is_redirect : null;
+ $t->mLatestID = isset( $row->page_latest ) ? intval( $row->page_latest ) : false;
return $t;
}
@@ -242,11 +273,12 @@ class Title {
* @param $ns \type{\int} the namespace of the article
* @param $title \type{\string} the unprefixed database key form
* @param $fragment \type{\string} The link fragment (after the "#")
+ * @param $interwiki \type{\string} The interwiki prefix
* @return \type{Title} the new object
*/
- public static function &makeTitle( $ns, $title, $fragment = '' ) {
+ public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
$t = new Title();
- $t->mInterwiki = '';
+ $t->mInterwiki = $interwiki;
$t->mFragment = $fragment;
$t->mNamespace = $ns = intval( $ns );
$t->mDbkeyform = str_replace( ' ', '_', $title );
@@ -264,12 +296,13 @@ class Title {
* @param $ns \type{\int} the namespace of the article
* @param $title \type{\string} the database key form
* @param $fragment \type{\string} The link fragment (after the "#")
+ * @param $interwiki \type{\string} The interwiki prefix
* @return \type{Title} the new object, or NULL on an error
*/
- public static function makeTitleSafe( $ns, $title, $fragment = '' ) {
+ public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
$t = new Title();
- $t->mDbkeyform = Title::makeName( $ns, $title, $fragment );
- if( $t->secureAndSplit() ) {
+ $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki );
+ if ( $t->secureAndSplit() ) {
return $t;
} else {
return null;
@@ -278,6 +311,7 @@ class Title {
/**
* Create a new Title for the Main Page
+ *
* @return \type{Title} the new object
*/
public static function newMainPage() {
@@ -295,8 +329,8 @@ class Title {
* This will only return the very next target, useful for
* the redirect table and other checks that don't need full recursion
*
- * @param $text \type{\string} Text with possible redirect
- * @return \type{Title} The corresponding Title
+ * @param $text String: Text with possible redirect
+ * @return Title: The corresponding Title
*/
public static function newFromRedirect( $text ) {
return self::newFromRedirectInternal( $text );
@@ -328,23 +362,25 @@ class Title {
public static function newFromRedirectArray( $text ) {
global $wgMaxRedirects;
// are redirects disabled?
- if( $wgMaxRedirects < 1 )
+ if ( $wgMaxRedirects < 1 ) {
return null;
+ }
$title = self::newFromRedirectInternal( $text );
- if( is_null( $title ) )
+ if ( is_null( $title ) ) {
return null;
+ }
// recursive check to follow double redirects
$recurse = $wgMaxRedirects;
$titles = array( $title );
- while( --$recurse > 0 ) {
- if( $title->isRedirect() ) {
+ while ( --$recurse > 0 ) {
+ if ( $title->isRedirect() ) {
$article = new Article( $title, 0 );
$newtitle = $article->getRedirectTarget();
} else {
break;
}
// Redirects to some special pages are not permitted
- if( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
+ if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
// the new title passes the checks, so make that our current title so that further recursion can be checked
$title = $newtitle;
$titles[] = $newtitle;
@@ -364,16 +400,16 @@ class Title {
*/
protected static function newFromRedirectInternal( $text ) {
$redir = MagicWord::get( 'redirect' );
- $text = trim($text);
- if( $redir->matchStartAndRemove( $text ) ) {
+ $text = trim( $text );
+ if ( $redir->matchStartAndRemove( $text ) ) {
// Extract the first link and see if it's usable
// Ensure that it really does come directly after #REDIRECT
// Some older redirects included a colon, so don't freak about that!
$m = array();
- if( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
+ if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
// Strip preceding colon used to "escape" categories, etc.
// and URL-decode links
- if( strpos( $m[1], '%' ) !== false ) {
+ if ( strpos( $m[1], '%' ) !== false ) {
// Match behavior of inline link parsing here;
// don't interpret + as " " most of the time!
// It might be safe to just use rawurldecode instead, though.
@@ -381,7 +417,7 @@ class Title {
}
$title = Title::newFromText( $m[1] );
// If the title is a redirect to bad special pages or is invalid, return null
- if( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
+ if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
return null;
}
return $title;
@@ -390,12 +426,13 @@ class Title {
return null;
}
-#----------------------------------------------------------------------------
+# ----------------------------------------------------------------------------
# Static functions
-#----------------------------------------------------------------------------
+# ----------------------------------------------------------------------------
/**
* Get the prefixed DB key associated with an ID
+ *
* @param $id \type{\int} the page_id of the article
* @return \type{Title} an object representing the article, or NULL
* if no such article was found
@@ -403,11 +440,15 @@ class Title {
public static function nameOf( $id ) {
$dbr = wfGetDB( DB_SLAVE );
- $s = $dbr->selectRow( 'page',
- array( 'page_namespace','page_title' ),
+ $s = $dbr->selectRow(
+ 'page',
+ array( 'page_namespace', 'page_title' ),
array( 'page_id' => $id ),
- __METHOD__ );
- if ( $s === false ) { return null; }
+ __METHOD__
+ );
+ if ( $s === false ) {
+ return null;
+ }
$n = self::makeName( $s->page_namespace, $s->page_title );
return $n;
@@ -415,6 +456,7 @@ class Title {
/**
* Get a regex character class describing the legal characters in a link
+ *
* @return \type{\string} the list of characters, not delimited
*/
public static function legalChars() {
@@ -453,16 +495,21 @@ class Title {
/**
* Make a prefixed DB key from a DB key and a namespace index
+ *
* @param $ns \type{\int} numerical representation of the namespace
* @param $title \type{\string} the DB key form the title
* @param $fragment \type{\string} The link fragment (after the "#")
+ * @param $interwiki \type{\string} The interwiki prefix
* @return \type{\string} the prefixed form of the title
*/
- public static function makeName( $ns, $title, $fragment = '' ) {
+ public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
global $wgContLang;
$namespace = $wgContLang->getNsText( $ns );
$name = $namespace == '' ? $title : "$namespace:$title";
+ if ( strval( $interwiki ) != '' ) {
+ $name = "$interwiki:$name";
+ }
if ( strval( $fragment ) != '' ) {
$name .= '#' . $fragment;
}
@@ -491,14 +538,32 @@ class Title {
* @return \type{\bool} TRUE if this is transcludable
*/
public function isTrans() {
- if ($this->mInterwiki == '')
+ if ( $this->mInterwiki == '' ) {
return false;
+ }
return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
}
/**
+ * Returns the DB name of the distant wiki
+ * which owns the object.
+ *
+ * @return \type{\string} the DB name
+ */
+ public function getTransWikiID() {
+ if ( $this->mInterwiki == '' ) {
+ return false;
+ }
+
+ return Interwiki::fetch( $this->mInterwiki )->getWikiID();
+ }
+
+ /**
* Escape a text fragment, say from a link, for a URL
+ *
+ * @param $fragment string containing a URL or link fragment (after the "#")
+ * @return String: escaped string
*/
static function escapeFragmentForURL( $fragment ) {
# Note that we don't urlencode the fragment. urlencoded Unicode
@@ -508,34 +573,43 @@ class Title {
return Sanitizer::escapeId( $fragment, 'noninitial' );
}
-#----------------------------------------------------------------------------
+# ----------------------------------------------------------------------------
# Other stuff
-#----------------------------------------------------------------------------
+# ----------------------------------------------------------------------------
/** Simple accessors */
/**
* Get the text form (spaces not underscores) of the main part
+ *
* @return \type{\string} Main part of the title
*/
public function getText() { return $this->mTextform; }
+
/**
* Get the URL-encoded form of the main part
+ *
* @return \type{\string} Main part of the title, URL-encoded
*/
public function getPartialURL() { return $this->mUrlform; }
+
/**
* Get the main part with underscores
- * @return \type{\string} Main part of the title, with underscores
+ *
+ * @return String: Main part of the title, with underscores
*/
public function getDBkey() { return $this->mDbkeyform; }
+
/**
* Get the namespace index, i.e.\ one of the NS_xxxx constants.
- * @return \type{\int} Namespace index
+ *
+ * @return Integer: Namespace index
*/
public function getNamespace() { return $this->mNamespace; }
+
/**
* Get the namespace text
- * @return \type{\string} Namespace text
+ *
+ * @return String: Namespace text
*/
public function getNsText() {
global $wgContLang;
@@ -547,52 +621,65 @@ class Title {
//
// Use the canonical namespaces if possible to try to
// resolve a foreign namespace.
- if( MWNamespace::exists( $this->mNamespace ) ) {
+ if ( MWNamespace::exists( $this->mNamespace ) ) {
return MWNamespace::getCanonicalName( $this->mNamespace );
}
}
return $wgContLang->getNsText( $this->mNamespace );
}
+
/**
* Get the DB key with the initial letter case as specified by the user
+ *
* @return \type{\string} DB key
*/
function getUserCaseDBKey() {
return $this->mUserCaseDBKey;
}
+
/**
* Get the namespace text of the subject (rather than talk) page
+ *
* @return \type{\string} Namespace text
*/
public function getSubjectNsText() {
global $wgContLang;
return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
}
+
/**
* Get the namespace text of the talk page
+ *
* @return \type{\string} Namespace text
*/
public function getTalkNsText() {
global $wgContLang;
return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) );
}
+
/**
* Could this title have a corresponding talk page?
+ *
* @return \type{\bool} TRUE or FALSE
*/
public function canTalk() {
return( MWNamespace::canTalk( $this->mNamespace ) );
}
+
/**
* Get the interwiki prefix (or null string)
+ *
* @return \type{\string} Interwiki prefix
*/
public function getInterwiki() { return $this->mInterwiki; }
+
/**
* Get the Title fragment (i.e.\ the bit after the #) in text form
+ *
* @return \type{\string} Title fragment
*/
public function getFragment() { return $this->mFragment; }
+
/**
* Get the fragment in URL form, including the "#" character if there is one
* @return \type{\string} Fragment in URL form
@@ -604,14 +691,17 @@ class Title {
return '#' . Title::escapeFragmentForURL( $this->mFragment );
}
}
+
/**
* Get the default namespace index, for when there is no namespace
+ *
* @return \type{\int} Default namespace index
*/
public function getDefaultNamespace() { return $this->mDefaultNamespace; }
/**
* Get title for search index
+ *
* @return \type{\string} a stripped-down title string ready for the
* search index
*/
@@ -621,6 +711,7 @@ class Title {
/**
* Get the prefixed database key form
+ *
* @return \type{\string} the prefixed title, with underscores and
* any interwiki and namespace prefixes
*/
@@ -633,6 +724,7 @@ class Title {
/**
* Get the prefixed title with spaces.
* This is the form usually used for display
+ *
* @return \type{\string} the prefixed title, with spaces
*/
public function getPrefixedText() {
@@ -647,12 +739,13 @@ class Title {
/**
* Get the prefixed title with spaces, plus any fragment
* (part beginning with '#')
+ *
* @return \type{\string} the prefixed title, with spaces and
* the fragment, including '#'
*/
public function getFullText() {
$text = $this->getPrefixedText();
- if( $this->mFragment != '' ) {
+ if ( $this->mFragment != '' ) {
$text .= '#' . $this->mFragment;
}
return $text;
@@ -660,34 +753,38 @@ class Title {
/**
* Get the base name, i.e. the leftmost parts before the /
+ *
* @return \type{\string} Base name
*/
public function getBaseText() {
- if( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
+ if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
return $this->getText();
}
$parts = explode( '/', $this->getText() );
# Don't discard the real title if there's no subpage involved
- if( count( $parts ) > 1 )
- unset( $parts[ count( $parts ) - 1 ] );
+ if ( count( $parts ) > 1 ) {
+ unset( $parts[count( $parts ) - 1] );
+ }
return implode( '/', $parts );
}
/**
* Get the lowest-level subpage name, i.e. the rightmost part after /
+ *
* @return \type{\string} Subpage name
*/
public function getSubpageText() {
- if( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
+ if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
return( $this->mTextform );
}
$parts = explode( '/', $this->mTextform );
- return( $parts[ count( $parts ) - 1 ] );
+ return( $parts[count( $parts ) - 1] );
}
/**
* Get a URL-encoded form of the subpage text
+ *
* @return \type{\string} URL-encoded subpage name
*/
public function getSubpageUrlForm() {
@@ -698,6 +795,7 @@ class Title {
/**
* Get a URL-encoded title (not an actual URL) including interwiki
+ *
* @return \type{\string} the URL-encoded form
*/
public function getPrefixedURL() {
@@ -717,9 +815,9 @@ class Title {
* @return \type{\string} the URL
*/
public function getFullURL( $query = '', $variant = false ) {
- global $wgContLang, $wgServer, $wgRequest;
+ global $wgServer, $wgRequest;
- if( is_array( $query ) ) {
+ if ( is_array( $query ) ) {
$query = wfArrayToCGI( $query );
}
@@ -729,11 +827,11 @@ class Title {
// Ugly quick hack to avoid duplicate prefixes (bug 4571 etc)
// Correct fix would be to move the prepending elsewhere.
- if ($wgRequest->getVal('action') != 'render') {
+ if ( $wgRequest->getVal( 'action' ) != 'render' ) {
$url = $wgServer . $url;
}
} else {
- $baseUrl = $interwiki->getURL( );
+ $baseUrl = $interwiki->getURL();
$namespace = wfUrlencode( $this->getNsText() );
if ( $namespace != '' ) {
@@ -755,7 +853,8 @@ class Title {
/**
* Get a URL with no fragment or server name. If this page is generated
* with action=render, $wgServer is prepended.
- * @param mixed $query an optional query string; if not specified,
+ *
+ * @param $query Mixed: an optional query string; if not specified,
* $wgArticlePath will be used. Can be specified as an associative array
* as well, e.g., array( 'action' => 'edit' ) (keys and values will be
* URL-escaped).
@@ -764,19 +863,12 @@ class Title {
*/
public function getLocalURL( $query = '', $variant = false ) {
global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
- global $wgVariantArticlePath, $wgContLang, $wgUser;
+ global $wgVariantArticlePath, $wgContLang;
- if( is_array( $query ) ) {
+ if ( is_array( $query ) ) {
$query = wfArrayToCGI( $query );
}
- // internal links should point to same variant as current page (only anonymous users)
- if($variant == false && $wgContLang->hasVariants() && !$wgUser->isLoggedIn()){
- $pref = $wgContLang->getPreferredVariant(false);
- if($pref != $wgContLang->getCode())
- $variant = $pref;
- }
-
if ( $this->isExternal() ) {
$url = $this->getFullURL();
if ( $query ) {
@@ -789,8 +881,8 @@ class Title {
} else {
$dbkey = wfUrlencode( $this->getPrefixedDBkey() );
if ( $query == '' ) {
- if( $variant != false && $wgContLang->hasVariants() ) {
- if( $wgVariantArticlePath == false ) {
+ if ( $variant != false && $wgContLang->hasVariants() ) {
+ if ( !$wgVariantArticlePath ) {
$variantArticlePath = "$wgScript?title=$1&variant=$2"; // default
} else {
$variantArticlePath = $wgVariantArticlePath;
@@ -804,15 +896,17 @@ class Title {
global $wgActionPaths;
$url = false;
$matches = array();
- if( !empty( $wgActionPaths ) &&
+ if ( !empty( $wgActionPaths ) &&
preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
{
$action = urldecode( $matches[2] );
- if( isset( $wgActionPaths[$action] ) ) {
+ if ( isset( $wgActionPaths[$action] ) ) {
$query = $matches[1];
- if( isset( $matches[4] ) ) $query .= $matches[4];
+ if ( isset( $matches[4] ) ) {
+ $query .= $matches[4];
+ }
$url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
- if( $query != '' ) {
+ if ( $query != '' ) {
$url = wfAppendQuery( $url, $query );
}
}
@@ -827,7 +921,7 @@ class Title {
// FIXME: this causes breakage in various places when we
// actually expected a local URL and end up with dupe prefixes.
- if ($wgRequest->getVal('action') == 'render') {
+ if ( $wgRequest->getVal( 'action' ) == 'render' ) {
$url = $wgServer . $url;
}
}
@@ -854,9 +948,9 @@ class Title {
*/
public function getLinkUrl( $query = array(), $variant = false ) {
wfProfileIn( __METHOD__ );
- if( $this->isExternal() ) {
+ if ( $this->isExternal() ) {
$ret = $this->getFullURL( $query );
- } elseif( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
+ } elseif ( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
$ret = $this->getFragmentForURL();
} else {
$ret = $this->getLocalURL( $query, $variant ) . $this->getFragmentForURL();
@@ -868,6 +962,7 @@ class Title {
/**
* Get an HTML-escaped version of the URL form, suitable for
* using in a link, without a server name or fragment
+ *
* @param $query \type{\string} an optional query string
* @return \type{\string} the URL
*/
@@ -904,11 +999,14 @@ class Title {
/**
* Get the edit URL for this Title
+ *
* @return \type{\string} the URL, or a null string if this is an
* interwiki link
*/
public function getEditURL() {
- if ( $this->mInterwiki != '' ) { return ''; }
+ if ( $this->mInterwiki != '' ) {
+ return '';
+ }
$s = $this->getLocalURL( 'action=edit' );
return $s;
@@ -917,6 +1015,7 @@ class Title {
/**
* Get the HTML-escaped displayable text form.
* Used for the title field in <a> tags.
+ *
* @return \type{\string} the text, including any prefixes
*/
public function getEscapedText() {
@@ -925,23 +1024,27 @@ class Title {
/**
* Is this Title interwiki?
+ *
* @return \type{\bool}
*/
- public function isExternal() { return ( $this->mInterwiki != '' ); }
+ public function isExternal() {
+ return ( $this->mInterwiki != '' );
+ }
/**
* Is this page "semi-protected" - the *only* protection is autoconfirm?
*
- * @param @action \type{\string} Action to check (default: edit)
+ * @param $action \type{\string} Action to check (default: edit)
* @return \type{\bool}
*/
public function isSemiProtected( $action = 'edit' ) {
- if( $this->exists() ) {
+ if ( $this->exists() ) {
$restrictions = $this->getRestrictions( $action );
- if( count( $restrictions ) > 0 ) {
- foreach( $restrictions as $restriction ) {
- if( strtolower( $restriction ) != 'autoconfirmed' )
+ if ( count( $restrictions ) > 0 ) {
+ foreach ( $restrictions as $restriction ) {
+ if ( strtolower( $restriction ) != 'autoconfirmed' ) {
return false;
+ }
}
} else {
# Not protected
@@ -956,7 +1059,8 @@ class Title {
/**
* Does the title correspond to a protected article?
- * @param $what \type{\string} the action the page is protected from,
+ *
+ * @param $action \type{\string} the action the page is protected from,
* by default checks all actions.
* @return \type{\bool}
*/
@@ -966,15 +1070,16 @@ class Title {
$restrictionTypes = $this->getRestrictionTypes();
# Special pages have inherent protection
- if( $this->getNamespace() == NS_SPECIAL )
+ if( $this->getNamespace() == NS_SPECIAL ) {
return true;
+ }
# Check regular protection levels
- foreach( $restrictionTypes as $type ){
- if( $action == $type || $action == '' ) {
+ foreach ( $restrictionTypes as $type ) {
+ if ( $action == $type || $action == '' ) {
$r = $this->getRestrictions( $type );
- foreach( $wgRestrictionLevels as $level ) {
- if( in_array( $level, $r ) && $level != '' ) {
+ foreach ( $wgRestrictionLevels as $level ) {
+ if ( in_array( $level, $r ) && $level != '' ) {
return true;
}
}
@@ -986,11 +1091,15 @@ class Title {
/**
* Is this a conversion table for the LanguageConverter?
+ *
* @return \type{\bool}
*/
public function isConversionTable() {
- if($this->getNamespace() == NS_MEDIAWIKI
- && strpos( $this->getText(), 'Conversiontable' ) !== false ) {
+ if(
+ $this->getNamespace() == NS_MEDIAWIKI &&
+ strpos( $this->getText(), 'Conversiontable' ) !== false
+ )
+ {
return true;
}
@@ -999,13 +1108,14 @@ class Title {
/**
* Is $wgUser watching this page?
+ *
* @return \type{\bool}
*/
public function userIsWatching() {
global $wgUser;
if ( is_null( $this->mWatched ) ) {
- if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn()) {
+ if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn() ) {
$this->mWatched = false;
} else {
$this->mWatched = $wgUser->isWatched( $this );
@@ -1039,10 +1149,11 @@ class Title {
*/
public function isNamespaceProtected() {
global $wgNamespaceProtection, $wgUser;
- if( isset( $wgNamespaceProtection[ $this->mNamespace ] ) ) {
- foreach( (array)$wgNamespaceProtection[ $this->mNamespace ] as $right ) {
- if( $right != '' && !$wgUser->isAllowed( $right ) )
+ if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
+ foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
+ if ( $right != '' && !$wgUser->isAllowed( $right ) ) {
return true;
+ }
}
}
return false;
@@ -1050,13 +1161,14 @@ class Title {
/**
* Can $wgUser perform $action on this page?
+ *
* @param $action \type{\string} action that permission needs to be checked for
* @param $doExpensiveQueries \type{\bool} Set this to false to avoid doing unnecessary queries.
* @return \type{\bool}
*/
public function userCan( $action, $doExpensiveQueries = true ) {
global $wgUser;
- return ($this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries, true ) === array());
+ return ( $this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries, true ) === array() );
}
/**
@@ -1071,79 +1183,14 @@ class Title {
* @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems.
*/
public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
- if( !StubObject::isRealObject( $user ) ) {
- //Since StubObject is always used on globals, we can unstub $wgUser here and set $user = $wgUser
- global $wgUser;
- $wgUser->_unstub( '', 5 );
- $user = $wgUser;
- }
$errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
- global $wgContLang;
- global $wgLang;
- global $wgEmailConfirmToEdit;
-
- if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' ) {
- $errors[] = array( 'confirmedittext' );
- }
-
- // Edit blocks should not affect reading. Account creation blocks handled at userlogin.
- if ( $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) ) {
- $block = $user->mBlock;
-
- // This is from OutputPage::blockedPage
- // Copied at r23888 by werdna
-
- $id = $user->blockedBy();
- $reason = $user->blockedFor();
- if( $reason == '' ) {
- $reason = wfMsg( 'blockednoreason' );
- }
- $ip = wfGetIP();
-
- if ( is_numeric( $id ) ) {
- $name = User::whoIs( $id );
- } else {
- $name = $id;
- }
-
- $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
- $blockid = $block->mId;
- $blockExpiry = $user->mBlock->mExpiry;
- $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
-
- if ( $blockExpiry == 'infinity' ) {
- // Entry in database (table ipblocks) is 'infinity' but 'ipboptions' uses 'infinite' or 'indefinite'
- $scBlockExpiryOptions = wfMsg( 'ipboptions' );
-
- foreach ( explode( ',', $scBlockExpiryOptions ) as $option ) {
- if ( strpos( $option, ':' ) == false )
- continue;
-
- list ($show, $value) = explode( ":", $option );
-
- if ( $value == 'infinite' || $value == 'indefinite' ) {
- $blockExpiry = $show;
- break;
- }
- }
- } else {
- $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
- }
-
- $intended = $user->mBlock->mAddress;
-
- $errors[] = array( ($block->mAuto ? 'autoblockedtext' : 'blockedtext'), $link, $reason, $ip, $name,
- $blockid, $blockExpiry, $intended, $blockTimestamp );
- }
-
// Remove the errors being ignored.
+ foreach ( $errors as $index => $error ) {
+ $error_key = is_array( $error ) ? $error[0] : $error;
- foreach( $errors as $index => $error ) {
- $error_key = is_array($error) ? $error[0] : $error;
-
- if (in_array( $error_key, $ignoreErrors )) {
- unset($errors[$index]);
+ if ( in_array( $error_key, $ignoreErrors ) ) {
+ unset( $errors[$index] );
}
}
@@ -1151,36 +1198,35 @@ class Title {
}
/**
- * Can $user perform $action on this page? This is an internal function,
- * which checks ONLY that previously checked by userCan (i.e. it leaves out
- * checks on wfReadOnly() and blocks)
+ * Permissions checks that fail most often, and which are easiest to test.
*
- * @param $action \type{\string} action that permission needs to be checked for
- * @param $user \type{User} user to check
- * @param $doExpensiveQueries \type{\bool} Set this to false to avoid doing unnecessary queries.
- * @param $short \type{\bool} Set this to true to stop after the first permission error.
- * @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems.
+ * @param $action String the action to check
+ * @param $user User user to check
+ * @param $errors Array list of current errors
+ * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
+ * @param $short Boolean short circuit on first error
+ *
+ * @return Array list of errors
*/
- private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries=true, $short=false ) {
- wfProfileIn( __METHOD__ );
-
- $errors = array();
-
- // First stop is permissions checks, which fail most often, and which are easiest to test.
- if ( $action == 'move' ) {
- if( !$user->isAllowed( 'move-rootuserpages' )
- && $this->getNamespace() == NS_USER && !$this->isSubpage() )
- {
+ private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ if ( $action == 'create' ) {
+ if ( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
+ ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) {
+ $errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' );
+ }
+ } elseif ( $action == 'move' ) {
+ if ( !$user->isAllowed( 'move-rootuserpages' )
+ && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
// Show user page-specific message only if the user can move other pages
$errors[] = array( 'cant-move-user-page' );
}
// Check if user is allowed to move files if it's a file
- if( $this->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
+ if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
$errors[] = array( 'movenotallowedfile' );
}
- if( !$user->isAllowed( 'move' ) ) {
+ if ( !$user->isAllowed( 'move' ) ) {
// User can't move anything
global $wgGroupPermissions;
$userCanMove = false;
@@ -1193,135 +1239,183 @@ class Title {
}
if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
// custom message if logged-in users without any special rights can move
- $errors[] = array ( 'movenologintext' );
+ $errors[] = array( 'movenologintext' );
} else {
- $errors[] = array ('movenotallowed');
+ $errors[] = array( 'movenotallowed' );
}
}
- } elseif ( $action == 'create' ) {
- if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
- ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) )
- {
- $errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin');
- }
- } elseif( $action == 'move-target' ) {
- if( !$user->isAllowed( 'move' ) ) {
+ } elseif ( $action == 'move-target' ) {
+ if ( !$user->isAllowed( 'move' ) ) {
// User can't move anything
- $errors[] = array ('movenotallowed');
- } elseif( !$user->isAllowed( 'move-rootuserpages' )
- && $this->getNamespace() == NS_USER && !$this->isSubpage() )
- {
+ $errors[] = array( 'movenotallowed' );
+ } elseif ( !$user->isAllowed( 'move-rootuserpages' )
+ && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
// Show user page-specific message only if the user can move other pages
$errors[] = array( 'cant-move-to-user-page' );
}
- } elseif( !$user->isAllowed( $action ) ) {
- $return = null;
-
+ } elseif ( !$user->isAllowed( $action ) ) {
// We avoid expensive display logic for quickUserCan's and such
$groups = false;
- if (!$short) {
+ if ( !$short ) {
$groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
User::getGroupsWithPermission( $action ) );
}
- if( $groups ) {
- $return = array( 'badaccess-groups',
- array( implode( ', ', $groups ), count( $groups ) ) );
+ if ( $groups ) {
+ global $wgLang;
+ $return = array(
+ 'badaccess-groups',
+ $wgLang->commaList( $groups ),
+ count( $groups )
+ );
} else {
- $return = array( "badaccess-group0" );
+ $return = array( 'badaccess-group0' );
}
$errors[] = $return;
}
- # Short-circuit point
- if( $short && count($errors) > 0 ) {
- wfProfileOut( __METHOD__ );
- return $errors;
+ return $errors;
+ }
+
+ /**
+ * Add the resulting error code to the errors array
+ *
+ * @param $errors Array list of current errors
+ * @param $result Mixed result of errors
+ *
+ * @return Array list of errors
+ */
+ private function resultToError( $errors, $result ) {
+ if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
+ // A single array representing an error
+ $errors[] = $result;
+ } else if ( is_array( $result ) && is_array( $result[0] ) ) {
+ // A nested array representing multiple errors
+ $errors = array_merge( $errors, $result );
+ } else if ( $result !== '' && is_string( $result ) ) {
+ // A string representing a message-id
+ $errors[] = array( $result );
+ } else if ( $result === false ) {
+ // a generic "We don't want them to do that"
+ $errors[] = array( 'badaccess-group0' );
}
+ return $errors;
+ }
+ /**
+ * Check various permission hooks
+ *
+ * @param $action String the action to check
+ * @param $user User user to check
+ * @param $errors Array list of current errors
+ * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
+ * @param $short Boolean short circuit on first error
+ *
+ * @return Array list of errors
+ */
+ private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
// Use getUserPermissionsErrors instead
- if( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
- wfProfileOut( __METHOD__ );
+ $result = '';
+ if ( !wfRunHooks( '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( is_array($result) && count($result) && !is_array($result[0]) )
- $errors[] = $result; # A single array representing an error
- else if( is_array($result) && is_array($result[0]) )
- $errors = array_merge( $errors, $result ); # A nested array representing multiple errors
- else if( $result !== '' && is_string($result) )
- $errors[] = array($result); # A string representing a message-id
- else if( $result === false )
- $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
- }
- # Short-circuit point
- if( $short && count($errors) > 0 ) {
- wfProfileOut( __METHOD__ );
- return $errors;
+ if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
+ $errors = $this->resultToError( $errors, $result );
}
// Check getUserPermissionsErrorsExpensive hook
- if( $doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array(&$this,&$user,$action,&$result) ) ) {
- if( is_array($result) && count($result) && !is_array($result[0]) )
- $errors[] = $result; # A single array representing an error
- else if( is_array($result) && is_array($result[0]) )
- $errors = array_merge( $errors, $result ); # A nested array representing multiple errors
- else if( $result !== '' && is_string($result) )
- $errors[] = array($result); # A string representing a message-id
- else if( $result === false )
- $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
- }
- # Short-circuit point
- if( $short && count($errors) > 0 ) {
- wfProfileOut( __METHOD__ );
- return $errors;
+ if ( $doExpensiveQueries && !( $short && count( $errors ) > 0 ) &&
+ !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) ) ) {
+ $errors = $this->resultToError( $errors, $result );
}
+ return $errors;
+ }
+
+ /**
+ * Check permissions on special pages & namespaces
+ *
+ * @param $action String the action to check
+ * @param $user User user to check
+ * @param $errors Array list of current errors
+ * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
+ * @param $short Boolean short circuit on first error
+ *
+ * @return Array list of errors
+ */
+ private function checkSpecialsAndNSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
# Only 'createaccount' and 'execute' can be performed on
# special pages, which don't actually exist in the DB.
$specialOKActions = array( 'createaccount', 'execute' );
- if( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions) ) {
- $errors[] = array('ns-specialprotected');
+ if ( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions ) ) {
+ $errors[] = array( 'ns-specialprotected' );
}
# Check $wgNamespaceProtection for restricted namespaces
- if( $this->isNamespaceProtected() ) {
- $ns = $this->getNamespace() == NS_MAIN ?
+ if ( $this->isNamespaceProtected() ) {
+ $ns = $this->mNamespace == NS_MAIN ?
wfMsg( 'nstab-main' ) : $this->getNsText();
- $errors[] = NS_MEDIAWIKI == $this->mNamespace ?
- array('protectedinterface') : array( 'namespaceprotected', $ns );
+ $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
+ array( 'protectedinterface' ) : array( 'namespaceprotected', $ns );
}
+ return $errors;
+ }
+
+ /**
+ * Check CSS/JS sub-page permissions
+ *
+ * @param $action String the action to check
+ * @param $user User user to check
+ * @param $errors Array list of current errors
+ * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
+ * @param $short Boolean short circuit on first error
+ *
+ * @return Array list of errors
+ */
+ private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
# Protect css/js subpages of user pages
# XXX: this might be better using restrictions
# XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssSubpage()
# and $this->userCanEditJsSubpage() from working
# XXX: right 'editusercssjs' is deprecated, for backward compatibility only
- if( $this->isCssSubpage() && !( $user->isAllowed('editusercssjs') || $user->isAllowed('editusercss') )
- && $action != 'patrol'
- && !preg_match('/^'.preg_quote($user->getName(), '/').'\//', $this->mTextform) )
- {
- $errors[] = array('customcssjsprotected');
- } else if( $this->isJsSubpage() && !( $user->isAllowed('editusercssjs') || $user->isAllowed('edituserjs') )
- && $action != 'patrol'
- && !preg_match('/^'.preg_quote($user->getName(), '/').'\//', $this->mTextform) )
- {
- $errors[] = array('customcssjsprotected');
+ if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' )
+ && !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
+ if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
+ $errors[] = array( 'customcssjsprotected' );
+ } else if ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
+ $errors[] = array( 'customcssjsprotected' );
+ }
}
- # Check against page_restrictions table requirements on this
- # page. The user must possess all required rights for this action.
- foreach( $this->getRestrictions($action) as $right ) {
+ return $errors;
+ }
+
+ /**
+ * Check against page_restrictions table requirements on this
+ * page. The user must possess all required rights for this
+ * action.
+ *
+ * @param $action String the action to check
+ * @param $user User user to check
+ * @param $errors Array list of current errors
+ * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
+ * @param $short Boolean short circuit on first error
+ *
+ * @return Array list of errors
+ */
+ private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ foreach ( $this->getRestrictions( $action ) as $right ) {
// Backwards compatibility, rewrite sysop -> protect
- if( $right == 'sysop' ) {
+ if ( $right == 'sysop' ) {
$right = 'protect';
}
- if( $right != '' && !$user->isAllowed( $right ) ) {
+ if ( $right != '' && !$user->isAllowed( $right ) ) {
// Users with 'editprotected' permission can edit protected pages
- if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
+ if ( $action == 'edit' && $user->isAllowed( 'editprotected' ) ) {
// Users with 'editprotected' permission cannot edit protected pages
// with cascading option turned on.
- if( $this->mCascadeRestriction ) {
+ if ( $this->mCascadeRestriction ) {
$errors[] = array( 'protectedpagetext', $right );
}
} else {
@@ -1329,74 +1423,200 @@ class Title {
}
}
}
- # Short-circuit point
- if( $short && count($errors) > 0 ) {
- wfProfileOut( __METHOD__ );
- return $errors;
- }
- if( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
- # We /could/ use the protection level on the source page, but it's fairly ugly
- # as we have to establish a precedence hierarchy for pages included by multiple
- # cascade-protected pages. So just restrict it to people with 'protect' permission,
- # as they could remove the protection anyway.
+ return $errors;
+ }
+
+ /**
+ * Check restrictions on cascading pages.
+ *
+ * @param $action String the action to check
+ * @param $user User user to check
+ * @param $errors Array list of current errors
+ * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
+ * @param $short Boolean short circuit on first error
+ *
+ * @return Array list of errors
+ */
+ private function checkCascadingSourcesRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
+ # We /could/ use the protection level on the source page, but it's
+ # fairly ugly as we have to establish a precedence hierarchy for pages
+ # included by multiple cascade-protected pages. So just restrict
+ # it to people with 'protect' permission, as they could remove the
+ # protection anyway.
list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
# Cascading protection depends on more than this page...
# Several cascading protected pages may include this page...
# Check each cascading level
# This is only for protection restrictions, not for all actions
- if( $cascadingSources > 0 && isset($restrictions[$action]) ) {
- foreach( $restrictions[$action] as $right ) {
+ if ( isset( $restrictions[$action] ) ) {
+ foreach ( $restrictions[$action] as $right ) {
$right = ( $right == 'sysop' ) ? 'protect' : $right;
- if( $right != '' && !$user->isAllowed( $right ) ) {
+ if ( $right != '' && !$user->isAllowed( $right ) ) {
$pages = '';
- foreach( $cascadingSources as $page )
+ foreach ( $cascadingSources as $page )
$pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
$errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
}
}
}
}
- # Short-circuit point
- if( $short && count($errors) > 0 ) {
- wfProfileOut( __METHOD__ );
- return $errors;
- }
- if( $action == 'protect' ) {
- if( $this->getUserPermissionsErrors('edit', $user) != array() ) {
- $errors[] = array( 'protect-cantedit' ); // If they can't edit, they shouldn't protect.
- }
- }
+ return $errors;
+ }
- if( $action == 'create' ) {
+ /**
+ * Check action permissions not already checked in checkQuickPermissions
+ *
+ * @param $action String the action to check
+ * @param $user User user to check
+ * @param $errors Array list of current errors
+ * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
+ * @param $short Boolean short circuit on first error
+ *
+ * @return Array list of errors
+ */
+ private function checkActionPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ if ( $action == 'protect' ) {
+ if ( $this->getUserPermissionsErrors( 'edit', $user ) != array() ) {
+ // If they can't edit, they shouldn't protect.
+ $errors[] = array( 'protect-cantedit' );
+ }
+ } elseif ( $action == 'create' ) {
$title_protection = $this->getTitleProtection();
- if( is_array($title_protection) ) {
- extract($title_protection); // is this extract() really needed?
-
- if( $pt_create_perm == 'sysop' ) {
- $pt_create_perm = 'protect'; // B/C
+ if( $title_protection ) {
+ if( $title_protection['pt_create_perm'] == 'sysop' ) {
+ $title_protection['pt_create_perm'] = 'protect'; // B/C
}
- if( $pt_create_perm == '' || !$user->isAllowed($pt_create_perm) ) {
- $errors[] = array( 'titleprotected', User::whoIs($pt_user), $pt_reason );
+ if( $title_protection['pt_create_perm'] == '' || !$user->isAllowed( $title_protection['pt_create_perm'] ) ) {
+ $errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
}
}
- } elseif( $action == 'move' ) {
+ } elseif ( $action == 'move' ) {
// Check for immobile pages
- if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
+ if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
// Specific message for this case
$errors[] = array( 'immobile-source-namespace', $this->getNsText() );
- } elseif( !$this->isMovable() ) {
+ } elseif ( !$this->isMovable() ) {
// Less specific message for rarer cases
$errors[] = array( 'immobile-page' );
}
- } elseif( $action == 'move-target' ) {
- if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
+ } elseif ( $action == 'move-target' ) {
+ if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
$errors[] = array( 'immobile-target-namespace', $this->getNsText() );
- } elseif( !$this->isMovable() ) {
+ } elseif ( !$this->isMovable() ) {
$errors[] = array( 'immobile-target-page' );
}
}
+ return $errors;
+ }
+
+ /**
+ * Check that the user isn't blocked from editting.
+ *
+ * @param $action String the action to check
+ * @param $user User user to check
+ * @param $errors Array list of current errors
+ * @param $doExpensiveQueries Boolean whether or not to perform expensive queries
+ * @param $short Boolean short circuit on first error
+ *
+ * @return Array list of errors
+ */
+ private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ if( $short && count( $errors ) > 0 ) {
+ return $errors;
+ }
+
+ global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
+
+ if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' ) {
+ $errors[] = array( 'confirmedittext' );
+ }
+
+ // Edit blocks should not affect reading. Account creation blocks handled at userlogin.
+ if ( $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) ) {
+ $block = $user->mBlock;
+
+ // This is from OutputPage::blockedPage
+ // Copied at r23888 by werdna
+
+ $id = $user->blockedBy();
+ $reason = $user->blockedFor();
+ if ( $reason == '' ) {
+ $reason = wfMsg( 'blockednoreason' );
+ }
+ $ip = wfGetIP();
+
+ if ( is_numeric( $id ) ) {
+ $name = User::whoIs( $id );
+ } else {
+ $name = $id;
+ }
+
+ $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
+ $blockid = $block->mId;
+ $blockExpiry = $user->mBlock->mExpiry;
+ $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
+ if ( $blockExpiry == 'infinity' ) {
+ // Entry in database (table ipblocks) is 'infinity' but 'ipboptions' uses 'infinite' or 'indefinite'
+ $scBlockExpiryOptions = wfMsg( 'ipboptions' );
+
+ foreach ( explode( ',', $scBlockExpiryOptions ) as $option ) {
+ if ( !strpos( $option, ':' ) )
+ continue;
+
+ list( $show, $value ) = explode( ':', $option );
+
+ if ( $value == 'infinite' || $value == 'indefinite' ) {
+ $blockExpiry = $show;
+ break;
+ }
+ }
+ } else {
+ $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
+ }
+
+ $intended = $user->mBlock->mAddress;
+
+ $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
+ $blockid, $blockExpiry, $intended, $blockTimestamp );
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Can $user perform $action on this page? This is an internal function,
+ * which checks ONLY that previously checked by userCan (i.e. it leaves out
+ * checks on wfReadOnly() and blocks)
+ *
+ * @param $action \type{\string} action that permission needs to be checked for
+ * @param $user \type{User} user to check
+ * @param $doExpensiveQueries \type{\bool} Set this to false to avoid doing unnecessary queries.
+ * @param $short \type{\bool} Set this to true to stop after the first permission error.
+ * @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems.
+ */
+ protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) {
+ wfProfileIn( __METHOD__ );
+
+ $errors = array();
+ $checks = array(
+ 'checkQuickPermissions',
+ 'checkPermissionHooks',
+ 'checkSpecialsAndNSPermissions',
+ 'checkCSSandJSPermissions',
+ 'checkPageRestrictions',
+ 'checkCascadingSourcesRestrictions',
+ 'checkActionPermissions',
+ 'checkUserBlock'
+ );
+
+ while( count( $checks ) > 0 &&
+ !( $short && count( $errors ) > 0 ) ) {
+ $method = array_shift( $checks );
+ $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
+ }
wfProfileOut( __METHOD__ );
return $errors;
@@ -1404,6 +1624,8 @@ class Title {
/**
* Is this title subject to title protection?
+ * Title protection is the one applied against creation of such title.
+ *
* @return \type{\mixed} An associative array representing any existent title
* protection, or false if there's none.
*/
@@ -1414,75 +1636,78 @@ class Title {
}
// Can't protect pages that exist.
- if ($this->exists()) {
+ if ( $this->exists() ) {
return false;
}
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'protected_titles', '*',
- array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
- __METHOD__ );
+ if ( !isset( $this->mTitleProtection ) ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'protected_titles', '*',
+ array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
+ __METHOD__ );
- if ($row = $dbr->fetchRow( $res )) {
- return $row;
- } else {
- return false;
+ // fetchRow returns false if there are no rows.
+ $this->mTitleProtection = $dbr->fetchRow( $res );
}
+ return $this->mTitleProtection;
}
/**
* Update the title protection status
+ *
* @param $create_perm \type{\string} Permission required for creation
* @param $reason \type{\string} Reason for protection
* @param $expiry \type{\string} Expiry timestamp
+ * @return boolean true
*/
public function updateTitleProtection( $create_perm, $reason, $expiry ) {
- global $wgUser,$wgContLang;
+ global $wgUser, $wgContLang;
- if ($create_perm == implode(',',$this->getRestrictions('create'))
- && $expiry == $this->mRestrictionsExpiry['create']) {
+ if ( $create_perm == implode( ',', $this->getRestrictions( 'create' ) )
+ && $expiry == $this->mRestrictionsExpiry['create'] ) {
// No change
return true;
}
- list ($namespace, $title) = array( $this->getNamespace(), $this->getDBkey() );
+ list ( $namespace, $title ) = array( $this->getNamespace(), $this->getDBkey() );
$dbw = wfGetDB( DB_MASTER );
- $encodedExpiry = Block::encodeExpiry($expiry, $dbw );
+ $encodedExpiry = Block::encodeExpiry( $expiry, $dbw );
$expiry_description = '';
if ( $encodedExpiry != 'infinity' ) {
- $expiry_description = ' (' . wfMsgForContent( 'protect-expiring',$wgContLang->timeanddate( $expiry ),
- $wgContLang->date( $expiry ) , $wgContLang->time( $expiry ) ).')';
- }
- else {
- $expiry_description .= ' (' . wfMsgForContent( 'protect-expiry-indefinite' ).')';
+ $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ),
+ $wgContLang->date( $expiry ) , $wgContLang->time( $expiry ) ) . ')';
+ } else {
+ $expiry_description .= ' (' . wfMsgForContent( 'protect-expiry-indefinite' ) . ')';
}
# Update protection table
- if ($create_perm != '' ) {
- $dbw->replace( 'protected_titles', array(array('pt_namespace', 'pt_title')),
- array(
+ if ( $create_perm != '' ) {
+ $this->mTitleProtection = array(
'pt_namespace' => $namespace,
'pt_title' => $title,
'pt_create_perm' => $create_perm,
- 'pt_timestamp' => Block::encodeExpiry(wfTimestampNow(), $dbw),
+ 'pt_timestamp' => Block::encodeExpiry( wfTimestampNow(), $dbw ),
'pt_expiry' => $encodedExpiry,
'pt_user' => $wgUser->getId(),
'pt_reason' => $reason,
- ), __METHOD__
- );
+ );
+ $dbw->replace( 'protected_titles', array( array( 'pt_namespace', 'pt_title' ) ),
+ $this->mTitleProtection, __METHOD__ );
} else {
$dbw->delete( 'protected_titles', array( 'pt_namespace' => $namespace,
'pt_title' => $title ), __METHOD__ );
+ $this->mTitleProtection = false;
}
+
# Update the protection log
- if( $dbw->affectedRows() ) {
+ if ( $dbw->affectedRows() ) {
$log = new LogPage( 'protect' );
- if( $create_perm ) {
- $params = array("[create=$create_perm] $expiry_description",'');
+ if ( $create_perm ) {
+ $params = array( "[create=$create_perm] $expiry_description", '' );
$log->addEntry( ( isset( $this->mRestrictions['create'] ) && $this->mRestrictions['create'] ) ? 'modify' : 'protect', $this, trim( $reason ), $params );
} else {
$log->addEntry( 'unprotect', $this, $reason );
@@ -1498,9 +1723,12 @@ class Title {
public function deleteTitleProtection() {
$dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'protected_titles',
+ $dbw->delete(
+ 'protected_titles',
array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
- __METHOD__ );
+ __METHOD__
+ );
+ $this->mTitleProtection = false;
}
/**
@@ -1515,7 +1743,8 @@ class Title {
/**
* Can $wgUser read this page?
- * @return \type{\bool} TRUE or FALSE
+ *
+ * @return \type{\bool}
* @todo fold these checks into userCan()
*/
public function userCanRead() {
@@ -1524,21 +1753,21 @@ class Title {
static $useShortcut = null;
# Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
- if( is_null( $useShortcut ) ) {
+ if ( is_null( $useShortcut ) ) {
global $wgRevokePermissions;
$useShortcut = true;
- if( empty( $wgGroupPermissions['*']['read'] ) ) {
+ if ( empty( $wgGroupPermissions['*']['read'] ) ) {
# Not a public wiki, so no shortcut
$useShortcut = false;
- } elseif( !empty( $wgRevokePermissions ) ) {
+ } elseif ( !empty( $wgRevokePermissions ) ) {
/*
* Iterate through each group with permissions being revoked (key not included since we don't care
* what the group name is), then check if the read permission is being revoked. If it is, then
* we don't use the shortcut below since the user might not be able to read, even though anon
* reading is allowed.
*/
- foreach( $wgRevokePermissions as $perms ) {
- if( !empty( $perms['read'] ) ) {
+ foreach ( $wgRevokePermissions as $perms ) {
+ if ( !empty( $perms['read'] ) ) {
# We might be removing the read right from the user, so no shortcut
$useShortcut = false;
break;
@@ -1554,10 +1783,11 @@ class Title {
}
# Shortcut for public wikis, allows skipping quite a bit of code
- if ( $useShortcut )
+ if ( $useShortcut ) {
return true;
+ }
- if( $wgUser->isAllowed( 'read' ) ) {
+ if ( $wgUser->isAllowed( 'read' ) ) {
return true;
} else {
global $wgWhitelistRead;
@@ -1566,14 +1796,14 @@ class Title {
* Always grant access to the login page.
* Even anons need to be able to log in.
*/
- if( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'Resetpass' ) ) {
+ if ( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'Resetpass' ) ) {
return true;
}
/**
* Bail out if there isn't whitelist
*/
- if( !is_array($wgWhitelistRead) ) {
+ if ( !is_array( $wgWhitelistRead ) ) {
return false;
}
@@ -1583,33 +1813,35 @@ class Title {
$name = $this->getPrefixedText();
$dbName = $this->getPrefixedDBKey();
// Check with and without underscores
- if( in_array($name,$wgWhitelistRead,true) || in_array($dbName,$wgWhitelistRead,true) )
+ if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) )
return true;
/**
* Old settings might have the title prefixed with
* a colon for main-namespace pages
*/
- if( $this->getNamespace() == NS_MAIN ) {
- if( in_array( ':' . $name, $wgWhitelistRead ) )
+ if ( $this->getNamespace() == NS_MAIN ) {
+ if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
return true;
+ }
}
/**
* If it's a special page, ditch the subpage bit
* and check again
*/
- if( $this->getNamespace() == NS_SPECIAL ) {
+ if ( $this->getNamespace() == NS_SPECIAL ) {
$name = $this->getDBkey();
- list( $name, /* $subpage */) = SpecialPage::resolveAliasWithSubpage( $name );
+ list( $name, /* $subpage */ ) = SpecialPage::resolveAliasWithSubpage( $name );
if ( $name === false ) {
# Invalid special page, but we show standard login required message
return false;
}
$pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
- if( in_array( $pure, $wgWhitelistRead, true ) )
+ if ( in_array( $pure, $wgWhitelistRead, true ) ) {
return true;
+ }
}
}
@@ -1618,7 +1850,8 @@ class Title {
/**
* Is this a talk page of some sort?
- * @return \type{\bool} TRUE or FALSE
+ *
+ * @return \type{\bool}
*/
public function isTalkPage() {
return MWNamespace::isTalk( $this->getNamespace() );
@@ -1626,7 +1859,8 @@ class Title {
/**
* Is this a subpage?
- * @return \type{\bool} TRUE or FALSE
+ *
+ * @return \type{\bool}
*/
public function isSubpage() {
return MWNamespace::hasSubpages( $this->mNamespace )
@@ -1636,10 +1870,11 @@ class Title {
/**
* Does this have subpages? (Warning, usually requires an extra DB query.)
- * @return \type{\bool} TRUE or FALSE
+ *
+ * @return \type{\bool}
*/
public function hasSubpages() {
- if( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
+ if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
# Duh
return false;
}
@@ -1648,32 +1883,36 @@ class Title {
# alone to cache the result. There's no point in having it hanging
# around uninitialized in every Title object; therefore we only add it
# if needed and don't declare it statically.
- if( isset( $this->mHasSubpages ) ) {
+ if ( isset( $this->mHasSubpages ) ) {
return $this->mHasSubpages;
}
$subpages = $this->getSubpages( 1 );
- if( $subpages instanceof TitleArray )
+ if ( $subpages instanceof TitleArray ) {
return $this->mHasSubpages = (bool)$subpages->count();
+ }
return $this->mHasSubpages = false;
}
/**
* Get all subpages of this page.
+ *
* @param $limit Maximum number of subpages to fetch; -1 for no limit
* @return mixed TitleArray, or empty array if this page's namespace
* doesn't allow subpages
*/
public function getSubpages( $limit = -1 ) {
- if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
+ if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
return array();
+ }
$dbr = wfGetDB( DB_SLAVE );
$conds['page_namespace'] = $this->getNamespace();
$conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
$options = array();
- if( $limit > -1 )
+ if ( $limit > -1 ) {
$options['LIMIT'] = $limit;
+ }
return $this->mSubpages = TitleArray::newFromResult(
$dbr->select( 'page',
array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
@@ -1688,7 +1927,7 @@ class Title {
* Could this page contain custom CSS or JavaScript, based
* on the title?
*
- * @return \type{\bool} TRUE or FALSE
+ * @return \type{\bool}
*/
public function isCssOrJsPage() {
return $this->mNamespace == NS_MEDIAWIKI
@@ -1697,69 +1936,75 @@ class Title {
/**
* Is this a .css or .js subpage of a user page?
- * @return \type{\bool} TRUE or FALSE
+ * @return \type{\bool}
*/
public function isCssJsSubpage() {
- return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
}
+
/**
* Is this a *valid* .css or .js subpage of a user page?
- * Check that the corresponding skin exists
- * @return \type{\bool} TRUE or FALSE
+ *
+ * @return \type{\bool}
+ * @deprecated
*/
public function isValidCssJsSubpage() {
- if ( $this->isCssJsSubpage() ) {
- $skinNames = Skin::getSkinNames();
- return array_key_exists( $this->getSkinFromCssJsSubpage(), $skinNames );
- } else {
- return false;
- }
+ return $this->isCssJsSubpage();
}
+
/**
* Trim down a .css or .js subpage title to get the corresponding skin name
+ *
+ * @return string containing skin name from .css or .js subpage title
*/
public function getSkinFromCssJsSubpage() {
$subpage = explode( '/', $this->mTextform );
$subpage = $subpage[ count( $subpage ) - 1 ];
return( str_replace( array( '.css', '.js' ), array( '', '' ), $subpage ) );
}
+
/**
* Is this a .css subpage of a user page?
- * @return \type{\bool} TRUE or FALSE
+ *
+ * @return \type{\bool}
*/
public function isCssSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match("/\\/.*\\.css$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
}
+
/**
* Is this a .js subpage of a user page?
- * @return \type{\bool} TRUE or FALSE
+ *
+ * @return \type{\bool}
*/
public function isJsSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match("/\\/.*\\.js$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
}
+
/**
* Protect css subpages of user pages: can $wgUser edit
* this page?
*
- * @return \type{\bool} TRUE or FALSE
+ * @return \type{\bool}
* @todo XXX: this might be better using restrictions
*/
public function userCanEditCssSubpage() {
global $wgUser;
- return ( ( $wgUser->isAllowed('editusercssjs') && $wgUser->isAllowed('editusercss') )
- || preg_match('/^'.preg_quote($wgUser->getName(), '/').'\//', $this->mTextform) );
+ return ( ( $wgUser->isAllowed( 'editusercssjs' ) && $wgUser->isAllowed( 'editusercss' ) )
+ || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
}
+
/**
* Protect js subpages of user pages: can $wgUser edit
* this page?
*
- * @return \type{\bool} TRUE or FALSE
+ * @return \type{\bool}
* @todo XXX: this might be better using restrictions
*/
public function userCanEditJsSubpage() {
global $wgUser;
- return ( ( $wgUser->isAllowed('editusercssjs') && $wgUser->isAllowed('edituserjs') )
- || preg_match('/^'.preg_quote($wgUser->getName(), '/').'\//', $this->mTextform) );
+ return ( ( $wgUser->isAllowed( 'editusercssjs' ) && $wgUser->isAllowed( 'edituserjs' ) )
+ || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
}
/**
@@ -1775,17 +2020,20 @@ class Title {
/**
* Cascading protection: Get the source of any cascading restrictions on this page.
*
- * @param $get_pages \type{\bool} Whether or not to retrieve the actual pages that the restrictions have come from.
- * @return \type{\arrayof{mixed title array, restriction array}} Array of the Title objects of the pages from
- * which cascading restrictions have come, false for none, or true if such restrictions exist, but $get_pages was not set.
- * The restriction array is an array of each type, each of which contains an array of unique groups.
- */
- public function getCascadeProtectionSources( $get_pages = true ) {
+ * @param $getPages \type{\bool} Whether or not to retrieve the actual pages
+ * that the restrictions have come from.
+ * @return \type{\arrayof{mixed title array, restriction array}} Array of the Title
+ * objects of the pages from which cascading restrictions have come,
+ * false for none, or true if such restrictions exist, but $getPages was not set.
+ * The restriction array is an array of each type, each of which contains a
+ * array of unique groups.
+ */
+ public function getCascadeProtectionSources( $getPages = true ) {
$pagerestrictions = array();
- if ( isset( $this->mCascadeSources ) && $get_pages ) {
+ if ( isset( $this->mCascadeSources ) && $getPages ) {
return array( $this->mCascadeSources, $this->mCascadingRestrictions );
- } else if ( isset( $this->mHasCascadingRestrictions ) && !$get_pages ) {
+ } else if ( isset( $this->mHasCascadingRestrictions ) && !$getPages ) {
return array( $this->mHasCascadingRestrictions, $pagerestrictions );
}
@@ -1794,22 +2042,25 @@ class Title {
$dbr = wfGetDB( DB_SLAVE );
if ( $this->getNamespace() == NS_FILE ) {
- $tables = array ('imagelinks', 'page_restrictions');
+ $tables = array( 'imagelinks', 'page_restrictions' );
$where_clauses = array(
'il_to' => $this->getDBkey(),
'il_from=pr_page',
- 'pr_cascade' => 1 );
+ 'pr_cascade' => 1
+ );
} else {
- $tables = array ('templatelinks', 'page_restrictions');
+ $tables = array( 'templatelinks', 'page_restrictions' );
$where_clauses = array(
'tl_namespace' => $this->getNamespace(),
'tl_title' => $this->getDBkey(),
'tl_from=pr_page',
- 'pr_cascade' => 1 );
+ 'pr_cascade' => 1
+ );
}
- if ( $get_pages ) {
- $cols = array('pr_page', 'page_namespace', 'page_title', 'pr_expiry', 'pr_type', 'pr_level' );
+ if ( $getPages ) {
+ $cols = array( 'pr_page', 'page_namespace', 'page_title',
+ 'pr_expiry', 'pr_type', 'pr_level' );
$where_clauses[] = 'page_id=pr_page';
$tables[] = 'page';
} else {
@@ -1818,18 +2069,18 @@ class Title {
$res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
- $sources = $get_pages ? array() : false;
+ $sources = $getPages ? array() : false;
$now = wfTimestampNow();
$purgeExpired = false;
- foreach( $res as $row ) {
+ foreach ( $res as $row ) {
$expiry = Block::decodeExpiry( $row->pr_expiry );
- if( $expiry > $now ) {
- if ($get_pages) {
+ if ( $expiry > $now ) {
+ if ( $getPages ) {
$page_id = $row->pr_page;
$page_ns = $row->page_namespace;
$page_title = $row->page_title;
- $sources[$page_id] = Title::makeTitle($page_ns, $page_title);
+ $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
# Add groups needed for each restriction type if its not already there
# Make sure this restriction type still exists
@@ -1837,9 +2088,9 @@ class Title {
$pagerestrictions[$row->pr_type] = array();
}
- if ( isset($pagerestrictions[$row->pr_type]) &&
- !in_array($row->pr_level, $pagerestrictions[$row->pr_type]) ) {
- $pagerestrictions[$row->pr_type][]=$row->pr_level;
+ if ( isset( $pagerestrictions[$row->pr_type] ) &&
+ !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] ) ) {
+ $pagerestrictions[$row->pr_type][] = $row->pr_level;
}
} else {
$sources = true;
@@ -1849,23 +2100,28 @@ class Title {
$purgeExpired = true;
}
}
- if( $purgeExpired ) {
+ if ( $purgeExpired ) {
Title::purgeExpiredRestrictions();
}
- wfProfileOut( __METHOD__ );
-
- if ( $get_pages ) {
+ if ( $getPages ) {
$this->mCascadeSources = $sources;
$this->mCascadingRestrictions = $pagerestrictions;
} else {
$this->mHasCascadingRestrictions = $sources;
}
+
+ wfProfileOut( __METHOD__ );
return array( $sources, $pagerestrictions );
}
+ /**
+ * Returns cascading restrictions for the current article
+ *
+ * @return Boolean
+ */
function areRestrictionsCascading() {
- if (!$this->mRestrictionsLoaded) {
+ if ( !$this->mRestrictionsLoaded ) {
$this->loadRestrictions();
}
@@ -1874,27 +2130,38 @@ class Title {
/**
* Loads a string into mRestrictions array
+ *
* @param $res \type{Resource} restrictions as an SQL result.
+ * @param $oldFashionedRestrictions string comma-separated list of page
+ * restrictions from page table (pre 1.10)
*/
private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
$rows = array();
- $dbr = wfGetDB( DB_SLAVE );
- while( $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$rows[] = $row;
}
$this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
}
+ /**
+ * Compiles list of active page restrictions from both page table (pre 1.10)
+ * and page_restrictions table for this existing page.
+ * Public for usage by LiquidThreads.
+ *
+ * @param $rows array of db result objects
+ * @param $oldFashionedRestrictions string comma-separated list of page
+ * restrictions from page table (pre 1.10)
+ */
public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
$dbr = wfGetDB( DB_SLAVE );
$restrictionTypes = $this->getRestrictionTypes();
- foreach( $restrictionTypes as $type ){
+ foreach ( $restrictionTypes as $type ) {
$this->mRestrictions[$type] = array();
- $this->mRestrictionsExpiry[$type] = Block::decodeExpiry('');
+ $this->mRestrictionsExpiry[$type] = Block::decodeExpiry( '' );
}
$this->mCascadeRestriction = false;
@@ -1906,11 +2173,11 @@ class Title {
array( 'page_id' => $this->getArticleId() ), __METHOD__ );
}
- if ($oldFashionedRestrictions != '') {
+ if ( $oldFashionedRestrictions != '' ) {
- foreach( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
+ foreach ( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
$temp = explode( '=', trim( $restrict ) );
- if(count($temp) == 1) {
+ if ( count( $temp ) == 1 ) {
// old old format should be treated as edit/move restriction
$this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
$this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
@@ -1923,17 +2190,17 @@ class Title {
}
- if( count($rows) ) {
+ if ( count( $rows ) ) {
# Current system - load second to make them override.
$now = wfTimestampNow();
$purgeExpired = false;
- foreach( $rows as $row ) {
+ foreach ( $rows as $row ) {
# Cycle through all the restrictions.
// Don't take care of restrictions types that aren't allowed
- if( !in_array( $row->pr_type, $restrictionTypes ) )
+ if ( !in_array( $row->pr_type, $restrictionTypes ) )
continue;
// This code should be refactored, now that it's being used more generally,
@@ -1952,7 +2219,7 @@ class Title {
}
}
- if( $purgeExpired ) {
+ if ( $purgeExpired ) {
Title::purgeExpiredRestrictions();
}
}
@@ -1962,34 +2229,36 @@ class Title {
/**
* Load restrictions from the page_restrictions table
+ *
+ * @param $oldFashionedRestrictions string comma-separated list of page
+ * restrictions from page table (pre 1.10)
*/
public function loadRestrictions( $oldFashionedRestrictions = null ) {
- if( !$this->mRestrictionsLoaded ) {
- if ($this->exists()) {
+ if ( !$this->mRestrictionsLoaded ) {
+ if ( $this->exists() ) {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'page_restrictions', '*',
- array ( 'pr_page' => $this->getArticleId() ), __METHOD__ );
+ array( 'pr_page' => $this->getArticleId() ), __METHOD__ );
$this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
} else {
$title_protection = $this->getTitleProtection();
- if (is_array($title_protection)) {
- extract($title_protection);
-
+ if ( $title_protection ) {
$now = wfTimestampNow();
- $expiry = Block::decodeExpiry($pt_expiry);
+ $expiry = Block::decodeExpiry( $title_protection['pt_expiry'] );
- if (!$expiry || $expiry > $now) {
+ if ( !$expiry || $expiry > $now ) {
// Apply the restrictions
$this->mRestrictionsExpiry['create'] = $expiry;
- $this->mRestrictions['create'] = explode(',', trim($pt_create_perm) );
+ $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
} else { // Get rid of the old restrictions
Title::purgeExpiredRestrictions();
+ $this->mTitleProtection = false;
}
} else {
- $this->mRestrictionsExpiry['create'] = Block::decodeExpiry('');
+ $this->mRestrictionsExpiry['create'] = Block::decodeExpiry( '' );
}
$this->mRestrictionsLoaded = true;
}
@@ -2001,13 +2270,17 @@ class Title {
*/
static function purgeExpiredRestrictions() {
$dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'page_restrictions',
+ $dbw->delete(
+ 'page_restrictions',
array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
- __METHOD__ );
+ __METHOD__
+ );
- $dbw->delete( 'protected_titles',
+ $dbw->delete(
+ 'protected_titles',
array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
- __METHOD__ );
+ __METHOD__
+ );
}
/**
@@ -2017,7 +2290,7 @@ class Title {
* @return \type{\arrayof{\string}} the array of groups allowed to edit this article
*/
public function getRestrictions( $action ) {
- if( !$this->mRestrictionsLoaded ) {
+ if ( !$this->mRestrictionsLoaded ) {
$this->loadRestrictions();
}
return isset( $this->mRestrictions[$action] )
@@ -2027,11 +2300,12 @@ class Title {
/**
* Get the expiry time for the restriction against a given action
+ *
* @return 14-char timestamp, or 'infinity' if the page is protected forever
- * or not protected at all, or false if the action is not recognised.
+ * or not protected at all, or false if the action is not recognised.
*/
public function getRestrictionExpiry( $action ) {
- if( !$this->mRestrictionsLoaded ) {
+ if ( !$this->mRestrictionsLoaded ) {
$this->loadRestrictions();
}
return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
@@ -2039,10 +2313,11 @@ class Title {
/**
* Is there a version of this page in the deletion archive?
+ *
* @return \type{\int} the number of archived revisions
*/
public function isDeleted() {
- if( $this->getNamespace() < 0 ) {
+ if ( $this->getNamespace() < 0 ) {
$n = 0;
} else {
$dbr = wfGetDB( DB_SLAVE );
@@ -2050,7 +2325,7 @@ class Title {
array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
__METHOD__
);
- if( $this->getNamespace() == NS_FILE ) {
+ if ( $this->getNamespace() == NS_FILE ) {
$n += $dbr->selectField( 'filearchive', 'COUNT(*)',
array( 'fa_name' => $this->getDBkey() ),
__METHOD__
@@ -2062,10 +2337,11 @@ class Title {
/**
* Is there a version of this page in the deletion archive?
- * @return bool
+ *
+ * @return Boolean
*/
public function isDeletedQuick() {
- if( $this->getNamespace() < 0 ) {
+ if ( $this->getNamespace() < 0 ) {
return false;
}
$dbr = wfGetDB( DB_SLAVE );
@@ -2073,7 +2349,7 @@ class Title {
array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
__METHOD__
);
- if( !$deleted && $this->getNamespace() == NS_FILE ) {
+ if ( !$deleted && $this->getNamespace() == NS_FILE ) {
$deleted = (bool)$dbr->selectField( 'filearchive', '1',
array( 'fa_name' => $this->getDBkey() ),
__METHOD__
@@ -2085,22 +2361,23 @@ class Title {
/**
* Get the article ID for this Title from the link cache,
* adding it if necessary
- * @param $flags \type{\int} a bit field; may be GAID_FOR_UPDATE to select
+ *
+ * @param $flags \type{\int} a bit field; may be Title::GAID_FOR_UPDATE to select
* for update
* @return \type{\int} the ID
*/
public function getArticleID( $flags = 0 ) {
- if( $this->getNamespace() < 0 ) {
+ if ( $this->getNamespace() < 0 ) {
return $this->mArticleID = 0;
}
$linkCache = LinkCache::singleton();
- if( $flags & GAID_FOR_UPDATE ) {
+ if ( $flags & self::GAID_FOR_UPDATE ) {
$oldUpdate = $linkCache->forUpdate( true );
$linkCache->clearLink( $this );
$this->mArticleID = $linkCache->addLinkObj( $this );
$linkCache->forUpdate( $oldUpdate );
} else {
- if( -1 == $this->mArticleID ) {
+ if ( -1 == $this->mArticleID ) {
$this->mArticleID = $linkCache->addLinkObj( $this );
}
}
@@ -2110,14 +2387,16 @@ class Title {
/**
* Is this an article that is a redirect page?
* Uses link cache, adding it if necessary
- * @param $flags \type{\int} a bit field; may be GAID_FOR_UPDATE to select for update
+ *
+ * @param $flags \type{\int} a bit field; may be Title::GAID_FOR_UPDATE to select for update
* @return \type{\bool}
*/
public function isRedirect( $flags = 0 ) {
- if( !is_null($this->mRedirect) )
+ if ( !is_null( $this->mRedirect ) ) {
return $this->mRedirect;
+ }
# Calling getArticleID() loads the field from cache as needed
- if( !$this->getArticleID($flags) ) {
+ if ( !$this->getArticleID( $flags ) ) {
return $this->mRedirect = false;
}
$linkCache = LinkCache::singleton();
@@ -2129,14 +2408,16 @@ class Title {
/**
* What is the length of this page?
* Uses link cache, adding it if necessary
- * @param $flags \type{\int} a bit field; may be GAID_FOR_UPDATE to select for update
+ *
+ * @param $flags \type{\int} a bit field; may be Title::GAID_FOR_UPDATE to select for update
* @return \type{\bool}
*/
public function getLength( $flags = 0 ) {
- if( $this->mLength != -1 )
+ if ( $this->mLength != -1 ) {
return $this->mLength;
+ }
# Calling getArticleID() loads the field from cache as needed
- if( !$this->getArticleID($flags) ) {
+ if ( !$this->getArticleID( $flags ) ) {
return $this->mLength = 0;
}
$linkCache = LinkCache::singleton();
@@ -2147,15 +2428,21 @@ class Title {
/**
* What is the page_latest field for this page?
- * @param $flags \type{\int} a bit field; may be GAID_FOR_UPDATE to select for update
- * @return \type{\int} or false if the page doesn't exist
+ *
+ * @param $flags \type{\int} a bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @return \type{\int} or 0 if the page doesn't exist
*/
public function getLatestRevID( $flags = 0 ) {
- if( $this->mLatestID !== false )
- return $this->mLatestID;
+ if ( $this->mLatestID !== false ) {
+ return intval( $this->mLatestID );
+ }
+ # Calling getArticleID() loads the field from cache as needed
+ if ( !$this->getArticleID( $flags ) ) {
+ return $this->mLatestID = 0;
+ }
+ $linkCache = LinkCache::singleton();
+ $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
- $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_SLAVE);
- $this->mLatestID = $db->selectField( 'page', 'page_latest', $this->pageCond(), __METHOD__ );
return $this->mLatestID;
}
@@ -2173,22 +2460,30 @@ class Title {
$linkCache = LinkCache::singleton();
$linkCache->clearBadLink( $this->getPrefixedDBkey() );
- if ( $newid === false ) { $this->mArticleID = -1; }
- else { $this->mArticleID = intval( $newid ); }
+ if ( $newid === false ) {
+ $this->mArticleID = -1;
+ } else {
+ $this->mArticleID = intval( $newid );
+ }
$this->mRestrictionsLoaded = false;
$this->mRestrictions = array();
+ $this->mRedirect = null;
+ $this->mLength = -1;
+ $this->mLatestID = false;
}
/**
* Updates page_touched for this page; called from LinksUpdate.php
+ *
* @return \type{\bool} true if the update succeded
*/
public function invalidateCache() {
- if( wfReadOnly() ) {
+ if ( wfReadOnly() ) {
return;
}
$dbw = wfGetDB( DB_MASTER );
- $success = $dbw->update( 'page',
+ $success = $dbw->update(
+ 'page',
array( 'page_touched' => $dbw->timestamp() ),
$this->pageCond(),
__METHOD__
@@ -2216,12 +2511,16 @@ class Title {
return $p . $name;
}
- // 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.
+ /**
+ * 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.
+ *
+ * @return string regex string
+ */
static function getTitleInvalidRegex() {
static $rxTc = false;
- if( !$rxTc ) {
+ if ( !$rxTc ) {
# Matching titles will be held as illegal.
$rxTc = '/' .
# Any character not allowed is forbidden...
@@ -2240,15 +2539,20 @@ class Title {
}
/**
- * Capitalize a text if it belongs to a namespace that capitalizes
+ * Capitalize a text string for a title if it belongs to a namespace that capitalizes
+ *
+ * @param $text string containing title to capitalize
+ * @param $ns int namespace index, defaults to NS_MAIN
+ * @return String containing capitalized title
*/
public static function capitalize( $text, $ns = NS_MAIN ) {
global $wgContLang;
- if ( MWNamespace::isCapitalized( $ns ) )
+ if ( MWNamespace::isCapitalized( $ns ) ) {
return $wgContLang->ucfirst( $text );
- else
+ } else {
return $text;
+ }
}
/**
@@ -2259,6 +2563,7 @@ class Title {
* removes illegal characters, splits off the interwiki and
* namespace prefixes, sets the other forms, and canonicalizes
* everything.
+ *
* @return \type{\bool} true on success
*/
private function secureAndSplit() {
@@ -2289,7 +2594,7 @@ class Title {
return false;
}
- if( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
+ if ( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
# Contained illegal UTF-8 sequences or forbidden Unicode chars.
return false;
}
@@ -2298,7 +2603,7 @@ class Title {
# Initial colon indicates main namespace rather than specified default
# but should not create invalid {ns,title} pairs such as {0,Project:Foo}
- if ( ':' == $dbkey{0} ) {
+ if ( ':' == $dbkey { 0 } ) {
$this->mNamespace = NS_MAIN;
$dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
$dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
@@ -2311,19 +2616,20 @@ class Title {
$m = array();
if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
$p = $m[1];
- if ( $ns = $wgContLang->getNsIndex( $p ) ) {
+ if ( ( $ns = $wgContLang->getNsIndex( $p ) ) !== false ) {
# Ordinary namespace
$dbkey = $m[2];
$this->mNamespace = $ns;
# For Talk:X pages, check if X has a "namespace" prefix
- if( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
- if( $wgContLang->getNsIndex( $x[1] ) )
+ if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
+ if ( $wgContLang->getNsIndex( $x[1] ) ) {
return false; # Disallow Talk:File:x type titles...
- else if( Interwiki::isValidInterwiki( $x[1] ) )
+ } else if ( Interwiki::isValidInterwiki( $x[1] ) ) {
return false; # Disallow Talk:Interwiki:x type titles...
+ }
}
- } elseif( Interwiki::isValidInterwiki( $p ) ) {
- if( !$firstPass ) {
+ } elseif ( Interwiki::isValidInterwiki( $p ) ) {
+ if ( !$firstPass ) {
# Can't make a local interwiki link to an interwiki link.
# That's just crazy!
return false;
@@ -2334,8 +2640,10 @@ class Title {
$this->mInterwiki = $wgContLang->lc( $p );
# Redundant interwiki prefix to the local wiki
- if ( 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) {
- if( $dbkey == '' ) {
+ if ( $wgLocalInterwiki !== false
+ && 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) )
+ {
+ if ( $dbkey == '' ) {
# Can't have an empty self-link
return false;
}
@@ -2356,7 +2664,7 @@ class Title {
# then let the colon expression be part of the title.
}
break;
- } while( true );
+ } while ( true );
# We already know that some pages won't be in the database!
#
@@ -2365,7 +2673,7 @@ class Title {
}
$fragment = strstr( $dbkey, '#' );
if ( false !== $fragment ) {
- $this->setFragment( $fragment );
+ $this->setFragment( preg_replace( '/^#_*/', '#', $fragment ) );
$dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
# remove whitespace again: prevents "Foo_bar_#"
# becoming "Foo_bar_"
@@ -2374,7 +2682,7 @@ class Title {
# Reject illegal characters.
#
- if( preg_match( $rxTc, $dbkey ) ) {
+ if ( preg_match( $rxTc, $dbkey ) ) {
return false;
}
@@ -2398,7 +2706,7 @@ class Title {
/**
* Magic tilde sequences? Nu-uh!
*/
- if( strpos( $dbkey, '~~~' ) !== false ) {
+ if ( strpos( $dbkey, '~~~' ) !== false ) {
return false;
}
@@ -2424,7 +2732,7 @@ class Title {
* site might be case-sensitive.
*/
$this->mUserCaseDBKey = $dbkey;
- if( $this->mInterwiki == '') {
+ if ( $this->mInterwiki == '' ) {
$dbkey = self::capitalize( $dbkey, $this->mNamespace );
}
@@ -2433,7 +2741,7 @@ class Title {
* "empty" local links can only be self-links
* with a fragment identifier.
*/
- if( $dbkey == '' &&
+ if ( $dbkey == '' &&
$this->mInterwiki == '' &&
$this->mNamespace != NS_MAIN ) {
return false;
@@ -2444,10 +2752,10 @@ class Title {
// there are numerous ways to present the same IP. Having sp:contribs scan
// them all is silly and having some show the edits and others not is
// inconsistent. Same for talk/userpages. Keep them normalized instead.
- $dbkey = ($this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK) ?
+ $dbkey = ( $this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK ) ?
IP::sanitizeIP( $dbkey ) : $dbkey;
// Any remaining initial :s are illegal.
- if ( $dbkey !== '' && ':' == $dbkey{0} ) {
+ if ( $dbkey !== '' && ':' == $dbkey { 0 } ) {
return false;
}
@@ -2476,7 +2784,8 @@ class Title {
/**
* Get a Title object associated with the talk page of this article
- * @return \type{Title} the object for the talk page
+ *
+ * @return Title the object for the talk page
*/
public function getTalkPage() {
return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
@@ -2486,12 +2795,12 @@ class Title {
* Get a title object associated with the subject page of this
* talk page
*
- * @return \type{Title} the object for the subject page
+ * @return Title the object for the subject page
*/
public function getSubjectPage() {
// Is this the same title?
$subjectNS = MWNamespace::getSubject( $this->getNamespace() );
- if( $this->getNamespace() == $subjectNS ) {
+ if ( $this->getNamespace() == $subjectNS ) {
return $this;
}
return Title::makeTitle( $subjectNS, $this->getDBkey() );
@@ -2504,7 +2813,9 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param array $options may be FOR UPDATE
+ * @param $options Array: may be FOR UPDATE
+ * @param $table String: table name
+ * @param $prefix String: fields prefix
* @return \type{\arrayof{Title}} the Title objects linking here
*/
public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
@@ -2516,25 +2827,27 @@ class Title {
$db = wfGetDB( DB_SLAVE );
}
- $res = $db->select( array( 'page', $table ),
- array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect' ),
+ $res = $db->select(
+ array( 'page', $table ),
+ array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
array(
"{$prefix}_from=page_id",
"{$prefix}_namespace" => $this->getNamespace(),
"{$prefix}_title" => $this->getDBkey() ),
__METHOD__,
- $options );
+ $options
+ );
$retVal = array();
if ( $db->numRows( $res ) ) {
- foreach( $res as $row ) {
- if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) {
- $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect );
+ foreach ( $res as $row ) {
+ $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
+ if ( $titleObj ) {
+ $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect, $row->page_latest );
$retVal[] = $titleObj;
}
}
}
- $db->freeResult( $res );
return $retVal;
}
@@ -2545,7 +2858,7 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param array $options may be FOR UPDATE
+ * @param $options Array: may be FOR UPDATE
* @return \type{\arrayof{Title}} the Title objects linking here
*/
public function getTemplateLinksTo( $options = array() ) {
@@ -2582,7 +2895,7 @@ class Title {
);
$retVal = array();
- foreach( $res as $row ) {
+ foreach ( $res as $row ) {
$retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
}
return $retVal;
@@ -2604,7 +2917,7 @@ class Title {
);
// purge variant urls as well
- if($wgContLang->hasVariants()){
+ if ( $wgContLang->hasVariants() ) {
$variants = $wgContLang->getVariants();
foreach ( $variants as $vCode ) {
$urls[] = $this->getInternalURL( '', $vCode );
@@ -2628,7 +2941,9 @@ class Title {
/**
* Move this page without authentication
- * @param &$nt \type{Title} the new page Title
+ *
+ * @param $nt \type{Title} the new page Title
+ * @return \type{\mixed} true on success, getUserPermissionsErrors()-like array on failure
*/
public function moveNoAuth( &$nt ) {
return $this->moveTo( $nt, false );
@@ -2637,7 +2952,8 @@ class Title {
/**
* Check whether a given move operation would be valid.
* Returns true if ok, or a getUserPermissionsErrors()-like array otherwise
- * @param &$nt \type{Title} the new title
+ *
+ * @param $nt \type{Title} the new title
* @param $auth \type{\bool} indicates whether $wgUser's permissions
* should be checked
* @param $reason \type{\string} is the log summary of the move, used for spam checking
@@ -2647,74 +2963,77 @@ class Title {
global $wgUser;
$errors = array();
- if( !$nt ) {
+ if ( !$nt ) {
// 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'));
+ return array( array( 'badtitletext' ) );
}
- if( $this->equals( $nt ) ) {
- $errors[] = array('selfmove');
+ if ( $this->equals( $nt ) ) {
+ $errors[] = array( 'selfmove' );
}
- if( !$this->isMovable() ) {
+ if ( !$this->isMovable() ) {
$errors[] = array( 'immobile-source-namespace', $this->getNsText() );
}
if ( $nt->getInterwiki() != '' ) {
$errors[] = array( 'immobile-target-namespace-iw' );
}
if ( !$nt->isMovable() ) {
- $errors[] = array('immobile-target-namespace', $nt->getNsText() );
+ $errors[] = array( 'immobile-target-namespace', $nt->getNsText() );
}
$oldid = $this->getArticleID();
$newid = $nt->getArticleID();
if ( strlen( $nt->getDBkey() ) < 1 ) {
- $errors[] = array('articleexists');
+ $errors[] = array( 'articleexists' );
}
if ( ( $this->getDBkey() == '' ) ||
( !$oldid ) ||
( $nt->getDBkey() == '' ) ) {
- $errors[] = array('badarticleerror');
+ $errors[] = array( 'badarticleerror' );
}
// Image-specific checks
- if( $this->getNamespace() == NS_FILE ) {
+ if ( $this->getNamespace() == NS_FILE ) {
+ if ( $nt->getNamespace() != NS_FILE ) {
+ $errors[] = array( 'imagenocrossnamespace' );
+ }
$file = wfLocalFile( $this );
- if( $file->exists() ) {
- if( $nt->getNamespace() != NS_FILE ) {
- $errors[] = array('imagenocrossnamespace');
+ if ( $file->exists() ) {
+ if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
+ $errors[] = array( 'imageinvalidfilename' );
}
- if( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
- $errors[] = array('imageinvalidfilename');
- }
- if( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
- $errors[] = array('imagetypemismatch');
+ if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
+ $errors[] = array( 'imagetypemismatch' );
}
}
$destfile = wfLocalFile( $nt );
- if( !$wgUser->isAllowed( 'reupload-shared' ) && !$destfile->exists() && wfFindFile( $nt ) ) {
+ if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destfile->exists() && wfFindFile( $nt ) ) {
$errors[] = array( 'file-exists-sharedrepo' );
}
+ }
+ if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
+ $errors[] = array( 'nonfile-cannot-move-to-file' );
}
if ( $auth ) {
$errors = wfMergeErrorArrays( $errors,
- $this->getUserPermissionsErrors('move', $wgUser),
- $this->getUserPermissionsErrors('edit', $wgUser),
- $nt->getUserPermissionsErrors('move-target', $wgUser),
- $nt->getUserPermissionsErrors('edit', $wgUser) );
+ $this->getUserPermissionsErrors( 'move', $wgUser ),
+ $this->getUserPermissionsErrors( 'edit', $wgUser ),
+ $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
+ $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
}
$match = EditPage::matchSummarySpamRegex( $reason );
- if( $match !== false ) {
+ if ( $match !== false ) {
// This is kind of lame, won't display nice
- $errors[] = array('spamprotectiontext');
+ $errors[] = array( 'spamprotectiontext' );
}
$err = null;
- if( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
- $errors[] = array('hookaborted', $err);
+ if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
+ $errors[] = array( 'hookaborted', $err );
}
# The move is allowed only if (1) the target doesn't exist, or
@@ -2722,24 +3041,26 @@ class Title {
# (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');
+ if ( !$this->isValidMoveTarget( $nt ) ) {
+ $errors[] = array( 'articleexists' );
}
} else {
$tp = $nt->getTitleProtection();
$right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm'];
if ( $tp and !$wgUser->isAllowed( $right ) ) {
- $errors[] = array('cantmove-titleprotected');
+ $errors[] = array( 'cantmove-titleprotected' );
}
}
- if(empty($errors))
+ if ( empty( $errors ) ) {
return true;
+ }
return $errors;
}
/**
* Move a title to a new location
- * @param &$nt \type{Title} the new title
+ *
+ * @param $nt \type{Title} the new title
* @param $auth \type{\bool} indicates whether $wgUser's permissions
* should be checked
* @param $reason \type{\string} The reason for the move
@@ -2749,58 +3070,57 @@ class Title {
*/
public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
$err = $this->isValidMoveOperation( $nt, $auth, $reason );
- if( is_array( $err ) ) {
+ if ( is_array( $err ) ) {
return $err;
}
// If it is a file, move it first. It is done before all other moving stuff is done because it's hard to revert
$dbw = wfGetDB( DB_MASTER );
- if( $this->getNamespace() == NS_FILE ) {
+ if ( $this->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $this );
- if( $file->exists() ) {
+ if ( $file->exists() ) {
$status = $file->move( $nt );
- if( !$status->isOk() ) {
+ if ( !$status->isOk() ) {
return $status->getErrorsArray();
}
}
}
- $pageid = $this->getArticleID();
+ $dbw->begin(); # If $file was a LocalFile, its transaction would have closed our own.
+ $pageid = $this->getArticleID( GAID_FOR_UPDATE );
$protected = $this->isProtected();
- if( $nt->exists() ) {
+ if ( $nt->exists() ) {
$err = $this->moveOverExistingRedirect( $nt, $reason, $createRedirect );
- $pageCountChange = ($createRedirect ? 0 : -1);
+ $pageCountChange = ( $createRedirect ? 0 : -1 );
} else { # Target didn't exist, do normal move.
$err = $this->moveToNewTitle( $nt, $reason, $createRedirect );
- $pageCountChange = ($createRedirect ? 1 : 0);
+ $pageCountChange = ( $createRedirect ? 1 : 0 );
}
- if( is_array( $err ) ) {
+ if ( is_array( $err ) ) {
+ # FIXME: What about the File we have already moved?
+ $dbw->rollback();
return $err;
}
$redirid = $this->getArticleID();
- // Category memberships include a sort key which may be customized.
- // If it's left as the default (the page title), we need to update
- // the sort key to match the new title.
- //
- // Be careful to avoid resetting cl_timestamp, which may disturb
- // time-based lists on some sites.
- //
- // Warning -- if the sort key is *explicitly* set to the old title,
- // we can't actually distinguish it from a default here, and it'll
- // be set to the new title even though it really shouldn't.
- // It'll get corrected on the next edit, but resetting cl_timestamp.
+ // Refresh the sortkey for this row. Be careful to avoid resetting
+ // cl_timestamp, which may disturb time-based lists on some sites.
+ $prefix = $dbw->selectField(
+ 'categorylinks',
+ 'cl_sortkey_prefix',
+ array( 'cl_from' => $pageid ),
+ __METHOD__
+ );
$dbw->update( 'categorylinks',
array(
- 'cl_sortkey' => $nt->getPrefixedText(),
+ 'cl_sortkey' => Collation::singleton()->getSortKey(
+ $nt->getCategorySortkey( $prefix ) ),
'cl_timestamp=cl_timestamp' ),
- array(
- 'cl_from' => $pageid,
- 'cl_sortkey' => $this->getPrefixedText() ),
+ array( 'cl_from' => $pageid ),
__METHOD__ );
- if( $protected ) {
+ if ( $protected ) {
# Protect the redirect title as the title used to be...
$dbw->insertSelect( 'page_restrictions', 'page_restrictions',
array(
@@ -2818,8 +3138,10 @@ class Title {
# Update the protection log
$log = new LogPage( 'protect' );
$comment = wfMsgForContent( 'prot_1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
- if( $reason ) $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
- $log->addEntry( 'move_prot', $nt, $comment, array($this->getPrefixedText()) ); // FIXME: $params?
+ if ( $reason ) {
+ $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
+ }
+ $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) ); // FIXME: $params?
}
# Update watchlists
@@ -2828,7 +3150,7 @@ class Title {
$oldtitle = $this->getDBkey();
$newtitle = $nt->getDBkey();
- if( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
+ if ( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
WatchedItem::duplicateEntries( $this, $nt );
}
@@ -2838,28 +3160,30 @@ class Title {
$u = new SearchUpdate( $redirid, $this->getPrefixedDBkey(), '' );
$u->doUpdate();
+ $dbw->commit();
+
# Update site_stats
- if( $this->isContentPage() && !$nt->isContentPage() ) {
+ if ( $this->isContentPage() && !$nt->isContentPage() ) {
# No longer a content page
# Not viewed, edited, removing
$u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange );
- } elseif( !$this->isContentPage() && $nt->isContentPage() ) {
+ } elseif ( !$this->isContentPage() && $nt->isContentPage() ) {
# Now a content page
# Not viewed, edited, adding
- $u = new SiteStatsUpdate( 0, 1, +1, $pageCountChange );
- } elseif( $pageCountChange ) {
+ $u = new SiteStatsUpdate( 0, 1, + 1, $pageCountChange );
+ } elseif ( $pageCountChange ) {
# Redirect added
$u = new SiteStatsUpdate( 0, 0, 0, 1 );
} else {
# Nothing special
$u = false;
}
- if( $u )
+ if ( $u ) {
$u->doUpdate();
+ }
# Update message cache for interface messages
- if( $nt->getNamespace() == NS_MEDIAWIKI ) {
- global $wgMessageCache;
-
+ global $wgMessageCache;
+ if ( $this->getNamespace() == NS_MEDIAWIKI ) {
# @bug 17860: old article can be deleted, if this the case,
# delete it from message cache
if ( $this->getArticleID() === 0 ) {
@@ -2868,7 +3192,8 @@ class Title {
$oldarticle = new Article( $this );
$wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() );
}
-
+ }
+ if ( $nt->getNamespace() == NS_MEDIAWIKI ) {
$newarticle = new Article( $nt );
$wgMessageCache->replace( $nt->getDBkey(), $newarticle->getContent() );
}
@@ -2882,7 +3207,7 @@ class Title {
* Move page to a title which is at present a redirect to the
* source page
*
- * @param &$nt \type{Title} the page to move to, which should currently
+ * @param $nt \type{Title} the page to move to, which should currently
* be a redirect
* @param $reason \type{\string} The reason for the move
* @param $createRedirect \type{\bool} Whether to leave a redirect at the old title.
@@ -2918,8 +3243,9 @@ class Title {
if ( !$dbw->cascadingDeletes() ) {
$dbw->delete( 'revision', array( 'rev_page' => $newid ), __METHOD__ );
global $wgUseTrackbacks;
- if ($wgUseTrackbacks)
+ if ( $wgUseTrackbacks ) {
$dbw->delete( 'trackbacks', array( 'tb_page' => $newid ), __METHOD__ );
+ }
$dbw->delete( 'pagelinks', array( 'pl_from' => $newid ), __METHOD__ );
$dbw->delete( 'imagelinks', array( 'il_from' => $newid ), __METHOD__ );
$dbw->delete( 'categorylinks', array( 'cl_from' => $newid ), __METHOD__ );
@@ -2939,12 +3265,12 @@ class Title {
$nullRevId = $nullRevision->insertOn( $dbw );
$article = new Article( $this );
- wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $wgUser ) );
# Change the name of the target page:
$dbw->update( 'page',
/* SET */ array(
- 'page_touched' => $dbw->timestamp($now),
+ 'page_touched' => $dbw->timestamp( $now ),
'page_namespace' => $nt->getNamespace(),
'page_title' => $nt->getDBkey(),
'page_latest' => $nullRevId,
@@ -2955,7 +3281,7 @@ class Title {
$nt->resetArticleID( $oldid );
# Recreate the redirect, this time in the other direction.
- if( $createRedirect || !$wgUser->isAllowed('suppressredirect') ) {
+ if ( $createRedirect || !$wgUser->isAllowed( 'suppressredirect' ) ) {
$mwRedir = MagicWord::get( 'redirect' );
$redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
$redirectArticle = new Article( $this );
@@ -2967,7 +3293,7 @@ class Title {
$redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
- wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $redirectArticle, $redirectRevision, false, $wgUser ) );
# Now, we record the link from the redirect to the new title.
# It should have no other outgoing links...
@@ -2999,13 +3325,14 @@ class Title {
/**
* Move page to non-existing title.
- * @param &$nt \type{Title} the new Title
+ *
+ * @param $nt \type{Title} the new Title
* @param $reason \type{\string} The reason for the move
* @param $createRedirect \type{\bool} Whether to create a redirect from the old title to the new title
* Ignored if the user doesn't have the suppressredirect right
*/
private function moveToNewTitle( &$nt, $reason = '', $createRedirect = true ) {
- global $wgUseSquid, $wgUser, $wgContLang;
+ global $wgUser, $wgContLang;
$comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
if ( $reason ) {
@@ -3016,7 +3343,6 @@ class Title {
# Truncate for whole multibyte characters. +5 bytes for ellipsis
$comment = $wgContLang->truncate( $comment, 250 );
- $newid = $nt->getArticleID();
$oldid = $this->getArticleID();
$latest = $this->getLatestRevId();
@@ -3031,7 +3357,7 @@ class Title {
$nullRevId = $nullRevision->insertOn( $dbw );
$article = new Article( $this );
- wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $wgUser ) );
# Rename page entry
$dbw->update( 'page',
@@ -3046,7 +3372,7 @@ class Title {
);
$nt->resetArticleID( $oldid );
- if( $createRedirect || !$wgUser->isAllowed('suppressredirect') ) {
+ if ( $createRedirect || !$wgUser->isAllowed( 'suppressredirect' ) ) {
# Insert redirect
$mwRedir = MagicWord::get( 'redirect' );
$redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
@@ -3059,7 +3385,7 @@ class Title {
$redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
- wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $redirectArticle, $redirectRevision, false, $wgUser ) );
# Record the just-created redirect's linking to the page
$dbw->insert( 'pagelinks',
@@ -3084,11 +3410,11 @@ class Title {
# Purge old title from squid
# The new title, and links to the new title, are purged in Article::onArticleCreate()
$this->purgeSquid();
-
}
/**
* Move this page's subpages to be subpages of $nt
+ *
* @param $nt Title Move target
* @param $auth bool Whether $wgUser's permissions should be checked
* @param $reason string The reason for the move
@@ -3100,22 +3426,25 @@ class Title {
public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
global $wgMaximumMovedPages;
// Check permissions
- if( !$this->userCan( 'move-subpages' ) )
+ if ( !$this->userCan( 'move-subpages' ) ) {
return array( 'cant-move-subpages' );
+ }
// Do the source and target namespaces support subpages?
- if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
+ if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
return array( 'namespace-nosubpages',
MWNamespace::getCanonicalName( $this->getNamespace() ) );
- if( !MWNamespace::hasSubpages( $nt->getNamespace() ) )
+ }
+ if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
return array( 'namespace-nosubpages',
MWNamespace::getCanonicalName( $nt->getNamespace() ) );
+ }
- $subpages = $this->getSubpages($wgMaximumMovedPages + 1);
+ $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
$retval = array();
$count = 0;
- foreach( $subpages as $oldSubpage ) {
+ foreach ( $subpages as $oldSubpage ) {
$count++;
- if( $count > $wgMaximumMovedPages ) {
+ if ( $count > $wgMaximumMovedPages ) {
$retval[$oldSubpage->getPrefixedTitle()] =
array( 'movepage-max-pages',
$wgMaximumMovedPages );
@@ -3125,16 +3454,18 @@ class Title {
// We don't know whether this function was called before
// or after moving the root page, so check both
// $this and $nt
- if( $oldSubpage->getArticleId() == $this->getArticleId() ||
+ if ( $oldSubpage->getArticleId() == $this->getArticleId() ||
$oldSubpage->getArticleID() == $nt->getArticleId() )
+ {
// When moving a page to a subpage of itself,
// don't move it twice
continue;
+ }
$newPageName = preg_replace(
- '#^'.preg_quote( $this->getDBkey(), '#' ).'#',
+ '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
$oldSubpage->getDBkey() );
- if( $oldSubpage->isTalkPage() ) {
+ if ( $oldSubpage->isTalkPage() ) {
$newNs = $nt->getTalkPage()->getNamespace();
} else {
$newNs = $nt->getSubjectPage()->getNamespace();
@@ -3144,7 +3475,7 @@ class Title {
$newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
$success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
- if( $success === true ) {
+ if ( $success === true ) {
$retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
} else {
$retval[$oldSubpage->getPrefixedText()] = $success;
@@ -3157,7 +3488,7 @@ class Title {
* Checks if this page is just a one-rev redirect.
* Adds lock, so don't use just for light purposes.
*
- * @return \type{\bool} TRUE or FALSE
+ * @return \type{\bool}
*/
public function isSingleRevRedirect() {
$dbw = wfGetDB( DB_MASTER );
@@ -3169,14 +3500,14 @@ class Title {
array( 'FOR UPDATE' )
);
# Cache some fields we may want
- $this->mArticleID = $row ? intval($row->page_id) : 0;
+ $this->mArticleID = $row ? intval( $row->page_id ) : 0;
$this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
- $this->mLatestID = $row ? intval($row->page_latest) : false;
- if( !$this->mRedirect ) {
+ $this->mLatestID = $row ? intval( $row->page_latest ) : false;
+ if ( !$this->mRedirect ) {
return false;
}
# Does the article have a history?
- $row = $dbw->selectField( array( 'page', 'revision'),
+ $row = $dbw->selectField( array( 'page', 'revision' ),
'rev_id',
array( 'page_namespace' => $this->getNamespace(),
'page_title' => $this->getDBkey(),
@@ -3187,28 +3518,27 @@ class Title {
array( 'FOR UPDATE' )
);
# Return true if there was no history
- return ($row === false);
+ return ( $row === false );
}
/**
* Checks if $this can be moved to a given Title
* - Selects for update, so don't call it unless you mean business
*
- * @param &$nt \type{Title} the new title to check
+ * @param $nt \type{Title} the new title to check
* @return \type{\bool} TRUE or FALSE
*/
public function isValidMoveTarget( $nt ) {
- $dbw = wfGetDB( DB_MASTER );
- # Is it an existsing file?
- if( $nt->getNamespace() == NS_FILE ) {
+ # Is it an existing file?
+ if ( $nt->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $nt );
- if( $file->exists() ) {
+ if ( $file->exists() ) {
wfDebug( __METHOD__ . ": file exists\n" );
return false;
}
}
# Is it a redirect with no history?
- if( !$nt->isSingleRevRedirect() ) {
+ if ( !$nt->isSingleRevRedirect() ) {
wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
return false;
}
@@ -3220,7 +3550,7 @@ class Title {
$m = array();
if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
$redirTitle = Title::newFromText( $m[1] );
- if( !is_object( $redirTitle ) ||
+ if ( !is_object( $redirTitle ) ||
( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
$redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
wfDebug( __METHOD__ . ": redirect points to other page\n" );
@@ -3259,25 +3589,25 @@ class Title {
# NEW SQL
$sql = "SELECT * FROM $categorylinks"
- ." WHERE cl_from='$titlekey'"
- ." AND cl_from <> '0'"
- ." ORDER BY cl_sortkey";
+ . " WHERE cl_from='$titlekey'"
+ . " AND cl_from <> '0'"
+ . " ORDER BY cl_sortkey";
$res = $dbr->query( $sql );
+ $data = array();
- if( $dbr->numRows( $res ) > 0 ) {
- foreach( $res as $row )
- //$data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$row->cl_to);
- $data[$wgContLang->getNSText( NS_CATEGORY ).':'.$row->cl_to] = $this->getFullText();
- $dbr->freeResult( $res );
- } else {
- $data = array();
+ if ( $dbr->numRows( $res ) > 0 ) {
+ foreach ( $res as $row ) {
+ // $data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$row->cl_to);
+ $data[$wgContLang->getNSText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
+ }
}
return $data;
}
/**
* Get a tree of parent categories
+ *
* @param $children \type{\array} an array with the children in the keys, to check for circular refs
* @return \type{\array} Tree of parent categories
*/
@@ -3285,22 +3615,21 @@ class Title {
$stack = array();
$parents = $this->getParentCategories();
- if( $parents ) {
- foreach( $parents as $parent => $current ) {
+ if ( $parents ) {
+ foreach ( $parents as $parent => $current ) {
if ( array_key_exists( $parent, $children ) ) {
# Circular reference
$stack[$parent] = array();
} else {
- $nt = Title::newFromText($parent);
+ $nt = Title::newFromText( $parent );
if ( $nt ) {
- $stack[$parent] = $nt->getParentCategoryTree( $children + array($parent => 1) );
+ $stack[$parent] = $nt->getParentCategoryTree( $children + array( $parent => 1 ) );
}
}
}
- return $stack;
- } else {
- return array();
}
+
+ return $stack;
}
@@ -3311,7 +3640,7 @@ class Title {
* @return \type{\array} Selection array
*/
public function pageCond() {
- if( $this->mArticleID > 0 ) {
+ if ( $this->mArticleID > 0 ) {
// PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
return array( 'page_id' => $this->mArticleID );
} else {
@@ -3323,14 +3652,14 @@ class Title {
* Get the revision ID of the previous revision
*
* @param $revId \type{\int} Revision ID. Get the revision that was before this one.
- * @param $flags \type{\int} GAID_FOR_UPDATE
+ * @param $flags \type{\int} Title::GAID_FOR_UPDATE
* @return \twotypes{\int,\bool} Old revision ID, or FALSE if none exists
*/
- public function getPreviousRevisionID( $revId, $flags=0 ) {
- $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+ public function getPreviousRevisionID( $revId, $flags = 0 ) {
+ $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
return $db->selectField( 'revision', 'rev_id',
array(
- 'rev_page' => $this->getArticleId($flags),
+ 'rev_page' => $this->getArticleId( $flags ),
'rev_id < ' . intval( $revId )
),
__METHOD__,
@@ -3342,14 +3671,14 @@ class Title {
* Get the revision ID of the next revision
*
* @param $revId \type{\int} Revision ID. Get the revision that was after this one.
- * @param $flags \type{\int} GAID_FOR_UPDATE
+ * @param $flags \type{\int} Title::GAID_FOR_UPDATE
* @return \twotypes{\int,\bool} Next revision ID, or FALSE if none exists
*/
- public function getNextRevisionID( $revId, $flags=0 ) {
- $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+ public function getNextRevisionID( $revId, $flags = 0 ) {
+ $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
return $db->selectField( 'revision', 'rev_id',
array(
- 'rev_page' => $this->getArticleId($flags),
+ 'rev_page' => $this->getArticleId( $flags ),
'rev_id > ' . intval( $revId )
),
__METHOD__,
@@ -3360,19 +3689,21 @@ class Title {
/**
* Get the first revision of the page
*
- * @param $flags \type{\int} GAID_FOR_UPDATE
+ * @param $flags \type{\int} Title::GAID_FOR_UPDATE
* @return Revision (or NULL if page doesn't exist)
*/
- public function getFirstRevision( $flags=0 ) {
- $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
- $pageId = $this->getArticleId($flags);
- if( !$pageId ) return null;
+ public function getFirstRevision( $flags = 0 ) {
+ $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+ $pageId = $this->getArticleId( $flags );
+ if ( !$pageId ) {
+ return null;
+ }
$row = $db->selectRow( 'revision', '*',
array( 'rev_page' => $pageId ),
__METHOD__,
array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
);
- if( !$row ) {
+ if ( !$row ) {
return null;
} else {
return new Revision( $row );
@@ -3392,11 +3723,11 @@ class Title {
/**
* Get the oldest revision timestamp of this page
*
- * @return string, MW timestamp
+ * @return String: MW timestamp
*/
public function getEarliestRevTime() {
$dbr = wfGetDB( DB_SLAVE );
- if( $this->exists() ) {
+ if ( $this->exists() ) {
$min = $dbr->selectField( 'revision',
'MIN(rev_timestamp)',
array( 'rev_page' => $this->getArticleId() ),
@@ -3425,9 +3756,32 @@ class Title {
}
/**
+ * Get the number of authors between the given revision IDs.
+ * Used for diffs and other things that really need it.
+ *
+ * @param $fromRevId \type{\int} Revision ID (first before range)
+ * @param $toRevId \type{\int} Revision ID (first after range)
+ * @param $limit \type{\int} Maximum number of authors
+ * @param $flags \type{\int} Title::GAID_FOR_UPDATE
+ * @return \type{\int}
+ */
+ public function countAuthorsBetween( $fromRevId, $toRevId, $limit, $flags = 0 ) {
+ $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+ $res = $db->select( 'revision', 'DISTINCT rev_user_text',
+ array(
+ 'rev_page = ' . $this->getArticleID(),
+ 'rev_id > ' . (int)$fromRevId,
+ 'rev_id < ' . (int)$toRevId
+ ), __METHOD__,
+ array( 'LIMIT' => $limit )
+ );
+ return (int)$db->numRows( $res );
+ }
+
+ /**
* Compare with another title.
*
- * @param \type{Title} $title
+ * @param $title \type{Title}
* @return \type{\bool} TRUE or FALSE
*/
public function equals( Title $title ) {
@@ -3439,9 +3793,11 @@ class Title {
/**
* Callback for usort() to do title sorts by (namespace, title)
+ *
+ * @return Integer: result of string comparison, or namespace comparison
*/
public static function compare( $a, $b ) {
- if( $a->getNamespace() == $b->getNamespace() ) {
+ if ( $a->getNamespace() == $b->getNamespace() ) {
return strcmp( $a->getText(), $b->getText() );
} else {
return $a->getNamespace() - $b->getNamespace();
@@ -3464,7 +3820,7 @@ class Title {
* If you want to know if a title can be meaningfully viewed, you should
* probably call the isKnown() method instead.
*
- * @return \type{\bool} TRUE or FALSE
+ * @return \type{\bool}
*/
public function exists() {
return $this->getArticleId() != 0;
@@ -3484,30 +3840,28 @@ class Title {
* existing code, but we might want to add an optional parameter to skip
* it and any other expensive checks.)
*
- * @return \type{\bool} TRUE or FALSE
+ * @return \type{\bool}
*/
public function isAlwaysKnown() {
- if( $this->mInterwiki != '' ) {
+ if ( $this->mInterwiki != '' ) {
return true; // any interwiki link might be viewable, for all we know
}
switch( $this->mNamespace ) {
- case NS_MEDIA:
- case NS_FILE:
- return wfFindFile( $this ); // file exists, possibly in a foreign repo
- case NS_SPECIAL:
- return SpecialPage::exists( $this->getDBkey() ); // valid special page
- case NS_MAIN:
- return $this->mDbkeyform == ''; // selflink, possibly with fragment
- case NS_MEDIAWIKI:
- // If the page is form Mediawiki:message/lang, calling wfMsgWeirdKey causes
- // the full l10n of that language to be loaded. That takes much memory and
- // isn't needed. So we strip the language part away.
- // Also, extension messages which are not loaded, are shown as red, because
- // we don't call MessageCache::loadAllMessages.
- list( $basename, /* rest */ ) = explode( '/', $this->mDbkeyform, 2 );
- return wfMsgWeirdKey( $basename ); // known system message
- default:
- return false;
+ case NS_MEDIA:
+ case NS_FILE:
+ return (bool)wfFindFile( $this ); // file exists, possibly in a foreign repo
+ case NS_SPECIAL:
+ return SpecialPage::exists( $this->getDBkey() ); // valid special page
+ case NS_MAIN:
+ return $this->mDbkeyform == ''; // selflink, possibly with fragment
+ case NS_MEDIAWIKI:
+ // If the page is form Mediawiki:message/lang, calling wfMsgWeirdKey causes
+ // the full l10n of that language to be loaded. That takes much memory and
+ // isn't needed. So we strip the language part away.
+ list( $basename, /* rest */ ) = explode( '/', $this->mDbkeyform, 2 );
+ return (bool)wfMsgWeirdKey( $basename ); // known system message
+ default:
+ return false;
}
}
@@ -3517,17 +3871,41 @@ class Title {
* links to the title should be rendered as "bluelinks" (as opposed to
* "redlinks" to non-existent pages).
*
- * @return \type{\bool} TRUE or FALSE
+ * @return \type{\bool}
*/
public function isKnown() {
- return $this->exists() || $this->isAlwaysKnown();
+ return $this->isAlwaysKnown() || $this->exists();
}
/**
- * Is this in a namespace that allows actual pages?
- *
- * @return \type{\bool} TRUE or FALSE
- */
+ * Does this page have source text?
+ *
+ * @return Boolean
+ */
+ public function hasSourceText() {
+ if ( $this->exists() ) {
+ return true;
+ }
+
+ if ( $this->mNamespace == NS_MEDIAWIKI ) {
+ // If the page doesn't exist but is a known system message, default
+ // message content will be displayed, same for language subpages
+ // Also, if the page is form Mediawiki:message/lang, calling wfMsgWeirdKey
+ // causes the full l10n of that language to be loaded. That takes much
+ // memory and isn't needed. So we strip the language part away.
+ list( $basename, /* rest */ ) = explode( '/', $this->mDbkeyform, 2 );
+ return (bool)wfMsgWeirdKey( $basename );
+ }
+
+ return false;
+ }
+
+ /**
+ * Is this in a namespace that allows actual pages?
+ *
+ * @return \type{\bool}
+ * @internal note -- uses hardcoded namespace index instead of constants
+ */
public function canExist() {
return $this->mNamespace >= 0 && $this->mNamespace != NS_MEDIA;
}
@@ -3549,34 +3927,39 @@ class Title {
/**
* Get the last touched timestamp
- * @param Database $db, optional db
+ *
+ * @param $db DatabaseBase: optional db
* @return \type{\string} Last touched timestamp
*/
public function getTouched( $db = null ) {
- $db = isset($db) ? $db : wfGetDB( DB_SLAVE );
+ $db = isset( $db ) ? $db : wfGetDB( DB_SLAVE );
$touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
return $touched;
}
/**
* Get the timestamp when this page was updated since the user last saw it.
- * @param User $user
- * @return mixed string/NULL
+ *
+ * @param $user User
+ * @return Mixed: string/null
*/
public function getNotificationTimestamp( $user = null ) {
global $wgUser, $wgShowUpdatedMarker;
// Assume current user if none given
- if( !$user ) $user = $wgUser;
+ if ( !$user ) {
+ $user = $wgUser;
+ }
// Check cache first
$uid = $user->getId();
- if( isset($this->mNotificationTimestamp[$uid]) ) {
+ // avoid isset here, as it'll return false for null entries
+ if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
return $this->mNotificationTimestamp[$uid];
}
- if( !$uid || !$wgShowUpdatedMarker ) {
+ if ( !$uid || !$wgShowUpdatedMarker ) {
return $this->mNotificationTimestamp[$uid] = false;
}
// Don't cache too much!
- if( count($this->mNotificationTimestamp) >= self::CACHE_MAX ) {
+ if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
$this->mNotificationTimestamp = array();
}
$dbr = wfGetDB( DB_SLAVE );
@@ -3593,22 +3976,24 @@ class Title {
/**
* Get the trackback URL for this page
+ *
* @return \type{\string} Trackback URL
*/
public function trackbackURL() {
global $wgScriptPath, $wgServer, $wgScriptExtension;
return "$wgServer$wgScriptPath/trackback$wgScriptExtension?article="
- . htmlspecialchars(urlencode($this->getPrefixedDBkey()));
+ . htmlspecialchars( urlencode( $this->getPrefixedDBkey() ) );
}
/**
* Get the trackback RDF for this page
+ *
* @return \type{\string} Trackback RDF
*/
public function trackbackRDF() {
- $url = htmlspecialchars($this->getFullURL());
- $title = htmlspecialchars($this->getText());
+ $url = htmlspecialchars( $this->getFullURL() );
+ $title = htmlspecialchars( $this->getText() );
$tburl = $this->trackbackURL();
// Autodiscovery RDF is placed in comments so HTML validator
@@ -3631,6 +4016,8 @@ class Title {
/**
* Generate strings used for xml 'id' names in monobook tabs
+ *
+ * @param $prepend string defaults to 'nstab-'
* @return \type{\string} XML 'id' name
*/
public function getNamespaceKey( $prepend = 'nstab-' ) {
@@ -3660,14 +4047,18 @@ class Title {
/**
* Returns true if this is a special page.
+ *
+ * @return boolean
*/
- public function isSpecialPage( ) {
+ public function isSpecialPage() {
return $this->getNamespace() == NS_SPECIAL;
}
/**
* Returns true if this title resolves to the named special page
+ *
* @param $name \type{\string} The special page name
+ * @return boolean
*/
public function isSpecial( $name ) {
if ( $this->getNamespace() == NS_SPECIAL ) {
@@ -3681,7 +4072,9 @@ class Title {
/**
* If the Title refers to a special page alias which is not the local default,
- * @return \type{Title} A new Title which points to the local default. Otherwise, returns $this.
+ *
+ * @return \type{Title} A new Title which points to the local default.
+ * Otherwise, returns $this.
*/
public function fixSpecialName() {
if ( $this->getNamespace() == NS_SPECIAL ) {
@@ -3701,7 +4094,7 @@ class Title {
* In other words, is this a content page, for the purposes of calculating
* statistics, etc?
*
- * @return \type{\bool} TRUE or FALSE
+ * @return Boolean
*/
public function isContentPage() {
return MWNamespace::isContent( $this->getNamespace() );
@@ -3723,7 +4116,9 @@ class Title {
'rd_title' => $this->getDBkey(),
'rd_from = page_id'
);
- if ( !is_null($ns) ) $where['page_namespace'] = $ns;
+ if ( !is_null( $ns ) ) {
+ $where['page_namespace'] = $ns;
+ }
$res = $dbr->select(
array( 'redirect', 'page' ),
@@ -3732,8 +4127,7 @@ class Title {
__METHOD__
);
-
- foreach( $res as $row ) {
+ foreach ( $res as $row ) {
$redirs[] = self::newFromRow( $row );
}
return $redirs;
@@ -3742,18 +4136,18 @@ class Title {
/**
* Check if this Title is a valid redirect target
*
- * @return \type{\bool} TRUE or FALSE
+ * @return \type{\bool}
*/
public function isValidRedirectTarget() {
global $wgInvalidRedirectTargets;
// invalid redirect targets are stored in a global array, but explicity disallow Userlogout here
- if( $this->isSpecial( 'Userlogout' ) ) {
+ if ( $this->isSpecial( 'Userlogout' ) ) {
return false;
}
- foreach( $wgInvalidRedirectTargets as $target ) {
- if( $this->isSpecial( $target ) ) {
+ foreach ( $wgInvalidRedirectTargets as $target ) {
+ if ( $this->isSpecial( $target ) ) {
return false;
}
}
@@ -3763,6 +4157,8 @@ class Title {
/**
* Get a backlink cache object
+ *
+ * @return object BacklinkCache
*/
function getBacklinkCache() {
if ( is_null( $this->mBacklinkCache ) ) {
@@ -3774,11 +4170,11 @@ class Title {
/**
* Whether the magic words __INDEX__ and __NOINDEX__ function for
* this page.
- * @return Bool
+ *
+ * @return Boolean
*/
- public function canUseNoindex(){
- global $wgArticleRobotPolicies, $wgContentNamespaces,
- $wgExemptFromUserRobotsControl;
+ public function canUseNoindex() {
+ global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
$bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
? $wgContentNamespaces
@@ -3788,16 +4184,66 @@ class Title {
}
+ /**
+ * Returns restriction types for the current Title
+ *
+ * @return array applicable restriction types
+ */
public function getRestrictionTypes() {
- global $wgRestrictionTypes;
- $types = $this->exists() ? $wgRestrictionTypes : array('create');
-
- if ( $this->getNamespace() == NS_FILE ) {
- $types[] = 'upload';
+ $types = self::getFilteredRestrictionTypes( $this->exists() );
+
+ if ( $this->getNamespace() != NS_FILE ) {
+ # Remove the upload restriction for non-file titles
+ $types = array_diff( $types, array( 'upload' ) );
}
-
+
wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
+
+ wfDebug( __METHOD__ . ': applicable restriction types for ' .
+ $this->getPrefixedText() . ' are ' . implode( ',', $types ) );
return $types;
}
+ /**
+ * Get a filtered list of all restriction types supported by this wiki.
+ * @param bool $exists True to get all restriction types that apply to
+ * titles that do exist, False for all restriction types that apply to
+ * titles that do not exist
+ * @return array
+ */
+ public static function getFilteredRestrictionTypes( $exists = true ) {
+ global $wgRestrictionTypes;
+ $types = $wgRestrictionTypes;
+ if ( $exists ) {
+ # Remove the create restriction for existing titles
+ $types = array_diff( $types, array( 'create' ) );
+ } else {
+ # Only the create and upload restrictions apply to non-existing titles
+ $types = array_intersect( $types, array( 'create', 'upload' ) );
+ }
+ return $types;
+ }
+
+ /**
+ * Returns the raw sort key to be used for categories, with the specified
+ * prefix. This will be fed to Collation::getSortKey() to get a
+ * binary sortkey that can be used for actual sorting.
+ *
+ * @param $prefix string The prefix to be used, specified using
+ * {{defaultsort:}} or like [[Category:Foo|prefix]]. Empty for no
+ * prefix.
+ * @return string
+ */
+ public function getCategorySortkey( $prefix = '' ) {
+ $unprefixed = $this->getText();
+ 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.
+ # In UCA, tab is the only character that can sort above LF
+ # so we strip both of them from the original prefix.
+ $prefix = strtr( $prefix, "\n\t", ' ' );
+ return "$prefix\n$unprefixed";
+ }
+ return $unprefixed;
+ }
}
diff --git a/includes/User.php b/includes/User.php
index fb19ddf2..5760003b 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -41,58 +41,13 @@ class PasswordError extends MWException {
* of the database.
*/
class User {
-
/**
- * \type{\arrayof{\string}} A list of default user toggles, i.e., boolean user
- * preferences that are displayed by Special:Preferences as checkboxes.
- * This list can be extended via the UserToggles hook or by
- * $wgContLang::getExtraUserToggles().
- * @showinitializer
+ * Global constants made accessible as class constants so that autoloader
+ * magic can be used.
*/
- public static $mToggles = array(
- 'highlightbroken',
- 'justify',
- 'hideminor',
- 'extendwatchlist',
- 'usenewrc',
- 'numberheadings',
- 'showtoolbar',
- 'editondblclick',
- 'editsection',
- 'editsectiononrightclick',
- 'showtoc',
- 'rememberpassword',
- 'editwidth',
- 'watchcreations',
- 'watchdefault',
- 'watchmoves',
- 'watchdeletion',
- 'minordefault',
- 'previewontop',
- 'previewonfirst',
- 'nocache',
- 'enotifwatchlistpages',
- 'enotifusertalkpages',
- 'enotifminoredits',
- 'enotifrevealaddr',
- 'shownumberswatching',
- 'fancysig',
- 'externaleditor',
- 'externaldiff',
- 'showjumplinks',
- 'uselivepreview',
- 'forceeditsummary',
- 'watchlisthideminor',
- 'watchlisthidebots',
- 'watchlisthideown',
- 'watchlisthideanons',
- 'watchlisthideliu',
- 'ccmeonemails',
- 'diffonly',
- 'showhiddencats',
- 'noconvertlink',
- 'norollbackdiff',
- );
+ const USER_TOKEN_LENGTH = USER_TOKEN_LENGTH;
+ const MW_USER_VERSION = MW_USER_VERSION;
+ const EDIT_TOKEN_SUFFIX = EDIT_TOKEN_SUFFIX;
/**
* \type{\arrayof{\string}} List of member variables which are saved to the
@@ -144,14 +99,18 @@ class User {
'deletedhistory',
'deletedtext',
'deleterevision',
+ 'disableaccount',
'edit',
'editinterface',
- 'editusercssjs',
+ 'editusercssjs', #deprecated
+ 'editusercss',
+ 'edituserjs',
'hideuser',
'import',
'importupload',
'ipblock-exempt',
'markbotedits',
+ 'mergehistory',
'minoredit',
'move',
'movefile',
@@ -168,12 +127,14 @@ class User {
'reupload',
'reupload-shared',
'rollback',
+ 'selenium',
'sendemail',
'siteadmin',
'suppressionlog',
'suppressredirect',
'suppressrevision',
'trackback',
+ 'unblockself',
'undelete',
'unwatchedpages',
'upload',
@@ -229,7 +190,7 @@ class User {
* @see newFromSession()
* @see newFromRow()
*/
- function User() {
+ function __construct() {
$this->clearInstanceCache( 'defaults' );
}
@@ -292,7 +253,7 @@ class User {
}
if ( !$data ) {
- wfDebug( "Cache miss for user {$this->mId}\n" );
+ wfDebug( "User: cache miss for user {$this->mId}\n" );
# Load from DB
if ( !$this->loadFromDatabase() ) {
# Can't load from ID, user is anonymous
@@ -300,7 +261,7 @@ class User {
}
$this->saveToCache();
} else {
- wfDebug( "Got user {$this->mId} from cache\n" );
+ wfDebug( "User: got user {$this->mId} from cache\n" );
# Restore from cache
foreach ( self::$mCacheVars as $name ) {
$this->$name = $data[$name];
@@ -345,7 +306,7 @@ class User {
* User::getCanonicalName(), except that true is accepted as an alias
* for 'valid', for BC.
*
- * @return \type{User} The User object, or false if the username is invalid
+ * @return User The User object, or false if the username is invalid
* (e.g. if it contains illegal characters or is an IP address). If the
* username is not present in the database, the result will be a user object
* with a name, zero user ID and default settings.
@@ -600,20 +561,34 @@ class User {
* either by batch processes or by user accounts which have
* already been created.
*
- * Additional character blacklisting may be added here
- * rather than in isValidUserName() to avoid disrupting
- * existing accounts.
+ * Additional blacklisting may be added here rather than in
+ * isValidUserName() to avoid disrupting existing accounts.
*
* @param $name \string String to match
* @return \bool True or false
*/
static function isCreatableName( $name ) {
global $wgInvalidUsernameCharacters;
- return
- self::isUsableName( $name ) &&
- // Registration-time character blacklisting...
- !preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name );
+ // Ensure that the username isn't longer than 235 bytes, so that
+ // (at least for the builtin skins) user javascript and css files
+ // will work. (bug 23080)
+ if( strlen( $name ) > 235 ) {
+ wfDebugLog( 'username', __METHOD__ .
+ ": '$name' invalid due to length" );
+ return false;
+ }
+
+ // Preg yells if you try to give it an empty string
+ if( $wgInvalidUsernameCharacters ) {
+ if( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
+ wfDebugLog( 'username', __METHOD__ .
+ ": '$name' invalid due to wgInvalidUsernameCharacters" );
+ return false;
+ }
+ }
+
+ return self::isUsableName( $name );
}
/**
@@ -635,6 +610,11 @@ class User {
*/
function getPasswordValidity( $password ) {
global $wgMinimalPasswordLength, $wgContLang;
+
+ static $blockedLogins = array(
+ 'Useruser' => 'Passpass', 'Useruser1' => 'Passpass1', # r75589
+ 'Apitestsysop' => 'testpass', 'Apitestuser' => 'testpass' # r75605
+ );
$result = false; //init $result to false for the internal checks
@@ -646,6 +626,8 @@ class User {
return 'passwordtooshort';
} elseif ( $wgContLang->lc( $password ) == $wgContLang->lc( $this->mName ) ) {
return 'password-name-match';
+ } elseif ( isset( $blockedLogins[ $this->getName() ] ) && $password == $blockedLogins[ $this->getName() ] ) {
+ return 'password-login-forbidden';
} else {
//it seems weird returning true here, but this is because of the
//initialization of $result to false above. If the hook is never run or it
@@ -663,14 +645,28 @@ class User {
/**
* Does a string look like an e-mail address?
*
- * There used to be a regular expression here, it got removed because it
- * rejected valid addresses. Actually just check if there is '@' somewhere
- * in the given address.
+ * This validates an email address using an HTML5 specification found at:
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
+ * Which as of 2011-01-24 says:
*
- * @todo Check for RFC 2822 compilance (bug 959)
+ * A valid e-mail address is a string that matches the ABNF production
+ * 1*( atext / "." ) "@" ldh-str *( "." ldh-str ) where atext is defined
+ * in RFC 5322 section 3.2.3, and ldh-str is defined in RFC 1034 section
+ * 3.5.
*
- * @param $addr \string E-mail address
- * @return \bool True or false
+ * This function is an implementation of the specification as requested in
+ * bug 22449.
+ *
+ * Client-side forms will use the same standard validation rules via JS or
+ * HTML 5 validation; additional restrictions can be enforced server-side
+ * by extensions via the 'isValidEmailAddr' hook.
+ *
+ * Note that this validation doesn't 100% match RFC 2822, but is believed
+ * to be liberal enough for wide use. Some invalid addresses will still
+ * pass validation here.
+ *
+ * @param $addr String E-mail address
+ * @return Bool
*/
public static function isValidEmailAddr( $addr ) {
$result = null;
@@ -678,7 +674,22 @@ class User {
return $result;
}
- return strpos( $addr, '@' ) !== false;
+ // Please note strings below are enclosed in brackets [], this make the
+ // hyphen "-" a range indicator. Hence it is double backslashed below.
+ // See bug 26948
+ $rfc5322_atext = "a-z0-9!#$%&'*+\\-\/=?^_`{|}~" ;
+ $rfc1034_ldh_str = "a-z0-9\\-" ;
+
+ $HTML5_email_regexp = "/
+ ^ # start of string
+ [$rfc5322_atext\\.]+ # user part which is liberal :p
+ @ # 'apostrophe'
+ [$rfc1034_ldh_str]+ # First domain part
+ (\\.[$rfc1034_ldh_str]+)* # Following part prefixed with a dot
+ $ # End of string
+ /ix" ; // case Insensitive, eXtended
+
+ return (bool) preg_match( $HTML5_email_regexp, $addr );
}
/**
@@ -711,7 +722,6 @@ class User {
}
# Reject various classes of invalid names
- $name = $t->getText();
global $wgAuth;
$name = $wgAuth->getCanonicalName( $t->getText() );
@@ -806,7 +816,7 @@ class User {
function loadDefaults( $name = false ) {
wfProfileIn( __METHOD__ );
- global $wgCookiePrefix;
+ global $wgRequest;
$this->mId = 0;
$this->mName = $name;
@@ -817,8 +827,8 @@ class User {
$this->mOptionOverrides = null;
$this->mOptionsLoaded = false;
- if ( isset( $_COOKIE[$wgCookiePrefix.'LoggedOut'] ) ) {
- $this->mTouched = wfTimestamp( TS_MW, $_COOKIE[$wgCookiePrefix.'LoggedOut'] );
+ if( $wgRequest->getCookie( 'LoggedOut' ) !== null ) {
+ $this->mTouched = wfTimestamp( TS_MW, $wgRequest->getCookie( 'LoggedOut' ) );
} else {
$this->mTouched = '0'; # Allow any pages to be cached
}
@@ -849,7 +859,7 @@ class User {
* @return \bool True if the user is logged in, false otherwise.
*/
private function loadFromSession() {
- global $wgMemc, $wgCookiePrefix, $wgExternalAuthType, $wgAutocreatePolicy;
+ global $wgRequest, $wgExternalAuthType, $wgAutocreatePolicy;
$result = null;
wfRunHooks( 'UserLoadFromSession', array( $this, &$result ) );
@@ -865,8 +875,8 @@ class User {
}
}
- if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) {
- $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] );
+ if ( $wgRequest->getCookie( 'UserID' ) !== null ) {
+ $sId = intval( $wgRequest->getCookie( 'UserID' ) );
if( isset( $_SESSION['wsUserID'] ) && $sId != $_SESSION['wsUserID'] ) {
$this->loadDefaults(); // Possible collision!
wfDebugLog( 'loginSessions', "Session user ID ({$_SESSION['wsUserID']}) and
@@ -888,15 +898,14 @@ class User {
if ( isset( $_SESSION['wsUserName'] ) ) {
$sName = $_SESSION['wsUserName'];
- } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserName"] ) ) {
- $sName = $_COOKIE["{$wgCookiePrefix}UserName"];
+ } else if ( $wgRequest->getCookie('UserName') !== null ) {
+ $sName = $wgRequest->getCookie('UserName');
$_SESSION['wsUserName'] = $sName;
} else {
$this->loadDefaults();
return false;
}
- $passwordCorrect = FALSE;
$proposedUser = User::newFromId( $sId );
if ( !$proposedUser->isLoggedIn() ) {
# Not a valid ID
@@ -914,8 +923,8 @@ class User {
if ( isset( $_SESSION['wsToken'] ) ) {
$passwordCorrect = $proposedUser->getToken() === $_SESSION['wsToken'];
$from = 'session';
- } else if ( isset( $_COOKIE["{$wgCookiePrefix}Token"] ) ) {
- $passwordCorrect = $proposedUser->getToken() === $_COOKIE["{$wgCookiePrefix}Token"];
+ } else if ( $wgRequest->getCookie( 'Token' ) !== null ) {
+ $passwordCorrect = $proposedUser->getToken() === $wgRequest->getCookie( 'Token' );
$from = 'cookie';
} else {
# No session or persistent login cookie
@@ -926,11 +935,11 @@ class User {
if ( ( $sName === $proposedUser->getName() ) && $passwordCorrect ) {
$this->loadFromUserObject( $proposedUser );
$_SESSION['wsToken'] = $this->mToken;
- wfDebug( "Logged in from $from\n" );
+ wfDebug( "User: logged in from $from\n" );
return true;
} else {
# Invalid credentials
- wfDebug( "Can't log in from $from, invalid credentials\n" );
+ wfDebug( "User: can't log in from $from, invalid credentials\n" );
$this->loadDefaults();
return false;
}
@@ -1023,7 +1032,7 @@ class User {
array( 'ug_user' => $this->mId ),
__METHOD__ );
$this->mGroups = array();
- while( $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$this->mGroups[] = $row->ug_group;
}
}
@@ -1068,7 +1077,7 @@ class User {
/**
* default language setting
*/
- $variant = $wgContLang->getPreferredVariant( false );
+ $variant = $wgContLang->getDefaultVariant();
$defOpt['variant'] = $variant;
$defOpt['language'] = $variant;
foreach( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
@@ -1094,22 +1103,6 @@ class User {
}
}
- /**
- * Get a list of user toggle names
- * @return \type{\arrayof{\string}} Array of user toggle names
- */
- static function getToggles() {
- global $wgContLang, $wgUseRCPatrol;
- $extraToggles = array();
- wfRunHooks( 'UserToggles', array( &$extraToggles ) );
- if( $wgUseRCPatrol ) {
- $extraToggles[] = 'hidepatrolled';
- $extraToggles[] = 'newpageshidepatrolled';
- $extraToggles[] = 'watchlisthidepatrolled';
- }
- return array_merge( self::$mToggles, $extraToggles, $wgContLang->getExtraUserToggles() );
- }
-
/**
* Get blocking information
@@ -1167,7 +1160,7 @@ class User {
if ( $this->mBlock->load( $ip , $this->mId ) ) {
wfDebug( __METHOD__ . ": Found block.\n" );
$this->mBlockedby = $this->mBlock->mBy;
- if( $this->mBlockedby == "0" )
+ if( $this->mBlockedby == 0 )
$this->mBlockedby = $this->mBlock->mByName;
$this->mBlockreason = $this->mBlock->mReason;
$this->mHideName = $this->mBlock->mHideName;
@@ -1236,7 +1229,6 @@ class User {
wfProfileIn( __METHOD__ );
$found = false;
- $host = '';
// FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
if( IP::isIPv4( $ip ) ) {
# Reverse IP, bug 21255
@@ -1507,7 +1499,7 @@ class User {
/**
* Get the user's ID.
- * @return \int The user's ID; 0 if the user is anonymous or nonexistent
+ * @return Integer The user's ID; 0 if the user is anonymous or nonexistent
*/
function getId() {
if( $this->mId === null and $this->mName !== null
@@ -1813,11 +1805,11 @@ class User {
}
if( !$this->isValidPassword( $str ) ) {
- global $wgMinimalPasswordLength;
+ global $wgMinimalPasswordLength;
$valid = $this->getPasswordValidity( $str );
throw new PasswordError( wfMsgExt( $valid, array( 'parsemag' ),
$wgMinimalPasswordLength ) );
- }
+ }
}
if( !$wgAuth->setPassword( $this, $str ) ) {
@@ -2045,7 +2037,7 @@ class User {
if ( $oname == 'skin' ) {
# Clear cached skin, so the new one displays immediately in Special:Preferences
- unset( $this->mSkin );
+ $this->mSkin = null;
}
// Explicitly NULL values should refer to defaults
@@ -2083,6 +2075,20 @@ class User {
}
/**
+ * Get the user preferred stub threshold
+ */
+ function getStubThreshold() {
+ global $wgMaxArticleSize; # Maximum article size, in Kb
+ $threshold = intval( $this->getOption( 'stubthreshold' ) );
+ if ( $threshold > $wgMaxArticleSize * 1024 ) {
+ # If they have set an impossible value, disable the preference
+ # so we can use the parser cache again.
+ $threshold = 0;
+ }
+ return $threshold;
+ }
+
+ /**
* Get the permissions this user has.
* @return \type{\arrayof{\string}} Array of permission names
*/
@@ -2164,7 +2170,7 @@ class User {
'ug_user' => $this->getID(),
'ug_group' => $group,
),
- 'User::addGroup',
+ __METHOD__,
array( 'IGNORE' ) );
}
@@ -2187,8 +2193,7 @@ class User {
array(
'ug_user' => $this->getID(),
'ug_group' => $group,
- ),
- 'User::removeGroup' );
+ ), __METHOD__ );
$this->loadGroups();
$this->mGroups = array_diff( $this->mGroups, array( $group ) );
@@ -2226,11 +2231,12 @@ class User {
/**
* Check if user is allowed to access a feature / make an action
* @param $action \string action to be checked
- * @return \bool True if action is allowed, else false
+ * @return Boolean: True if action is allowed, else false
*/
function isAllowed( $action = '' ) {
- if ( $action === '' )
+ if ( $action === '' ) {
return true; // In the spirit of DWIM
+ }
# Patrolling may not be enabled
if( $action === 'patrol' || $action === 'autopatrol' ) {
global $wgUseRCPatrol, $wgUseNPPatrol;
@@ -2244,7 +2250,7 @@ class User {
/**
* Check whether to enable recent changes patrol features for this user
- * @return \bool True or false
+ * @return Boolean: True or false
*/
public function useRCPatrol() {
global $wgUseRCPatrol;
@@ -2266,33 +2272,41 @@ class User {
* @return Skin The current skin
* @todo FIXME : need to check the old failback system [AV]
*/
- function &getSkin( $t = null ) {
- if ( !isset( $this->mSkin ) ) {
- wfProfileIn( __METHOD__ );
+ function getSkin( $t = null ) {
+ if( !$this->mSkin ) {
+ global $wgOut;
+ $this->mSkin = $this->createSkinObject();
+ $this->mSkin->setTitle( $wgOut->getTitle() );
+ }
+ if ( $t && ( !$this->mSkin->getTitle() || !$t->equals( $this->mSkin->getTitle() ) ) ) {
+ $skin = $this->createSkinObject();
+ $skin->setTitle( $t );
+ return $skin;
+ } else {
+ return $this->mSkin;
+ }
+ }
- global $wgHiddenPrefs;
- if( !in_array( 'skin', $wgHiddenPrefs ) ) {
- # get the user skin
- global $wgRequest;
- $userSkin = $this->getOption( 'skin' );
- $userSkin = $wgRequest->getVal( 'useskin', $userSkin );
- } else {
- # if we're not allowing users to override, then use the default
- global $wgDefaultSkin;
- $userSkin = $wgDefaultSkin;
- }
+ // Creates a Skin object, for getSkin()
+ private function createSkinObject() {
+ wfProfileIn( __METHOD__ );
- $this->mSkin =& Skin::newFromKey( $userSkin );
- wfProfileOut( __METHOD__ );
- }
- if( $t || !$this->mSkin->getTitle() ) {
- if ( !$t ) {
- global $wgOut;
- $t = $wgOut->getTitle();
- }
- $this->mSkin->setTitle( $t );
+ global $wgHiddenPrefs;
+ if( !in_array( 'skin', $wgHiddenPrefs ) ) {
+ global $wgRequest;
+ # get the user skin
+ $userSkin = $this->getOption( 'skin' );
+ $userSkin = $wgRequest->getVal( 'useskin', $userSkin );
+ } else {
+ # if we're not allowing users to override, then use the default
+ global $wgDefaultSkin;
+ $userSkin = $wgDefaultSkin;
}
- return $this->mSkin;
+
+ $skin = Skin::newFromKey( $userSkin );
+ wfProfileOut( __METHOD__ );
+
+ return $skin;
}
/**
@@ -2424,7 +2438,9 @@ class User {
$this->mOptionsLoaded = true;
$this->mOptionOverrides = array();
- $this->mOptions = array();
+ // If an option is not set in $str, use the default value
+ $this->mOptions = self::getDefaultOptions();
+
$a = explode( "\n", $str );
foreach ( $a as $s ) {
$m = array();
@@ -2536,8 +2552,8 @@ class User {
'user_newpassword' => $this->mNewpassword,
'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ),
'user_real_name' => $this->mRealName,
- 'user_email' => $this->mEmail,
- 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
+ 'user_email' => $this->mEmail,
+ 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_options' => '',
'user_touched' => $dbw->timestamp( $this->mTouched ),
'user_token' => $this->mToken,
@@ -2595,12 +2611,13 @@ class User {
}
$dbw = wfGetDB( DB_MASTER );
$seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
+
$fields = array(
'user_id' => $seqVal,
'user_name' => $name,
'user_password' => $user->mPassword,
'user_newpassword' => $user->mNewpassword,
- 'user_newpass_time' => $dbw->timestamp( $user->mNewpassTime ),
+ 'user_newpass_time' => $dbw->timestampOrNull( $user->mNewpassTime ),
'user_email' => $user->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
'user_real_name' => $user->mRealName,
@@ -2634,7 +2651,7 @@ class User {
'user_name' => $this->mName,
'user_password' => $this->mPassword,
'user_newpassword' => $this->mNewpassword,
- 'user_newpass_time' => $dbw->timestamp( $this->mNewpassTime ),
+ 'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ),
'user_email' => $this->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_real_name' => $this->mRealName,
@@ -2682,6 +2699,7 @@ class User {
* which will give them a chance to modify this key based on their own
* settings.
*
+ * @deprecated use the ParserOptions object to get the relevant options
* @return \string Page rendering hash
*/
function getPageRenderingHash() {
@@ -2689,13 +2707,15 @@ class User {
if( $this->mHash ){
return $this->mHash;
}
+ wfDeprecated( __METHOD__ );
// stubthreshold is only included below for completeness,
- // it will always be 0 when this function is called by parsercache.
+ // since it disables the parser cache, its value will always
+ // be 0 when this function is called by parsercache.
$confstr = $this->getOption( 'math' );
- $confstr .= '!' . $this->getOption( 'stubthreshold' );
- if ( $wgUseDynamicDates ) {
+ $confstr .= '!' . $this->getStubThreshold();
+ if ( $wgUseDynamicDates ) { # This is wrong (bug 24714)
$confstr .= '!' . $this->getDatePreference();
}
$confstr .= '!' . ( $this->getOption( 'numberheadings' ) ? '1' : '' );
@@ -2705,6 +2725,9 @@ class User {
$extra = $wgContLang->getExtraHashOptions();
$confstr .= $extra;
+ // Since the skin could be overloading link(), it should be
+ // included here but in practice, none of our skins do that.
+
$confstr .= $wgRenderHashAppend;
// Give a chance for extensions to modify the hash, if they have
@@ -2728,7 +2751,7 @@ class User {
/**
* Get whether the user is blocked from using Special:Emailuser.
- * @return \bool True if blocked
+ * @return Boolean: True if blocked
*/
function isBlockedFromEmailuser() {
$this->getBlockedStatus();
@@ -2737,23 +2760,16 @@ class User {
/**
* Get whether the user is allowed to create an account.
- * @return \bool True if allowed
+ * @return Boolean: True if allowed
*/
function isAllowedToCreateAccount() {
return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
}
/**
- * @deprecated
- */
- function setLoaded( $loaded ) {
- wfDeprecated( __METHOD__ );
- }
-
- /**
* Get this user's personal page title.
*
- * @return \type{Title} User's personal page title
+ * @return Title: User's personal page title
*/
function getUserPage() {
return Title::makeTitle( NS_USER, $this->getName() );
@@ -2762,7 +2778,7 @@ class User {
/**
* Get this user's talk page title.
*
- * @return \type{Title} User's talk page title
+ * @return Title: User's talk page title
*/
function getTalkPage() {
$title = $this->getUserPage();
@@ -2771,24 +2787,24 @@ class User {
/**
* Get the maximum valid user ID.
- * @return \int User ID
+ * @return Integer: User ID
* @static
*/
function getMaxID() {
static $res; // cache
- if ( isset( $res ) )
+ if ( isset( $res ) ) {
return $res;
- else {
+ } else {
$dbr = wfGetDB( DB_SLAVE );
- return $res = $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
+ return $res = $dbr->selectField( 'user', 'max(user_id)', false, __METHOD__ );
}
}
/**
* Determine whether the user is a newbie. Newbies are either
* anonymous IPs, or the most recently created accounts.
- * @return \bool True if the user is a newbie
+ * @return Boolean: True if the user is a newbie
*/
function isNewbie() {
return !$this->isAllowed( 'autoconfirmed' );
@@ -2796,8 +2812,8 @@ class User {
/**
* Check to see if the given clear-text password is one of the accepted passwords
- * @param $password \string user password.
- * @return \bool True if the given password is correct, otherwise False.
+ * @param $password String: user password.
+ * @return Boolean: True if the given password is correct, otherwise False.
*/
function checkPassword( $password ) {
global $wgAuth;
@@ -2807,7 +2823,7 @@ class User {
// are shorter than this, doesn't mean people wont be able
// to. Certain authentication plugins do NOT want to save
// domain passwords in a mysql database, so we should
- // check this (incase $wgAuth->strict() is false).
+ // check this (in case $wgAuth->strict() is false).
if( !$this->isValidPassword( $password ) ) {
return false;
}
@@ -2837,12 +2853,14 @@ class User {
/**
* Check if the given clear-text password matches the temporary password
* sent by e-mail for password reset operations.
- * @return \bool True if matches, false otherwise
+ * @return Boolean: True if matches, false otherwise
*/
function checkTemporaryPassword( $plaintext ) {
global $wgNewPasswordExpiry;
if( self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ) ) {
- $this->load();
+ if ( is_null( $this->mNewpassTime ) ) {
+ return true;
+ }
$expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgNewPasswordExpiry;
return ( time() < $expiry );
} else {
@@ -2895,7 +2913,7 @@ class User {
*
* @param $val \string Input value to compare
* @param $salt \string Optional function-specific data for hashing
- * @return \bool Whether the token matches
+ * @return Boolean: Whether the token matches
*/
function matchEditToken( $val, $salt = '' ) {
$sessionToken = $this->editToken( $salt );
@@ -2911,7 +2929,7 @@ class User {
*
* @param $val \string Input value to compare
* @param $salt \string Optional function-specific data for hashing
- * @return \bool Whether the token matches
+ * @return Boolean: Whether the token matches
*/
function matchEditTokenNoSuffix( $val, $salt = '' ) {
$sessionToken = $this->editToken( $salt );
@@ -2922,9 +2940,10 @@ class User {
* Generate a new e-mail confirmation token and send a confirmation/invalidation
* mail to the user's given address.
*
- * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure.
+ * @param $type String: message to send, either "created", "changed" or "set"
+ * @return Status object
*/
- function sendConfirmationMail() {
+ function sendConfirmationMail( $type = 'created' ) {
global $wgLang;
$expiration = null; // gets passed-by-ref and defined in next line.
$token = $this->confirmationToken( $expiration );
@@ -2932,8 +2951,16 @@ class User {
$invalidateURL = $this->invalidationTokenUrl( $token );
$this->saveSettings();
+ if ( $type == 'created' || $type === false ) {
+ $message = 'confirmemail_body';
+ } elseif ( $type === true ) {
+ $message = 'confirmemail_body_changed';
+ } else {
+ $message = 'confirmemail_body_' . $type;
+ }
+
return $this->sendMail( wfMsg( 'confirmemail_subject' ),
- wfMsg( 'confirmemail_body',
+ wfMsg( $message,
wfGetIP(),
$this->getName(),
$url,
@@ -2951,16 +2978,17 @@ class User {
* @param $body \string Message body
* @param $from \string Optional From address; if unspecified, default $wgPasswordSender will be used
* @param $replyto \string Reply-To address
- * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure
+ * @return Status object
*/
function sendMail( $subject, $body, $from = null, $replyto = null ) {
if( is_null( $from ) ) {
- global $wgPasswordSender;
- $from = $wgPasswordSender;
+ global $wgPasswordSender, $wgPasswordSenderName;
+ $sender = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
+ } else {
+ $sender = new MailAddress( $from );
}
$to = new MailAddress( $this );
- $sender = new MailAddress( $from );
return UserMailer::send( $to, $sender, $subject, $body, $replyto );
}
@@ -3069,7 +3097,7 @@ class User {
/**
* Is this user allowed to send e-mails within limits of current
* site configuration?
- * @return \bool True if allowed
+ * @return Boolean: True if allowed
*/
function canSendEmail() {
global $wgEnableEmail, $wgEnableUserEmail;
@@ -3084,7 +3112,7 @@ class User {
/**
* Is this user allowed to receive e-mails within limits of current
* site configuration?
- * @return \bool True if allowed
+ * @return Boolean: True if allowed
*/
function canReceiveEmail() {
return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
@@ -3098,7 +3126,7 @@ class User {
* confirmed their address by returning a code or using a password
* sent to the address from the wiki.
*
- * @return \bool True if confirmed
+ * @return Boolean: True if confirmed
*/
function isEmailConfirmed() {
global $wgEmailAuthentication;
@@ -3119,7 +3147,7 @@ class User {
/**
* Check whether there is an outstanding request for e-mail confirmation.
- * @return \bool True if pending
+ * @return Boolean: True if pending
*/
function isEmailConfirmationPending() {
global $wgEmailAuthentication;
@@ -3210,8 +3238,6 @@ class User {
* @return \string Localized descriptive group name
*/
static function getGroupName( $group ) {
- global $wgMessageCache;
- $wgMessageCache->loadAllMessages();
$key = "group-$group";
$name = wfMsg( $key );
return $name == '' || wfEmptyMsg( $key, $name )
@@ -3226,8 +3252,6 @@ class User {
* @return \string Localized name for group member
*/
static function getGroupMember( $group ) {
- global $wgMessageCache;
- $wgMessageCache->loadAllMessages();
$key = "group-$group-member";
$name = wfMsg( $key );
return $name == '' || wfEmptyMsg( $key, $name )
@@ -3239,7 +3263,7 @@ class User {
* Return the set of defined explicit groups.
* The implicit groups (by default *, 'user' and 'autoconfirmed')
* are not included, as they are defined automatically, not in the database.
- * @return \type{\arrayof{\string}} Array of internal group names
+ * @return Array of internal group names
*/
static function getAllGroups() {
global $wgGroupPermissions, $wgRevokePermissions;
@@ -3251,7 +3275,7 @@ class User {
/**
* Get a list of all available permissions.
- * @return \type{\arrayof{\string}} Array of permission names
+ * @return Array of permission names
*/
static function getAllRights() {
if ( self::$mAllRights === false ) {
@@ -3284,8 +3308,6 @@ class User {
* @return \types{\type{Title},\bool} Title of the page if it exists, false otherwise
*/
static function getGroupPage( $group ) {
- global $wgMessageCache;
- $wgMessageCache->loadAllMessages();
$page = wfMsgForContent( 'grouppage-' . $group );
if( !wfEmptyMsg( 'grouppage-' . $group, $page ) ) {
$title = Title::newFromText( $page );
@@ -3498,8 +3520,6 @@ class User {
* @return \string Localized description of the right
*/
static function getRightDescription( $right ) {
- global $wgMessageCache;
- $wgMessageCache->loadAllMessages();
$key = "right-$right";
$name = wfMsg( $key );
return $name == '' || wfEmptyMsg( $key, $name )
@@ -3556,10 +3576,9 @@ class User {
* @param $hash \string Password hash
* @param $password \string Plain-text password to compare
* @param $userId \string User ID for old-style password salt
- * @return \bool
+ * @return Boolean:
*/
static function comparePasswords( $hash, $password, $userId = false ) {
- $m = false;
$type = substr( $hash, 0, 3 );
$result = false;
@@ -3582,28 +3601,34 @@ class User {
/**
* Add a newuser log entry for this user
+ *
* @param $byEmail Boolean: account made by email?
+ * @param $reason String: user supplied reason
*/
- public function addNewUserLogEntry( $byEmail = false ) {
- global $wgUser, $wgNewUserLog;
+ public function addNewUserLogEntry( $byEmail = false, $reason = '' ) {
+ global $wgUser, $wgContLang, $wgNewUserLog;
if( empty( $wgNewUserLog ) ) {
return true; // disabled
}
if( $this->getName() == $wgUser->getName() ) {
$action = 'create';
- $message = '';
} else {
$action = 'create2';
- $message = $byEmail
- ? wfMsgForContent( 'newuserlog-byemail' )
- : '';
+ if ( $byEmail ) {
+ if ( $reason === '' ) {
+ $reason = wfMsgForContent( 'newuserlog-byemail' );
+ } else {
+ $reason = $wgContLang->commaList( array(
+ $reason, wfMsgForContent( 'newuserlog-byemail' ) ) );
+ }
+ }
}
$log = new LogPage( 'newusers' );
$log->addEntry(
$action,
$this->getUserPage(),
- $message,
+ $reason,
array( $this->getId() )
);
return true;
@@ -3614,8 +3639,8 @@ class User {
* Used by things like CentralAuth and perhaps other authplugins.
*/
public function addNewUserLogEntryAutoCreate() {
- global $wgNewUserLog;
- if( empty( $wgNewUserLog ) ) {
+ global $wgNewUserLog, $wgLogAutocreatedAccounts;
+ if( !$wgNewUserLog || !$wgLogAutocreatedAccounts ) {
return true; // disabled
}
$log = new LogPage( 'newusers', false );
@@ -3632,12 +3657,12 @@ class User {
// Maybe load from the object
if ( !is_null( $this->mOptionOverrides ) ) {
- wfDebug( "Loading options for user " . $this->getId() . " from override cache.\n" );
+ wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
foreach( $this->mOptionOverrides as $key => $value ) {
$this->mOptions[$key] = $value;
}
} else {
- wfDebug( "Loading options for user " . $this->getId() . " from database.\n" );
+ wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
// Load from database
$dbr = wfGetDB( DB_SLAVE );
@@ -3648,7 +3673,7 @@ class User {
__METHOD__
);
- while( $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$this->mOptionOverrides[$row->up_property] = $row->up_value;
$this->mOptions[$row->up_property] = $row->up_value;
}
@@ -3718,6 +3743,10 @@ class User {
*
* Obviously, you still need to do server-side checking.
*
+ * NOTE: A combination of bugs in various browsers means that this function
+ * actually just returns array() unconditionally at the moment. May as
+ * well keep it around for when the browser bugs get fixed, though.
+ *
* @return array Array of HTML attributes suitable for feeding to
* Html::element(), directly or indirectly. (Don't feed to Xml::*()!
* That will potentially output invalid XHTML 1.0 Transitional, and will
@@ -3732,7 +3761,14 @@ class User {
# Note that the pattern requirement will always be satisfied if the
# input is empty, so we need required in all cases.
- $ret = array( 'required' );
+ #
+ # FIXME (bug 23769): This needs to not claim the password is required
+ # if e-mail confirmation is being used. Since HTML5 input validation
+ # is b0rked anyway in some browsers, just return nothing. When it's
+ # re-enabled, fix this code to not output required for e-mail
+ # registration.
+ #$ret = array( 'required' );
+ $ret = array();
# We can't actually do this right now, because Opera 9.6 will print out
# the entered password visibly in its error message! When other
@@ -3750,4 +3786,92 @@ class User {
return $ret;
}
+
+ /**
+ * Format the user message using a hook, a template, or, failing these, a static format.
+ * @param $subject String the subject of the message
+ * @param $text String the content of the message
+ * @param $signature String the signature, if provided.
+ */
+ static protected function formatUserMessage( $subject, $text, $signature ) {
+ if ( wfRunHooks( 'FormatUserMessage',
+ array( $subject, &$text, $signature ) ) ) {
+
+ $signature = empty($signature) ? "~~~~~" : "{$signature} ~~~~~";
+
+ $template = Title::newFromText( wfMsgForContent( 'usermessage-template' ) );
+ if ( !$template
+ || $template->getNamespace() !== NS_TEMPLATE
+ || !$template->exists() ) {
+ $text = "\n== $subject ==\n\n$text\n\n-- $signature";
+ } else {
+ $text = '{{'. $template->getText()
+ . " | subject=$subject | body=$text | signature=$signature }}";
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * Leave a user a message
+ * @param $subject String the subject of the message
+ * @param $text String the message to leave
+ * @param $signature String Text to leave in the signature
+ * @param $summary String the summary for this change, defaults to
+ * "Leave system message."
+ * @param $editor User The user leaving the message, defaults to
+ * "{{MediaWiki:usermessage-editor}}"
+ * @param $flags Int default edit flags
+ *
+ * @return boolean true if it was successful
+ */
+ public function leaveUserMessage( $subject, $text, $signature = "",
+ $summary = null, $editor = null, $flags = 0 ) {
+ if ( !isset( $summary ) ) {
+ $summary = wfMsgForContent( 'usermessage-summary' );
+ }
+
+ if ( !isset( $editor ) ) {
+ $editor = User::newFromName( wfMsgForContent( 'usermessage-editor' ) );
+ if ( !$editor->isLoggedIn() ) {
+ $editor->addToDatabase();
+ }
+ }
+
+ $article = new Article( $this->getTalkPage() );
+ wfRunHooks( 'SetupUserMessageArticle',
+ array( $this, &$article, $subject, $text, $signature, $summary, $editor ) );
+
+
+ $text = self::formatUserMessage( $subject, $text, $signature );
+ $flags = $article->checkFlags( $flags );
+
+ if ( $flags & EDIT_UPDATE ) {
+ $text = $article->getContent() . $text;
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ try {
+ $status = $article->doEdit( $text, $summary, $flags, false, $editor );
+ } catch ( DBQueryError $e ) {
+ $status = Status::newFatal("DB Error");
+ }
+
+ if ( $status->isGood() ) {
+ // Set newtalk with the right user ID
+ $this->setNewtalk( true );
+ wfRunHooks( 'AfterUserMessage',
+ array( $this, $article, $summary, $text, $signature, $summary, $editor ) );
+ $dbw->commit();
+ } else {
+ // The article was concurrently created
+ wfDebug( __METHOD__ . ": Error ".$status->getWikiText() );
+ $dbw->rollback();
+ }
+
+ return $status->isGood();
+ }
}
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
index daf8b621..c15843d9 100644
--- a/includes/UserMailer.php
+++ b/includes/UserMailer.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Classes used to send e-mails
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
@@ -15,10 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
+ * @file
* @author <brion@pobox.com>
* @author <mail@tgries.de>
* @author Tim Starling
- *
*/
@@ -31,9 +33,10 @@ class MailAddress {
/**
* @param $address Mixed: string with an email address, or a User object
* @param $name String: human-readable name if a string address is given
+ * @param $realName String: human-readable real name if a string address is given
*/
function __construct( $address, $name = null, $realName = null ) {
- if( is_object( $address ) && $address instanceof User ) {
+ if ( is_object( $address ) && $address instanceof User ) {
$this->address = $address->getEmail();
$this->name = $address->getName();
$this->realName = $address->getRealName();
@@ -52,11 +55,11 @@ class MailAddress {
# PHP's mail() implementation under Windows is somewhat shite, and
# can't handle "Joe Bloggs <joe@bloggs.com>" format email addresses,
# so don't bother generating them
- if( $this->name != '' && !wfIsWindows() ) {
+ if ( $this->name != '' && !wfIsWindows() ) {
global $wgEnotifUseRealName;
$name = ( $wgEnotifUseRealName && $this->realName ) ? $this->realName : $this->name;
$quoted = wfQuotedPrintable( $name );
- if( strpos( $quoted, '.' ) !== false || strpos( $quoted, ',' ) !== false ) {
+ if ( strpos( $quoted, '.' ) !== false || strpos( $quoted, ',' ) !== false ) {
$quoted = '"' . $quoted . '"';
}
return "$quoted <{$this->address}>";
@@ -75,19 +78,20 @@ class MailAddress {
* Collection of static functions for sending mail
*/
class UserMailer {
+ static $mErrorString;
+
/**
* Send mail using a PEAR mailer
*/
- protected static function sendWithPear($mailer, $dest, $headers, $body)
- {
- $mailResult = $mailer->send($dest, $headers, $body);
+ protected static function sendWithPear( $mailer, $dest, $headers, $body ) {
+ $mailResult = $mailer->send( $dest, $headers, $body );
# Based on the result return an error string,
- if( PEAR::isError( $mailResult ) ) {
+ if ( PEAR::isError( $mailResult ) ) {
wfDebug( "PEAR::Mail failed: " . $mailResult->getMessage() . "\n" );
- return new WikiError( $mailResult->getMessage() );
+ return Status::newFatal( 'pear-mail-error', $mailResult->getMessage() );
} else {
- return true;
+ return Status::newGood();
}
}
@@ -97,41 +101,58 @@ class UserMailer {
* array of parameters. It requires PEAR:Mail to do that.
* Otherwise it just uses the standard PHP 'mail' function.
*
- * @param $to MailAddress: recipient's email
+ * @param $to MailAddress: recipient's email (or an array of them)
* @param $from MailAddress: sender's email
* @param $subject String: email's subject.
* @param $body String: email's text.
* @param $replyto MailAddress: optional reply-to email (default: null).
* @param $contentType String: optional custom Content-Type
- * @return mixed True on success, a WikiError object on failure.
+ * @return Status object
*/
- static function send( $to, $from, $subject, $body, $replyto=null, $contentType=null ) {
- global $wgSMTP, $wgOutputEncoding, $wgErrorString, $wgEnotifImpersonal;
- global $wgEnotifMaxRecips;
+ public static function send( $to, $from, $subject, $body, $replyto = null, $contentType = null ) {
+ global $wgSMTP, $wgOutputEncoding, $wgEnotifImpersonal;
+ global $wgEnotifMaxRecips, $wgAdditionalMailParams;
if ( is_array( $to ) ) {
- wfDebug( __METHOD__.': sending mail to ' . implode( ',', $to ) . "\n" );
+ // This wouldn't be necessary if implode() worked on arrays of
+ // objects using __toString(). http://bugs.php.net/bug.php?id=36612
+ foreach ( $to as $t ) {
+ $emails .= $t->toString() . ",";
+ }
+ $emails = rtrim( $emails, ',' );
+ wfDebug( __METHOD__ . ': sending mail to ' . $emails . "\n" );
} else {
- wfDebug( __METHOD__.': sending mail to ' . implode( ',', array( $to->toString() ) ) . "\n" );
+ wfDebug( __METHOD__ . ': sending mail to ' . implode( ',', array( $to->toString() ) ) . "\n" );
}
- if (is_array( $wgSMTP )) {
+ if ( is_array( $wgSMTP ) ) {
+ $found = false;
+ $pathArray = explode( PATH_SEPARATOR, get_include_path() );
+ foreach ( $pathArray as $path ) {
+ if ( file_exists( $path . DIRECTORY_SEPARATOR . 'Mail.php' ) ) {
+ $found = true;
+ break;
+ }
+ }
+ if ( !$found ) {
+ throw new MWException( 'PEAR mail package is not installed' );
+ }
require_once( 'Mail.php' );
- $msgid = str_replace(" ", "_", microtime());
- if (function_exists('posix_getpid'))
+ $msgid = str_replace( " ", "_", microtime() );
+ if ( function_exists( 'posix_getpid' ) )
$msgid .= '.' . posix_getpid();
- if (is_array($to)) {
+ if ( is_array( $to ) ) {
$dest = array();
- foreach ($to as $u)
+ foreach ( $to as $u )
$dest[] = $u->address;
} else
$dest = $to->address;
$headers['From'] = $from->toString();
- if ($wgEnotifImpersonal) {
+ if ( $wgEnotifImpersonal ) {
$headers['To'] = 'undisclosed-recipients:;';
}
else {
@@ -144,26 +165,33 @@ class UserMailer {
$headers['Subject'] = wfQuotedPrintable( $subject );
$headers['Date'] = date( 'r' );
$headers['MIME-Version'] = '1.0';
- $headers['Content-type'] = (is_null($contentType) ?
- 'text/plain; charset='.$wgOutputEncoding : $contentType);
+ $headers['Content-type'] = ( is_null( $contentType ) ?
+ 'text/plain; charset=' . $wgOutputEncoding : $contentType );
$headers['Content-transfer-encoding'] = '8bit';
$headers['Message-ID'] = "<$msgid@" . $wgSMTP['IDHost'] . '>'; // FIXME
$headers['X-Mailer'] = 'MediaWiki mailer';
+ wfSuppressWarnings();
+
// Create the mail object using the Mail::factory method
- $mail_object =& Mail::factory('smtp', $wgSMTP);
- if( PEAR::isError( $mail_object ) ) {
+ $mail_object =& Mail::factory( 'smtp', $wgSMTP );
+ if ( PEAR::isError( $mail_object ) ) {
wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" );
- return new WikiError( $mail_object->getMessage() );
+ wfRestoreWarnings();
+ return Status::newFatal( 'pear-mail-error', $mail_object->getMessage() );
}
wfDebug( "Sending mail via PEAR::Mail to $dest\n" );
$chunks = array_chunk( (array)$dest, $wgEnotifMaxRecips );
- foreach ($chunks as $chunk) {
- $e = self::sendWithPear($mail_object, $chunk, $headers, $body);
- if( WikiError::isError( $e ) )
- return $e;
+ foreach ( $chunks as $chunk ) {
+ $status = self::sendWithPear( $mail_object, $chunk, $headers, $body );
+ if ( !$status->isOK() ) {
+ wfRestoreWarnings();
+ return $status;
+ }
}
+ wfRestoreWarnings();
+ return Status::newGood();
} else {
# In the following $headers = expression we removed "Reply-To: {$from}\r\n" , because it is treated differently
# (fifth parameter of the PHP mail function, see some lines below)
@@ -176,68 +204,63 @@ class UserMailer {
} else {
$endl = "\n";
}
- $ctype = (is_null($contentType) ?
- 'text/plain; charset='.$wgOutputEncoding : $contentType);
+ $ctype = ( is_null( $contentType ) ?
+ 'text/plain; charset=' . $wgOutputEncoding : $contentType );
$headers =
"MIME-Version: 1.0$endl" .
"Content-type: $ctype$endl" .
"Content-Transfer-Encoding: 8bit$endl" .
- "X-Mailer: MediaWiki mailer$endl".
+ "X-Mailer: MediaWiki mailer$endl" .
'From: ' . $from->toString();
- if ($replyto) {
+ if ( $replyto ) {
$headers .= "{$endl}Reply-To: " . $replyto->toString();
}
wfDebug( "Sending mail via internal mail() function\n" );
-
- $wgErrorString = '';
+
+ self::$mErrorString = '';
$html_errors = ini_get( 'html_errors' );
ini_set( 'html_errors', '0' );
set_error_handler( array( 'UserMailer', 'errorHandler' ) );
- if (function_exists('mail')) {
- if (is_array($to)) {
- foreach ($to as $recip) {
- $sent = mail( $recip->toString(), wfQuotedPrintable( $subject ), $body, $headers );
- }
- } else {
- $sent = mail( $to->toString(), wfQuotedPrintable( $subject ), $body, $headers );
+ if ( is_array( $to ) ) {
+ foreach ( $to as $recip ) {
+ $sent = mail( $recip->toString(), wfQuotedPrintable( $subject ), $body, $headers, $wgAdditionalMailParams );
}
} else {
- $wgErrorString = 'PHP is not configured to send mail';
+ $sent = mail( $to->toString(), wfQuotedPrintable( $subject ), $body, $headers, $wgAdditionalMailParams );
}
restore_error_handler();
ini_set( 'html_errors', $html_errors );
- if ( $wgErrorString ) {
- wfDebug( "Error sending mail: $wgErrorString\n" );
- return new WikiError( $wgErrorString );
- } elseif (! $sent) {
- //mail function only tells if there's an error
+ if ( self::$mErrorString ) {
+ wfDebug( "Error sending mail: " . self::$mErrorString . "\n" );
+ return Status::newFatal( 'php-mail-error', self::$mErrorString );
+ } elseif ( ! $sent ) {
+ // mail function only tells if there's an error
wfDebug( "Error sending mail\n" );
- return new WikiError( 'mailer error' );
+ return Status::newFatal( 'php-mail-error-unknown' );
} else {
- return true;
+ return Status::newGood();
}
}
}
/**
- * Get the mail error message in global $wgErrorString
+ * Set the mail error message in self::$mErrorString
*
* @param $code Integer: error number
* @param $string String: error message
*/
static function errorHandler( $code, $string ) {
- global $wgErrorString;
- $wgErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string );
+ self::$mErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string );
}
/**
* Converts a string into a valid RFC 822 "phrase", such as is used for the sender name
*/
- static function rfc822Phrase( $phrase ) {
+ public static function rfc822Phrase( $phrase ) {
$phrase = strtr( $phrase, array( "\r" => '', "\n" => '', '"' => '' ) );
return '"' . $phrase . '"';
}
@@ -281,15 +304,15 @@ class EmailNotification {
* @param $minorEdit
* @param $oldid (default: false)
*/
- function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) {
+ public function notifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid = false ) {
global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker;
- if ($title->getNamespace() < 0)
+ if ( $title->getNamespace() < 0 )
return;
// Build a list of users to notfiy
$watchers = array();
- if ($wgEnotifWatchlist || $wgShowUpdatedMarker) {
+ if ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) {
$dbw = wfGetDB( DB_MASTER );
$res = $dbw->select( array( 'watchlist' ),
array( 'wl_user' ),
@@ -300,10 +323,10 @@ class EmailNotification {
'wl_notificationtimestamp IS NULL',
), __METHOD__
);
- while ($row = $dbw->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$watchers[] = intval( $row->wl_user );
}
- if ($watchers) {
+ if ( $watchers ) {
// Update wl_notificationtimestamp for all watching users except
// the editor
$dbw->begin();
@@ -320,7 +343,7 @@ class EmailNotification {
}
}
- if ($wgEnotifUseJobQ) {
+ if ( $wgEnotifUseJobQ ) {
$params = array(
"editor" => $editor->getName(),
"editorID" => $editor->getID(),
@@ -328,7 +351,7 @@ class EmailNotification {
"summary" => $summary,
"minorEdit" => $minorEdit,
"oldid" => $oldid,
- "watchers" => $watchers);
+ "watchers" => $watchers );
$job = new EnotifNotifyJob( $title, $params );
$job->insert();
} else {
@@ -351,11 +374,10 @@ class EmailNotification {
* @param $oldid int Revision ID
* @param $watchers array of user IDs
*/
- function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers) {
+ public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers ) {
# we use $wgPasswordSender as sender's address
global $wgEnotifWatchlist;
global $wgEnotifMinorEdits, $wgEnotifUserTalk;
- global $wgEnotifImpersonal;
wfProfileIn( __METHOD__ );
@@ -363,9 +385,7 @@ class EmailNotification {
# 1. EmailNotification for pages (other than user_talk pages) must be enabled
# 2. minor edits (changes) are only regarded if the global flag indicates so
- $isUserTalkPage = ($title->getNamespace() == NS_USER_TALK);
- $enotifusertalkpage = ($isUserTalkPage && $wgEnotifUserTalk);
- $enotifwatchlistpage = $wgEnotifWatchlist;
+ $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK );
$this->title = $title;
$this->timestamp = $timestamp;
@@ -377,23 +397,23 @@ class EmailNotification {
$userTalkId = false;
- if ( !$minorEdit || ($wgEnotifMinorEdits && !$editor->isAllowed('nominornewtalk') ) ) {
+ if ( !$minorEdit || ( $wgEnotifMinorEdits && !$editor->isAllowed( 'nominornewtalk' ) ) ) {
if ( $wgEnotifUserTalk && $isUserTalkPage ) {
$targetUser = User::newFromName( $title->getText() );
if ( !$targetUser || $targetUser->isAnon() ) {
- wfDebug( __METHOD__.": user talk page edited, but user does not exist\n" );
+ wfDebug( __METHOD__ . ": user talk page edited, but user does not exist\n" );
} elseif ( $targetUser->getId() == $editor->getId() ) {
- wfDebug( __METHOD__.": user edited their own talk page, no notification sent\n" );
- } elseif( $targetUser->getOption( 'enotifusertalkpages' ) ) {
- if( $targetUser->isEmailConfirmed() ) {
- wfDebug( __METHOD__.": sending talk page update notification\n" );
+ wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent\n" );
+ } elseif ( $targetUser->getOption( 'enotifusertalkpages' ) ) {
+ if ( $targetUser->isEmailConfirmed() ) {
+ wfDebug( __METHOD__ . ": sending talk page update notification\n" );
$this->compose( $targetUser );
$userTalkId = $targetUser->getId();
} else {
- wfDebug( __METHOD__.": talk page owner doesn't have validated email\n" );
+ wfDebug( __METHOD__ . ": talk page owner doesn't have validated email\n" );
}
} else {
- wfDebug( __METHOD__.": talk page owner doesn't want notifications\n" );
+ wfDebug( __METHOD__ . ": talk page owner doesn't want notifications\n" );
}
}
@@ -402,7 +422,7 @@ class EmailNotification {
$userArray = UserArray::newFromIDs( $watchers );
foreach ( $userArray as $watchingUser ) {
if ( $watchingUser->getOption( 'enotifwatchlistpages' ) &&
- ( !$minorEdit || $watchingUser->getOption('enotifminoredits') ) &&
+ ( !$minorEdit || $watchingUser->getOption( 'enotifminoredits' ) ) &&
$watchingUser->isEmailConfirmed() &&
$watchingUser->getID() != $userTalkId )
{
@@ -423,17 +443,17 @@ class EmailNotification {
}
/**
- * @private
+ * Generate the generic "this page has been changed" e-mail text.
*/
- function composeCommonMailtext() {
- global $wgPasswordSender, $wgNoReplyAddress;
+ private function composeCommonMailtext() {
+ global $wgPasswordSender, $wgPasswordSenderName, $wgNoReplyAddress;
global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
global $wgEnotifImpersonal, $wgEnotifUseRealName;
$this->composed_common = true;
- $summary = ($this->summary == '') ? ' - ' : $this->summary;
- $medit = ($this->minorEdit) ? wfMsg( 'minoredit' ) : '';
+ $summary = ( $this->summary == '' ) ? ' - ' : $this->summary;
+ $medit = ( $this->minorEdit ) ? wfMsgForContent( 'minoredit' ) : '';
# You as the WikiAdmin and Sysops can make use of plenty of
# named variables when composing your notification emails while
@@ -445,7 +465,7 @@ class EmailNotification {
$replyto = ''; /* fail safe */
$keys = array();
- if( $this->oldid ) {
+ if ( $this->oldid ) {
$difflink = $this->title->getFullUrl( 'diff=0&oldid=' . $this->oldid );
$keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastvisited', $difflink );
$keys['$OLDID'] = $this->oldid;
@@ -457,13 +477,14 @@ class EmailNotification {
$keys['$CHANGEDORCREATED'] = wfMsgForContent( 'created' );
}
- if ($wgEnotifImpersonal && $this->oldid)
+ if ( $wgEnotifImpersonal && $this->oldid ) {
/*
* For impersonal mail, show a diff link to the last
* revision.
*/
- $keys['$NEWPAGE'] = wfMsgForContent('enotif_lastdiff',
- $this->title->getFullURL("oldid={$this->oldid}&diff=prev"));
+ $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastdiff',
+ $this->title->getFullURL( "oldid={$this->oldid}&diff=next" ) );
+ }
$body = strtr( $body, $keys );
$pagetitle = $this->title->getPrefixedText();
@@ -481,12 +502,12 @@ class EmailNotification {
# global configuration level.
$editor = $this->editor;
$name = $wgEnotifUseRealName ? $editor->getRealName() : $editor->getName();
- $adminAddress = new MailAddress( $wgPasswordSender, 'WikiAdmin' );
+ $adminAddress = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
$editorAddress = new MailAddress( $editor );
- if( $wgEnotifRevealEditorAddress
+ if ( $wgEnotifRevealEditorAddress
&& ( $editor->getEmail() != '' )
&& $editor->getOption( 'enotifrevealaddr' ) ) {
- if( $wgEnotifFromEditor ) {
+ if ( $wgEnotifFromEditor ) {
$from = $editorAddress;
} else {
$from = $adminAddress;
@@ -497,14 +518,14 @@ class EmailNotification {
$replyto = new MailAddress( $wgNoReplyAddress );
}
- if( $editor->isIP( $name ) ) {
- #real anon (user:xxx.xxx.xxx.xxx)
- $utext = wfMsgForContent('enotif_anon_editor', $name);
- $subject = str_replace('$PAGEEDITOR', $utext, $subject);
+ if ( $editor->isIP( $name ) ) {
+ # real anon (user:xxx.xxx.xxx.xxx)
+ $utext = wfMsgForContent( 'enotif_anon_editor', $name );
+ $subject = str_replace( '$PAGEEDITOR', $utext, $subject );
$keys['$PAGEEDITOR'] = $utext;
$keys['$PAGEEDITOR_EMAIL'] = wfMsgForContent( 'noemailtitle' );
} else {
- $subject = str_replace('$PAGEEDITOR', $name, $subject);
+ $subject = str_replace( '$PAGEEDITOR', $name, $subject );
$keys['$PAGEEDITOR'] = $name;
$emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $name );
$keys['$PAGEEDITOR_EMAIL'] = $emailPage->getFullUrl();
@@ -555,9 +576,8 @@ class EmailNotification {
* timestamp in proper timezone, etc) and sends it out.
* Returns true if the mail was sent successfully.
*
- * @param User $watchingUser
- * @param object $mail
- * @return bool
+ * @param $watchingUser User object
+ * @return Boolean
* @private
*/
function sendPersonalised( $watchingUser ) {
@@ -567,7 +587,7 @@ class EmailNotification {
// The mail command will not parse this properly while talking with the MTA.
$to = new MailAddress( $watchingUser );
$name = $wgEnotifUseRealName ? $watchingUser->getRealName() : $watchingUser->getName();
- $body = str_replace( '$WATCHINGUSERNAME', $name , $this->body );
+ $body = str_replace( '$WATCHINGUSERNAME', $name, $this->body );
$timecorrection = $watchingUser->getOption( 'timecorrection' );
@@ -575,15 +595,15 @@ class EmailNotification {
# expressed in terms of individual local time of the notification
# recipient, i.e. watching user
$body = str_replace(
- array( '$PAGEEDITDATEANDTIME',
+ array( '$PAGEEDITDATEANDTIME',
'$PAGEEDITDATE',
'$PAGEEDITTIME' ),
- array( $wgContLang->timeanddate( $this->timestamp, true, false, $timecorrection ),
+ array( $wgContLang->timeanddate( $this->timestamp, true, false, $timecorrection ),
$wgContLang->date( $this->timestamp, true, false, $timecorrection ),
$wgContLang->time( $this->timestamp, true, false, $timecorrection ) ),
- $body);
+ $body );
- return UserMailer::send($to, $this->from, $this->subject, $body, $this->replyto);
+ return UserMailer::send( $to, $this->from, $this->subject, $body, $this->replyto );
}
/**
@@ -593,28 +613,33 @@ class EmailNotification {
function sendImpersonal( $addresses ) {
global $wgContLang;
- if (empty($addresses))
+ if ( empty( $addresses ) )
return;
$body = str_replace(
- array( '$WATCHINGUSERNAME',
- '$PAGEEDITDATE'),
- array( wfMsgForContent('enotif_impersonal_salutation'),
- $wgContLang->timeanddate($this->timestamp, true, false, false)),
- $this->body);
+ array( '$WATCHINGUSERNAME',
+ '$PAGEEDITDATE' ),
+ array( wfMsgForContent( 'enotif_impersonal_salutation' ),
+ $wgContLang->timeanddate( $this->timestamp, true, false, false ) ),
+ $this->body );
- return UserMailer::send($addresses, $this->from, $this->subject, $body, $this->replyto);
+ return UserMailer::send( $addresses, $this->from, $this->subject, $body, $this->replyto );
}
} # end of class EmailNotification
-/**
+/**@{
* Backwards compatibility functions
+ *
+ * @deprecated Use UserMailer methods; will be removed in 1.19
*/
function wfRFC822Phrase( $s ) {
+ wfDeprecated( __FUNCTION__ );
return UserMailer::rfc822Phrase( $s );
}
-function userMailer( $to, $from, $subject, $body, $replyto=null ) {
+function userMailer( $to, $from, $subject, $body, $replyto = null ) {
+ wfDeprecated( __FUNCTION__ );
return UserMailer::send( $to, $from, $subject, $body, $replyto );
}
+/**@}*/ \ No newline at end of file
diff --git a/includes/UserRightsProxy.php b/includes/UserRightsProxy.php
index 0d6b8151..6c2a5f12 100644
--- a/includes/UserRightsProxy.php
+++ b/includes/UserRightsProxy.php
@@ -21,6 +21,7 @@ class UserRightsProxy {
$this->database = $database;
$this->name = $name;
$this->id = intval( $id );
+ $this->newOptions = array();
}
/**
@@ -48,10 +49,11 @@ class UserRightsProxy {
*
* @param $database String: database name
* @param $id Integer: user ID
+ * @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
* @return String: user name or false if the user doesn't exist
*/
- public static function whoIs( $database, $id ) {
- $user = self::newFromId( $database, $id );
+ public static function whoIs( $database, $id, $ignoreInvalidDB = false ) {
+ $user = self::newFromId( $database, $id, $ignoreInvalidDB );
if( $user ) {
return $user->name;
} else {
@@ -64,10 +66,11 @@ class UserRightsProxy {
*
* @param $database String: database name
* @param $id Integer: user ID
+ * @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
* @return UserRightsProxy or null if doesn't exist
*/
- public static function newFromId( $database, $id ) {
- return self::newFromLookup( $database, 'user_id', intval( $id ) );
+ public static function newFromId( $database, $id, $ignoreInvalidDB = false ) {
+ return self::newFromLookup( $database, 'user_id', intval( $id ), $ignoreInvalidDB );
}
/**
@@ -75,14 +78,15 @@ class UserRightsProxy {
*
* @param $database String: database name
* @param $name String: user name
+ * @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
* @return UserRightsProxy or null if doesn't exist
*/
- public static function newFromName( $database, $name ) {
- return self::newFromLookup( $database, 'user_name', $name );
+ public static function newFromName( $database, $name, $ignoreInvalidDB = false ) {
+ return self::newFromLookup( $database, 'user_name', $name, $ignoreInvalidDB );
}
- private static function newFromLookup( $database, $field, $value ) {
- $db = self::getDB( $database );
+ private static function newFromLookup( $database, $field, $value, $ignoreInvalidDB = false ) {
+ $db = self::getDB( $database, $ignoreInvalidDB );
if( $db ) {
$row = $db->selectRow( 'user',
array( 'user_id', 'user_name' ),
@@ -102,10 +106,11 @@ class UserRightsProxy {
* This may be a new connection to another database for remote users.
*
* @param $database String
+ * @param $ignoreInvalidDB Boolean: if true, don't check if $database is in $wgLocalDatabases
* @return DatabaseBase or null if invalid selection
*/
- public static function getDB( $database ) {
- global $wgLocalDatabases, $wgDBname;
+ public static function getDB( $database, $ignoreInvalidDB = false ) {
+ global $wgDBname;
if( self::validDatabase( $database ) ) {
if( $database == $wgDBname ) {
// Hmm... this shouldn't happen though. :)
@@ -152,7 +157,7 @@ class UserRightsProxy {
array( 'ug_user' => $this->id ),
__METHOD__ );
$groups = array();
- while( $row = $this->db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$groups[] = $row->ug_group;
}
return $groups;
@@ -182,6 +187,29 @@ class UserRightsProxy {
),
__METHOD__ );
}
+
+ /**
+ * Replaces User::setOption()
+ */
+ public function setOption( $option, $value ) {
+ $this->newOptions[$option] = $value;
+ }
+
+ public function saveSettings() {
+ $rows = array();
+ foreach ( $this->newOptions as $option => $value ) {
+ $rows[] = array(
+ 'up_user' => $this->id,
+ 'up_property' => $option,
+ 'up_value' => $value,
+ );
+ }
+ $this->db->replace( 'user_properties',
+ array( array( 'up_user', 'up_property' ) ),
+ $rows, __METHOD__
+ );
+ $this->invalidateCache();
+ }
/**
* Replaces User::touchUser()
@@ -193,11 +221,7 @@ class UserRightsProxy {
__METHOD__ );
global $wgMemc;
- if ( function_exists( 'wfForeignMemcKey' ) ) {
- $key = wfForeignMemcKey( $this->database, false, 'user', 'id', $this->id );
- } else {
- $key = "$this->database:user:id:" . $this->id;
- }
+ $key = wfForeignMemcKey( $this->database, false, 'user', 'id', $this->id );
$wgMemc->delete( $key );
}
}
diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php
index d1c15296..dd6e247b 100644
--- a/includes/WatchedItem.php
+++ b/includes/WatchedItem.php
@@ -84,6 +84,8 @@ class WatchedItem {
* @return bool
*/
public function removeWatch() {
+ wfProfileIn( __METHOD__ );
+
$success = false;
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'watchlist',
@@ -112,6 +114,8 @@ class WatchedItem {
if ( $dbw->affectedRows() ) {
$success = true;
}
+
+ wfProfileOut( __METHOD__ );
return $success;
}
@@ -143,14 +147,13 @@ class WatchedItem {
);
# Construct array to replace into the watchlist
$values = array();
- while ( $s = $dbw->fetchObject( $res ) ) {
+ foreach ( $res as $s ) {
$values[] = array(
'wl_user' => $s->wl_user,
'wl_namespace' => $newnamespace,
'wl_title' => $newtitle
);
}
- $dbw->freeResult( $res );
if( empty( $values ) ) {
// Nothing to do
diff --git a/includes/WatchlistEditor.php b/includes/WatchlistEditor.php
index e9e79ee1..37673784 100644
--- a/includes/WatchlistEditor.php
+++ b/includes/WatchlistEditor.php
@@ -100,15 +100,17 @@ class WatchlistEditor {
$titles = array();
if( !is_array( $list ) ) {
$list = explode( "\n", trim( $list ) );
- if( !is_array( $list ) )
+ if( !is_array( $list ) ) {
return array();
+ }
}
foreach( $list as $text ) {
$text = trim( $text );
if( strlen( $text ) > 0 ) {
$title = Title::newFromText( $text );
- if( $title instanceof Title && $title->isWatchable() )
+ if( $title instanceof Title && $title->isWatchable() ) {
$titles[] = $title->getPrefixedText();
+ }
}
}
return array_unique( $titles );
@@ -129,8 +131,9 @@ class WatchlistEditor {
// Do a batch existence check
$batch = new LinkBatch();
foreach( $titles as $title ) {
- if( !$title instanceof Title )
+ if( !$title instanceof Title ) {
$title = Title::newFromText( $title );
+ }
if( $title instanceof Title ) {
$batch->addObj( $title );
$batch->addObj( $title->getTalkPage() );
@@ -140,8 +143,9 @@ class WatchlistEditor {
// Print out the list
$output->addHTML( "<ul>\n" );
foreach( $titles as $title ) {
- if( !$title instanceof Title )
+ if( !$title instanceof Title ) {
$title = Title::newFromText( $title );
+ }
if( $title instanceof Title ) {
$output->addHTML( "<li>" . $skin->link( $title )
. ' (' . $skin->link( $title->getTalkPage(), $talk ) . ")</li>\n" );
@@ -182,7 +186,7 @@ class WatchlistEditor {
__METHOD__
);
if( $res->numRows() > 0 ) {
- while( $row = $res->fetchObject() ) {
+ foreach ( $res as $row ) {
$title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
if( $title instanceof Title && !$title->isTalkPage() )
$list[] = $title->getPrefixedText();
@@ -205,24 +209,28 @@ class WatchlistEditor {
$dbr = wfGetDB( DB_MASTER );
$uid = intval( $user->getId() );
list( $watchlist, $page ) = $dbr->tableNamesN( 'watchlist', 'page' );
- $sql = "SELECT wl_namespace, wl_title, page_id, page_len, page_is_redirect
+ $sql = "SELECT wl_namespace, wl_title, page_id, page_len, page_is_redirect, page_latest
FROM {$watchlist} LEFT JOIN {$page} ON ( wl_namespace = page_namespace
AND wl_title = page_title ) WHERE wl_user = {$uid}";
+ if ( ! $dbr->implicitOrderby() ) {
+ $sql .= " ORDER BY wl_namespace, wl_title";
+ }
$res = $dbr->query( $sql, __METHOD__ );
if( $res && $dbr->numRows( $res ) > 0 ) {
$cache = LinkCache::singleton();
- while( $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
if( $title instanceof Title ) {
// Update the link cache while we're at it
if( $row->page_id ) {
- $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect );
+ $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest );
} else {
$cache->addBadLinkObj( $title );
}
// Ignore non-talk
- if( !$title->isTalkPage() )
+ if( !$title->isTalkPage() ) {
$titles[$row->wl_namespace][$row->wl_title] = $row->page_is_redirect;
+ }
}
}
}
@@ -270,8 +278,9 @@ class WatchlistEditor {
$dbw = wfGetDB( DB_MASTER );
$rows = array();
foreach( $titles as $title ) {
- if( !$title instanceof Title )
+ if( !$title instanceof Title ) {
$title = Title::newFromText( $title );
+ }
if( $title instanceof Title ) {
$rows[] = array(
'wl_user' => $user->getId(),
@@ -302,8 +311,9 @@ class WatchlistEditor {
private function unwatchTitles( $titles, $user ) {
$dbw = wfGetDB( DB_MASTER );
foreach( $titles as $title ) {
- if( !$title instanceof Title )
+ if( !$title instanceof Title ) {
$title = Title::newFromText( $title );
+ }
if( $title instanceof Title ) {
$dbw->delete(
'watchlist',
@@ -337,11 +347,12 @@ class WatchlistEditor {
*/
private function showNormalForm( $output, $user ) {
global $wgUser;
- if( ( $count = $this->showItemCount( $output, $user ) ) > 0 ) {
+ $count = $this->showItemCount( $output, $user );
+ if( $count > 0 ) {
$self = SpecialPage::getTitleFor( 'Watchlist' );
$form = Xml::openElement( 'form', array( 'method' => 'post',
'action' => $self->getLocalUrl( array( 'action' => 'edit' ) ) ) );
- $form .= Xml::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
+ $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
$form .= "<fieldset>\n<legend>" . wfMsgHtml( 'watchlistedit-normal-legend' ) . "</legend>";
$form .= wfMsgExt( 'watchlistedit-normal-explain', 'parse' );
$form .= $this->buildRemoveList( $user, $wgUser->getSkin() );
@@ -410,8 +421,9 @@ class WatchlistEditor {
global $wgLang;
$link = $skin->link( $title );
- if( $redirect )
+ if( $redirect ) {
$link = '<span class="watchlistredir">' . $link . '</span>';
+ }
$tools[] = $skin->link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) );
if( $title->exists() ) {
$tools[] = $skin->link(
@@ -431,10 +443,13 @@ class WatchlistEditor {
array( 'known', 'noclasses' )
);
}
+
+ wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $redirect, $skin ) );
+
return "<li>"
. Xml::check( 'titles[]', false, array( 'value' => $title->getPrefixedText() ) )
. $link . " (" . $wgLang->pipeList( $tools ) . ")" . "</li>\n";
- }
+ }
/**
* Show a form for editing the watchlist in "raw" mode
@@ -446,9 +461,9 @@ class WatchlistEditor {
global $wgUser;
$this->showItemCount( $output, $user );
$self = SpecialPage::getTitleFor( 'Watchlist' );
- $form = Xml::openElement( 'form', array( 'method' => 'post',
+ $form = Xml::openElement( 'form', array( 'method' => 'post',
'action' => $self->getLocalUrl( array( 'action' => 'raw' ) ) ) );
- $form .= Xml::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
+ $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
$form .= '<fieldset><legend>' . wfMsgHtml( 'watchlistedit-raw-legend' ) . '</legend>';
$form .= wfMsgExt( 'watchlistedit-raw-explain', 'parse' );
$form .= Xml::label( wfMsg( 'watchlistedit-raw-titles' ), 'titles' );
@@ -456,8 +471,9 @@ class WatchlistEditor {
$form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles',
'rows' => $wgUser->getIntOption( 'rows' ), 'cols' => $wgUser->getIntOption( 'cols' ) ) );
$titles = $this->getWatchlist( $user );
- foreach( $titles as $title )
+ foreach( $titles as $title ) {
$form .= htmlspecialchars( $title ) . "\n";
+ }
$form .= '</textarea>';
$form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-raw-submit' ) ) . '</p>';
$form .= '</fieldset></form>';
@@ -500,14 +516,13 @@ class WatchlistEditor {
$modes = array( 'view' => false, 'edit' => 'edit', 'raw' => 'raw' );
foreach( $modes as $mode => $subpage ) {
// can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
- $tools[] = $skin->link(
+ $tools[] = $skin->linkKnown(
SpecialPage::getTitleFor( 'Watchlist', $subpage ),
- wfMsgHtml( "watchlisttools-{$mode}" ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ wfMsgHtml( "watchlisttools-{$mode}" )
);
}
- return $wgLang->pipeList( $tools );
+ return Html::rawElement( 'span',
+ array( 'class' => 'mw-watchlist-toollinks' ),
+ wfMsg( 'parentheses', $wgLang->pipeList( $tools ) ) );
}
}
diff --git a/includes/WebRequest.php b/includes/WebRequest.php
index 877f7cf6..940b693f 100644
--- a/includes/WebRequest.php
+++ b/includes/WebRequest.php
@@ -1,35 +1,28 @@
<?php
/**
* Deal with importing all those nasssty globals and things
+ *
+ * Copyright © 2003 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
-# Copyright (C) 2003 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
-
-/**
- * Some entry points may use this file without first enabling the
- * autoloader.
- */
-if ( !function_exists( '__autoload' ) ) {
- require_once( dirname(__FILE__) . '/normal/UtfNormal.php' );
-}
-
/**
* The WebRequest class encapsulates getting at data passed in the
* URL or via a POSTed form, handling remove of "magic quotes" slashes,
@@ -44,7 +37,12 @@ if ( !function_exists( '__autoload' ) ) {
*/
class WebRequest {
protected $data, $headers = array();
- private $_response;
+
+ /**
+ * Lazy-init response object
+ * @var WebResponse
+ */
+ private $response;
public function __construct() {
/// @todo Fixme: this preemptive de-quoting can interfere with other web libraries
@@ -67,6 +65,11 @@ class WebRequest {
public function interpolateTitle() {
global $wgUsePathInfo;
+ // bug 16019: title interpolation on API queries is useless and possible harmful
+ if ( defined( 'MW_API' ) ) {
+ return;
+ }
+
if ( $wgUsePathInfo ) {
// PATH_INFO is mangled due to http://bugs.php.net/bug.php?id=31892
// And also by Apache 2.x, double slashes are converted to single slashes.
@@ -160,6 +163,7 @@ class WebRequest {
/**
* Recursively strips slashes from the given array;
* used for undoing the evil that is magic_quotes_gpc.
+ *
* @param $arr array: will be modified
* @return array the original array
*/
@@ -195,6 +199,7 @@ class WebRequest {
/**
* Recursively normalizes UTF-8 strings in the given array.
+ *
* @param $data string or array
* @return cleaned-up version of the given
* @private
@@ -214,9 +219,9 @@ class WebRequest {
/**
* Fetch a value from the given array or return $default if it's not set.
*
- * @param $arr array
- * @param $name string
- * @param $default mixed
+ * @param $arr Array
+ * @param $name String
+ * @param $default Mixed
* @return mixed
*/
private function getGPCVal( $arr, $name, $default ) {
@@ -247,9 +252,9 @@ class WebRequest {
* non-freeform text inputs (e.g. predefined internal text keys
* selected by a drop-down menu). For freeform input, see getText().
*
- * @param $name string
- * @param $default string: optional default (or NULL)
- * @return string
+ * @param $name String
+ * @param $default String: optional default (or NULL)
+ * @return String
*/
public function getVal( $name, $default = null ) {
$val = $this->getGPCVal( $this->data, $name, $default );
@@ -265,9 +270,10 @@ class WebRequest {
/**
* Set an aribtrary value into our get/post data.
- * @param $key string Key name to use
- * @param $value mixed Value to set
- * @return mixed old value if one was present, null otherwise
+ *
+ * @param $key String: key name to use
+ * @param $value Mixed: value to set
+ * @return Mixed: old value if one was present, null otherwise
*/
public function setVal( $key, $value ) {
$ret = isset( $this->data[$key] ) ? $this->data[$key] : null;
@@ -280,9 +286,9 @@ class WebRequest {
* If source was scalar, will return an array with a single element.
* If no source and no default, returns NULL.
*
- * @param $name string
- * @param $default array: optional default (or NULL)
- * @return array
+ * @param $name String
+ * @param $default Array: optional default (or NULL)
+ * @return Array
*/
public function getArray( $name, $default = null ) {
$val = $this->getGPCVal( $this->data, $name, $default );
@@ -299,9 +305,9 @@ class WebRequest {
* If no source and no default, returns NULL.
* If an array is returned, contents are guaranteed to be integers.
*
- * @param $name string
- * @param $default array: option default (or NULL)
- * @return array of ints
+ * @param $name String
+ * @param $default Array: option default (or NULL)
+ * @return Array of ints
*/
public function getIntArray( $name, $default = null ) {
$val = $this->getArray( $name, $default );
@@ -315,9 +321,10 @@ class WebRequest {
* Fetch an integer value from the input or return $default if not set.
* Guaranteed to return an integer; non-numeric input will typically
* return 0.
- * @param $name string
- * @param $default int
- * @return int
+ *
+ * @param $name String
+ * @param $default Integer
+ * @return Integer
*/
public function getInt( $name, $default = 0 ) {
return intval( $this->getVal( $name, $default ) );
@@ -327,8 +334,9 @@ class WebRequest {
* Fetch an integer value from the input or return null if empty.
* Guaranteed to return an integer or null; non-numeric input will
* typically return null.
- * @param $name string
- * @return int
+ *
+ * @param $name String
+ * @return Integer
*/
public function getIntOrNull( $name ) {
$val = $this->getVal( $name );
@@ -341,20 +349,35 @@ class WebRequest {
* Fetch a boolean value from the input or return $default if not set.
* Guaranteed to return true or false, with normal PHP semantics for
* boolean interpretation of strings.
- * @param $name string
- * @param $default bool
- * @return bool
+ *
+ * @param $name String
+ * @param $default Boolean
+ * @return Boolean
*/
public function getBool( $name, $default = false ) {
- return $this->getVal( $name, $default ) ? true : false;
+ return (bool)$this->getVal( $name, $default );
+ }
+
+ /**
+ * Fetch a boolean value from the input or return $default if not set.
+ * Unlike getBool, the string "false" will result in boolean false, which is
+ * useful when interpreting information sent from JavaScript.
+ *
+ * @param $name String
+ * @param $default Boolean
+ * @return Boolean
+ */
+ public function getFuzzyBool( $name, $default = false ) {
+ return $this->getBool( $name, $default ) && strcasecmp( $this->getVal( $name ), 'false' ) !== 0;
}
/**
* Return true if the named value is set in the input, whatever that
* value is (even "0"). Return false if the named value is not set.
* Example use is checking for the presence of check boxes in forms.
- * @param $name string
- * @return bool
+ *
+ * @param $name String
+ * @return Boolean
*/
public function getCheck( $name ) {
# Checkboxes and buttons are only present when clicked
@@ -365,15 +388,15 @@ class WebRequest {
/**
* Fetch a text string from the given array or return $default if it's not
- * set. \r is stripped from the text, and with some language modules there
- * is an input transliteration applied. This should generally be used for
- * form <textarea> and <input> fields. Used for user-supplied freeform text
- * input (for which input transformations may be required - e.g. Esperanto
- * x-coding).
+ * set. Carriage returns are stripped from the text, and with some language
+ * modules there is an input transliteration applied. This should generally
+ * be used for form <textarea> and <input> fields. Used for user-supplied
+ * freeform text input (for which input transformations may be required - e.g.
+ * Esperanto x-coding).
*
- * @param $name string
- * @param $default string: optional
- * @return string
+ * @param $name String
+ * @param $default String: optional
+ * @return String
*/
public function getText( $name, $default = '' ) {
global $wgContLang;
@@ -410,7 +433,7 @@ class WebRequest {
* Note that values retrieved by the object may come from the
* GET URL etc even on a POST request.
*
- * @return bool
+ * @return Boolean
*/
public function wasPosted() {
return $_SERVER['REQUEST_METHOD'] == 'POST';
@@ -425,15 +448,32 @@ class WebRequest {
* during the current request (in which case the cookie will
* be sent back to the client at the end of the script run).
*
- * @return bool
+ * @return Boolean
*/
public function checkSessionCookie() {
- return isset( $_COOKIE[session_name()] );
+ return isset( $_COOKIE[ session_name() ] );
+ }
+
+ /**
+ * Get a cookie from the $_COOKIE jar
+ *
+ * @param $key String: the name of the cookie
+ * @param $prefix String: a prefix to use for the cookie name, if not $wgCookiePrefix
+ * @param $default Mixed: what to return if the value isn't found
+ * @return Mixed: cookie value or $default if the cookie not set
+ */
+ public function getCookie( $key, $prefix = null, $default = null ) {
+ if( $prefix === null ) {
+ global $wgCookiePrefix;
+ $prefix = $wgCookiePrefix;
+ }
+ return $this->getGPCVal( $_COOKIE, $prefix . $key , $default );
}
/**
* Return the path portion of the request URI.
- * @return string
+ *
+ * @return String
*/
public function getRequestURL() {
if( isset( $_SERVER['REQUEST_URI']) && strlen($_SERVER['REQUEST_URI']) ) {
@@ -468,7 +508,8 @@ class WebRequest {
/**
* Return the request URI with the canonical service and hostname.
- * @return string
+ *
+ * @return String
*/
public function getFullRequestURL() {
global $wgServer;
@@ -478,7 +519,8 @@ class WebRequest {
/**
* Take an arbitrary query and rewrite the present URL to include it
* @param $query String: query string fragment; do not include initial '?'
- * @return string
+ *
+ * @return String
*/
public function appendQuery( $query ) {
global $wgTitle;
@@ -502,8 +544,9 @@ class WebRequest {
/**
* HTML-safe version of appendQuery().
+ *
* @param $query String: query string fragment; do not include initial '?'
- * @return string
+ * @return String
*/
public function escapeAppendQuery( $query ) {
return htmlspecialchars( $this->appendQuery( $query ) );
@@ -515,10 +558,11 @@ class WebRequest {
/**
* Appends or replaces value of query variables.
+ *
* @param $array Array of values to replace/add to query
* @param $onlyquery Bool: whether to only return the query string and not
* the complete URL
- * @return string
+ * @return String
*/
public function appendQueryArray( $array, $onlyquery = false ) {
global $wgTitle;
@@ -542,53 +586,59 @@ class WebRequest {
global $wgUser;
$limit = $this->getInt( 'limit', 0 );
- if( $limit < 0 ) $limit = 0;
+ if( $limit < 0 ) {
+ $limit = 0;
+ }
if( ( $limit == 0 ) && ( $optionname != '' ) ) {
$limit = (int)$wgUser->getOption( $optionname );
}
- if( $limit <= 0 ) $limit = $deflimit;
- if( $limit > 5000 ) $limit = 5000; # We have *some* limits...
+ if( $limit <= 0 ) {
+ $limit = $deflimit;
+ }
+ if( $limit > 5000 ) {
+ $limit = 5000; # We have *some* limits...
+ }
$offset = $this->getInt( 'offset', 0 );
- if( $offset < 0 ) $offset = 0;
+ if( $offset < 0 ) {
+ $offset = 0;
+ }
return array( $limit, $offset );
}
/**
* Return the path to the temporary file where PHP has stored the upload.
+ *
* @param $key String:
* @return string or NULL if no such file.
*/
public function getFileTempname( $key ) {
- if( !isset( $_FILES[$key] ) ) {
- return null;
- }
- return $_FILES[$key]['tmp_name'];
+ $file = new WebRequestUpload( $this, $key );
+ return $file->getTempName();
}
/**
* Return the size of the upload, or 0.
+ *
+ * @deprecated
* @param $key String:
* @return integer
*/
public function getFileSize( $key ) {
- if( !isset( $_FILES[$key] ) ) {
- return 0;
- }
- return $_FILES[$key]['size'];
+ $file = new WebRequestUpload( $this, $key );
+ return $file->getSize();
}
/**
* Return the upload error or 0
+ *
* @param $key String:
* @return integer
*/
public function getUploadError( $key ) {
- if( !isset( $_FILES[$key] ) || !isset( $_FILES[$key]['error'] ) ) {
- return 0/*UPLOAD_ERR_OK*/;
- }
- return $_FILES[$key]['error'];
+ $file = new WebRequestUpload( $this, $key );
+ return $file->getError();
}
/**
@@ -603,31 +653,33 @@ class WebRequest {
* @return string or NULL if no such file.
*/
public function getFileName( $key ) {
- global $wgContLang;
- if( !isset( $_FILES[$key] ) ) {
- return null;
- }
- $name = $_FILES[$key]['name'];
+ $file = new WebRequestUpload( $this, $key );
+ return $file->getName();
+ }
- # Safari sends filenames in HTML-encoded Unicode form D...
- # Horrid and evil! Let's try to make some kind of sense of it.
- $name = Sanitizer::decodeCharReferences( $name );
- $name = $wgContLang->normalize( $name );
- wfDebug( "WebRequest::getFileName() '" . $_FILES[$key]['name'] . "' normalized to '$name'\n" );
- return $name;
+ /**
+ * Return a WebRequestUpload object corresponding to the key
+ *
+ * @param @key string
+ * @return WebRequestUpload
+ */
+ public function getUpload( $key ) {
+ return new WebRequestUpload( $this, $key );
}
/**
* Return a handle to WebResponse style object, for setting cookies,
* headers and other stuff, for Request being worked on.
+ *
+ * @return WebResponse
*/
public function response() {
/* Lazy initialization of response object for this request */
- if ( !is_object( $this->_response ) ) {
+ if ( !is_object( $this->response ) ) {
$class = ( $this instanceof FauxRequest ) ? 'FauxResponse' : 'WebResponse';
- $this->_response = new $class();
+ $this->response = new $class();
}
- return $this->_response;
+ return $this->response;
}
/**
@@ -649,6 +701,9 @@ class WebRequest {
}
} else {
$name = 'HTTP_' . str_replace( '-', '_', $name );
+ if ( $name === 'HTTP_CONTENT_LENGTH' && !isset( $_SERVER[$name] ) ) {
+ $name = 'CONTENT_LENGTH';
+ }
if ( isset( $_SERVER[$name] ) ) {
return $_SERVER[$name];
} else {
@@ -657,27 +712,86 @@ class WebRequest {
}
}
- /*
+ /**
* Get data from $_SESSION
- * @param $key String Name of key in $_SESSION
- * @return mixed
+ *
+ * @param $key String: name of key in $_SESSION
+ * @return Mixed
*/
public function getSessionData( $key ) {
- if( !isset( $_SESSION[$key] ) )
+ if( !isset( $_SESSION[$key] ) ) {
return null;
+ }
return $_SESSION[$key];
}
/**
* Set session data
- * @param $key String Name of key in $_SESSION
- * @param $data mixed
+ *
+ * @param $key String: name of key in $_SESSION
+ * @param $data Mixed
*/
public function setSessionData( $key, $data ) {
$_SESSION[$key] = $data;
}
/**
+ * Check if Internet Explorer will detect an incorrect cache extension in
+ * PATH_INFO or QUERY_STRING. If the request can't be allowed, show an error
+ * message or redirect to a safer URL. Returns true if the URL is OK, and
+ * false if an error message has been shown and the request should be aborted.
+ */
+ public function checkUrlExtension( $extWhitelist = array() ) {
+ global $wgScriptExtension;
+ $extWhitelist[] = ltrim( $wgScriptExtension, '.' );
+ if ( IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist ) ) {
+ if ( !$this->wasPosted() ) {
+ $newUrl = IEUrlExtension::fixUrlForIE6(
+ $this->getFullRequestURL(), $extWhitelist );
+ if ( $newUrl !== false ) {
+ $this->doSecurityRedirect( $newUrl );
+ return false;
+ }
+ }
+ wfHttpError( 403, 'Forbidden',
+ 'Invalid file extension found in the path info or query string.' );
+
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Attempt to redirect to a URL with a QUERY_STRING that's not dangerous in
+ * IE 6. Returns true if it was successful, false otherwise.
+ */
+ protected function doSecurityRedirect( $url ) {
+ header( 'Location: ' . $url );
+ header( 'Content-Type: text/html' );
+ $encUrl = htmlspecialchars( $url );
+ echo <<<HTML
+<html>
+<head>
+<title>Security redirect</title>
+</head>
+<body>
+<h1>Security redirect</h1>
+<p>
+We can't serve non-HTML content from the URL you have requested, because
+Internet Explorer would interpret it as an incorrect and potentially dangerous
+content type.</p>
+<p>Instead, please use <a href="$encUrl">this URL</a>, which is the same as the URL you have requested, except that
+"&amp;*" is appended. This prevents Internet Explorer from seeing a bogus file
+extension.
+</p>
+</body>
+</html>
+HTML;
+ echo "\n";
+ return true;
+ }
+
+ /**
* Returns true if the PATH_INFO ends with an extension other than a script
* extension. This could confuse IE for scripts that send arbitrary data which
* is not HTML but may be detected as such.
@@ -695,30 +809,163 @@ class WebRequest {
*/
public function isPathInfoBad() {
global $wgScriptExtension;
+ $extWhitelist[] = ltrim( $wgScriptExtension, '.' );
+ return IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist );
+ }
+
+ /**
+ * Parse the Accept-Language header sent by the client into an array
+ * @return array( languageCode => q-value ) sorted by q-value in descending order
+ * May contain the "language" '*', which applies to languages other than those explicitly listed.
+ * This is aligned with rfc2616 section 14.4
+ */
+ public function getAcceptLang() {
+ // Modified version of code found at http://www.thefutureoftheweb.com/blog/use-accept-language-header
+ $acceptLang = $this->getHeader( 'Accept-Language' );
+ if ( !$acceptLang ) {
+ return array();
+ }
+
+ // Return the language codes in lower case
+ $acceptLang = strtolower( $acceptLang );
+
+ // Break up string into pieces (languages and q factors)
+ $lang_parse = null;
+ preg_match_all( '/([a-z]{1,8}(-[a-z]{1,8})?|\*)\s*(;\s*q\s*=\s*(1|0(\.[0-9]+)?)?)?/',
+ $acceptLang, $lang_parse );
- if ( isset( $_SERVER['QUERY_STRING'] )
- && preg_match( '/\.[^\\/:*?"<>|%]+(#|\?|$)/i', $_SERVER['QUERY_STRING'] ) )
- {
- // Bug 28235
- // Block only Internet Explorer, and requests with missing UA
- // headers that could be IE users behind a privacy proxy.
- if ( !isset( $_SERVER['HTTP_USER_AGENT'] )
- || preg_match( '/; *MSIE/', $_SERVER['HTTP_USER_AGENT'] ) )
- {
- return true;
+ if ( !count( $lang_parse[1] ) ) {
+ return array();
+ }
+
+ // Create a list like "en" => 0.8
+ $langs = array_combine( $lang_parse[1], $lang_parse[4] );
+ // Set default q factor to 1
+ foreach ( $langs as $lang => $val ) {
+ if ( $val === '' ) {
+ $langs[$lang] = 1;
+ } else if ( $val == 0 ) {
+ unset($langs[$lang]);
}
}
- if ( !isset( $_SERVER['PATH_INFO'] ) ) {
- return false;
+ // Sort list
+ arsort( $langs, SORT_NUMERIC );
+ return $langs;
+ }
+}
+
+/**
+ * Object to access the $_FILES array
+ */
+class WebRequestUpload {
+ protected $request;
+ protected $doesExist;
+ protected $fileInfo;
+
+ /**
+ * Constructor. Should only be called by WebRequest
+ *
+ * @param $request WebRequest The associated request
+ * @param $key string Key in $_FILES array (name of form field)
+ */
+ public function __construct( $request, $key ) {
+ $this->request = $request;
+ $this->doesExist = isset( $_FILES[$key] );
+ if ( $this->doesExist ) {
+ $this->fileInfo = $_FILES[$key];
}
- $pi = $_SERVER['PATH_INFO'];
- $dotPos = strrpos( $pi, '.' );
- if ( $dotPos === false ) {
- return false;
+ }
+
+ /**
+ * Return whether a file with this name was uploaded.
+ *
+ * @return bool
+ */
+ public function exists() {
+ return $this->doesExist;
+ }
+
+ /**
+ * Return the original filename of the uploaded file
+ *
+ * @return mixed Filename or null if non-existent
+ */
+ public function getName() {
+ if ( !$this->exists() ) {
+ return null;
+ }
+
+ global $wgContLang;
+ $name = $this->fileInfo['name'];
+
+ # Safari sends filenames in HTML-encoded Unicode form D...
+ # Horrid and evil! Let's try to make some kind of sense of it.
+ $name = Sanitizer::decodeCharReferences( $name );
+ $name = $wgContLang->normalize( $name );
+ wfDebug( __METHOD__ . ": {$this->fileInfo['name']} normalized to '$name'\n" );
+ return $name;
+ }
+
+ /**
+ * Return the file size of the uploaded file
+ *
+ * @return int File size or zero if non-existent
+ */
+ public function getSize() {
+ if ( !$this->exists() ) {
+ return 0;
+ }
+
+ return $this->fileInfo['size'];
+ }
+
+ /**
+ * Return the path to the temporary file
+ *
+ * @return mixed Path or null if non-existent
+ */
+ public function getTempName() {
+ if ( !$this->exists() ) {
+ return null;
+ }
+
+ return $this->fileInfo['tmp_name'];
+ }
+
+ /**
+ * Return the upload error. See link for explanation
+ * http://www.php.net/manual/en/features.file-upload.errors.php
+ *
+ * @return int One of the UPLOAD_ constants, 0 if non-existent
+ */
+ public function getError() {
+ if ( !$this->exists() ) {
+ return 0; # UPLOAD_ERR_OK
}
- $ext = substr( $pi, $dotPos );
- return !in_array( $ext, array( $wgScriptExtension, '.php', '.php5' ) );
+
+ return $this->fileInfo['error'];
+ }
+
+ /**
+ * Returns whether this upload failed because of overflow of a maximum set
+ * in php.ini
+ *
+ * @return bool
+ */
+ public function isIniSizeOverflow() {
+ if ( $this->getError() == UPLOAD_ERR_INI_SIZE ) {
+ # PHP indicated that upload_max_filesize is exceeded
+ return true;
+ }
+
+ $contentLength = $this->request->getHeader( 'CONTENT_LENGTH' );
+ if ( $contentLength > wfShorthandToInteger( ini_get( 'post_max_size' ) ) ) {
+ # post_max_size is exceeded
+ return true;
+ }
+
+ return false;
}
}
@@ -730,12 +977,12 @@ class WebRequest {
class FauxRequest extends WebRequest {
private $wasPosted = false;
private $session = array();
- private $response;
/**
* @param $data Array of *non*-urlencoded key => value pairs, the
* fake GET/POST values
* @param $wasPosted Bool: whether to treat the data as POST
+ * @param $session Mixed: session array or null
*/
public function __construct( $data, $wasPosted = false, $session = null ) {
if( is_array( $data ) ) {
@@ -774,7 +1021,25 @@ class FauxRequest extends WebRequest {
}
public function appendQuery( $query ) {
- $this->notImplemented( __METHOD__ );
+ global $wgTitle;
+ $basequery = '';
+ foreach( $this->data as $var => $val ) {
+ if ( $var == 'title' ) {
+ continue;
+ }
+ if ( is_array( $val ) ) {
+ /* This will happen given a request like
+ * http://en.wikipedia.org/w/index.php?title[]=Special:Userlogin&returnto[]=Main_Page
+ */
+ continue;
+ }
+ $basequery .= '&' . urlencode( $var ) . '=' . urlencode( $val );
+ }
+ $basequery .= '&' . $query;
+
+ # Trim the extra &
+ $basequery = substr( $basequery, 1 );
+ return $wgTitle->getLocalURL( $basequery );
}
public function getHeader( $name ) {
@@ -791,10 +1056,14 @@ class FauxRequest extends WebRequest {
}
public function setSessionData( $key, $data ) {
- $this->notImplemented( __METHOD__ );
+ $this->session[$key] = $data;
}
- public function isPathInfoBad() {
+ public function isPathInfoBad( $extWhitelist = array() ) {
return false;
}
+
+ public function checkUrlExtension( $extWhitelist = array() ) {
+ return true;
+ }
}
diff --git a/includes/WebResponse.php b/includes/WebResponse.php
index f7d57e41..2b1ec04c 100644
--- a/includes/WebResponse.php
+++ b/includes/WebResponse.php
@@ -1,5 +1,11 @@
<?php
/**
+ * Classes used to send headers and cookies back to the user
+ *
+ * @file
+ */
+
+/**
* Allow programs to request this object from WebRequest::response()
* and handle all outputting (or lack of outputting) via it.
* @ingroup HTTP
diff --git a/includes/WebStart.php b/includes/WebStart.php
index d62b4a62..b5140718 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -1,14 +1,18 @@
<?php
-
-# This does the initial setup for a web request. It does some security checks,
-# starts the profiler and loads the configuration, and optionally loads
-# Setup.php depending on whether MW_NO_SETUP is defined.
+/**
+ * This does the initial setup for a web request.
+ * It does some security checks, starts the profiler and loads the
+ * configuration, and optionally loads Setup.php depending on whether
+ * MW_NO_SETUP is defined.
+ *
+ * @file
+ */
# Protect against register_globals
# This must be done before any globals are set by the code
if ( ini_get( 'register_globals' ) ) {
if ( isset( $_REQUEST['GLOBALS'] ) ) {
- die( '<a href="http://www.hardened-php.net/index.76.html">$GLOBALS overwrite vulnerability</a>');
+ die( '<a href="http://www.hardened-php.net/globals-problem">$GLOBALS overwrite vulnerability</a>');
}
$verboten = array(
'GLOBALS',
@@ -30,7 +34,7 @@ if ( ini_get( 'register_globals' ) ) {
);
foreach ( $_REQUEST as $name => $value ) {
if( in_array( $name, $verboten ) ) {
- header( "HTTP/1.x 500 Internal Server Error" );
+ header( "HTTP/1.1 500 Internal Server Error" );
echo "register_globals security paranoia: trying to overwrite superglobals, aborting.";
die( -1 );
}
@@ -85,44 +89,47 @@ if ( !function_exists( 'version_compare' )
exit;
}
-# Test for PHP bug which breaks PHP 5.0.x on 64-bit...
-# As of 1.8 this breaks lots of common operations instead
-# of just some rare ones like export.
-$borked = str_replace( 'a', 'b', array( -1 => -1 ) );
-if( !isset( $borked[-1] ) ) {
- echo "PHP 5.0.x is buggy on your 64-bit system; you must upgrade to PHP 5.1.x\n" .
- "or higher. ABORTING. (http://bugs.php.net/bug.php?id=34879 for details)\n";
- exit;
-}
-
# Start the autoloader, so that extensions can derive classes from core files
require_once( "$IP/includes/AutoLoader.php" );
+# Load default settings
+require_once( "$IP/includes/DefaultSettings.php" );
if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
# Use a callback function to configure MediaWiki
- require_once( "$IP/includes/DefaultSettings.php" );
- call_user_func( MW_CONFIG_CALLBACK );
+ $callback = MW_CONFIG_CALLBACK;
+ # PHP 5.1 doesn't support "class::method" for call_user_func, so split it
+ if ( strpos( $callback, '::' ) !== false ) {
+ $callback = explode( '::', $callback, 2);
+ }
+ call_user_func( $callback );
} else {
- # LocalSettings.php is the per site customization file. If it does not exit
- # the wiki installer need to be launched or the generated file moved from
- # ./config/ to ./
- if( !file_exists( "$IP/LocalSettings.php" ) ) {
- require_once( "$IP/includes/DefaultSettings.php" ); # used for printing the version
+ if ( !defined('MW_CONFIG_FILE') )
+ define('MW_CONFIG_FILE', "$IP/LocalSettings.php");
+
+ # LocalSettings.php is the per site customization file. If it does not exist
+ # the wiki installer needs to be launched or the generated file uploaded to
+ # the root wiki directory
+ if( !file_exists( MW_CONFIG_FILE ) ) {
require_once( "$IP/includes/templates/NoLocalSettings.php" );
die();
}
# Include site settings. $IP may be changed (hopefully before the AutoLoader is invoked)
- require_once( "$IP/LocalSettings.php" );
+ require_once( MW_CONFIG_FILE );
}
+
+if ( $wgEnableSelenium ) {
+ require_once( "$IP/includes/SeleniumWebSettings.php" );
+}
+
wfProfileOut( 'WebStart.php-conf' );
wfProfileIn( 'WebStart.php-ob_start' );
# Initialise output buffering
-if ( ob_get_level() ) {
- # Someone's been mixing configuration data with code!
- # How annoying.
-} elseif ( !defined( 'MW_NO_OUTPUT_BUFFER' ) ) {
+# 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 ) {
require_once( "$IP/includes/OutputHandler.php" );
ob_start( 'wfOutputHandler' );
}
@@ -131,3 +138,4 @@ wfProfileOut( 'WebStart.php-ob_start' );
if ( !defined( 'MW_NO_SETUP' ) ) {
require_once( "$IP/includes/Setup.php" );
}
+
diff --git a/includes/Wiki.php b/includes/Wiki.php
index dc4467b6..b2cb1eb0 100644
--- a/includes/Wiki.php
+++ b/includes/Wiki.php
@@ -1,16 +1,14 @@
<?php
/**
* MediaWiki is the to-be base class for this whole project
+ *
+ * @internal documentation reviewed 15 Mar 2010
*/
class MediaWiki {
-
- var $GET; /* Stores the $_GET variables at time of creation, can be changed */
var $params = array();
- /** Constructor. It just save the $_GET variable */
- function __construct() {
- $this->GET = $_GET;
- }
+ /** Constructor */
+ function __construct() {}
/**
* Stores key/value pairs to circumvent global variables
@@ -29,7 +27,8 @@ class MediaWiki {
* Note that keys are case-insensitive!
*
* @param $key String: key to get
- * @param $default Mixed: default value if if the key doesn't exist
+ * @param $default string default value, defaults to empty string
+ * @return $default Mixed: default value if if the key doesn't exist
*/
function getVal( $key, $default = '' ) {
$key = strtolower( $key );
@@ -51,12 +50,12 @@ class MediaWiki {
*/
function performRequestForTitle( &$title, &$article, &$output, &$user, $request ) {
wfProfileIn( __METHOD__ );
-
+
$output->setTitle( $title );
-
+
wfRunHooks( 'BeforeInitialize', array( &$title, &$article, &$output, &$user, $request, $this ) );
-
- if( !$this->preliminaryChecks( $title, $output, $request ) ) {
+
+ if( !$this->preliminaryChecks( $title, $output ) ) {
wfProfileOut( __METHOD__ );
return;
}
@@ -109,9 +108,15 @@ class MediaWiki {
if( $wgRequest->getVal( 'printable' ) === 'yes' ) {
$wgOut->setPrintable();
}
- $ret = null;
- if( $curid = $wgRequest->getInt( 'curid' ) ) {
- # URLs like this are generated by RC, because rc_title isn't always accurate
+
+ $curid = $wgRequest->getInt( 'curid' );
+ if( $wgRequest->getCheck( 'search' ) ) {
+ // Compatibility with old search URLs which didn't use Special:Search
+ // Just check for presence here, so blank requests still
+ // show the search page when using ugly URLs (bug 8054).
+ $ret = SpecialPage::getTitleFor( 'Search' );
+ } elseif( $curid ) {
+ // URLs like this are generated by RC, because rc_title isn't always accurate
$ret = Title::newFromID( $curid );
} elseif( $title == '' && $action != 'delete' ) {
$ret = Title::newMainPage();
@@ -122,7 +127,7 @@ class MediaWiki {
if( count( $wgContLang->getVariants() ) > 1 && !is_null( $ret ) && $ret->getArticleID() == 0 )
$wgContLang->findVariantLink( $title, $ret );
}
- # For non-special titles, check for implicit titles
+ // For non-special titles, check for implicit titles
if( is_null( $ret ) || $ret->getNamespace() != NS_SPECIAL ) {
// We can have urls with just ?diff=,?oldid= or even just ?diff=
$oldid = $wgRequest->getInt( 'oldid' );
@@ -137,28 +142,19 @@ class MediaWiki {
}
/**
- * Checks for search query and anon-cannot-read case
+ * Checks for anon-cannot-read case
*
* @param $title Title
* @param $output OutputPage
- * @param $request WebRequest
+ * @return boolean true if successful
*/
- function preliminaryChecks( &$title, &$output, $request ) {
- if( $request->getCheck( 'search' ) ) {
- // Compatibility with old search URLs which didn't use Special:Search
- // Just check for presence here, so blank requests still
- // show the search page when using ugly URLs (bug 8054).
-
- // Do this above the read whitelist check for security...
- $title = SpecialPage::getTitleFor( 'Search' );
- }
- # If the user is not logged in, the Namespace:title of the article must be in
- # the Read array in order for the user to see it. (We have to check here to
- # catch special pages etc. We check again in Article::view())
+ function preliminaryChecks( &$title, &$output ) {
+ // If the user is not logged in, the Namespace:title of the article must be in
+ // the Read array in order for the user to see it. (We have to check here to
+ // catch special pages etc. We check again in Article::view())
if( !is_null( $title ) && !$title->userCanRead() ) {
- global $wgDeferredUpdateList;
$output->loginToUse();
- $this->finalCleanup( $wgDeferredUpdateList, $output );
+ $this->finalCleanup( $output );
$output->disable();
return false;
}
@@ -179,19 +175,20 @@ class MediaWiki {
*/
function handleSpecialCases( &$title, &$output, $request ) {
wfProfileIn( __METHOD__ );
- global $wgContLang, $wgUser;
+
$action = $this->getVal( 'Action' );
- $perferred = $wgContLang->getPreferredVariant( false );
// Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty.
- if( is_null($title) || ( ($title->getDBkey() == '') && ($title->getInterwiki() == '') ) ) {
+ if( is_null($title) || ( ( $title->getDBkey() == '' ) && ( $title->getInterwiki() == '' ) ) ) {
$title = SpecialPage::getTitleFor( 'Badtitle' );
- # Die now before we mess up $wgArticle and the skin stops working
+ $output->setTitle( $title ); // bug 21456
+ // Die now before we mess up $wgArticle and the skin stops working
throw new ErrorPageError( 'badtitle', 'badtitletext' );
// Interwiki redirects
} else if( $title->getInterwiki() != '' ) {
- if( $rdfrom = $request->getVal( 'rdfrom' ) ) {
+ $rdfrom = $request->getVal( 'rdfrom' );
+ if( $rdfrom ) {
$url = $title->getFullURL( 'rdfrom=' . urlencode( $rdfrom ) );
} else {
$query = $request->getValues();
@@ -200,26 +197,26 @@ class MediaWiki {
}
/* Check for a redirect loop */
if( !preg_match( '/^' . preg_quote( $this->getVal('Server'), '/' ) . '/', $url ) && $title->isLocal() ) {
- $output->redirect( $url );
+ // 301 so google et al report the target as the actual url.
+ $output->redirect( $url, 301 );
} else {
$title = SpecialPage::getTitleFor( 'Badtitle' );
+ $output->setTitle( $title ); // bug 21456
wfProfileOut( __METHOD__ );
throw new ErrorPageError( 'badtitle', 'badtitletext' );
}
// Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
- } else if( $action == 'view' && !$request->wasPosted() &&
- ( ( !isset($this->GET['title']) || $title->getPrefixedDBKey() != $this->GET['title'] ) ||
- // No valid variant in URL (if the main-language has multi-variants), to ensure
- // anonymous access would always be redirect to a URL with 'variant' parameter
- ( !isset($this->GET['variant']) && $wgContLang->hasVariants() && !$wgUser->isLoggedIn() ) ) &&
- !count( array_diff( array_keys( $this->GET ), array( 'action', 'title' ) ) ) )
+ } else if ( $action == 'view' && !$request->wasPosted()
+ && ( $request->getVal( 'title' ) === null || $title->getPrefixedDBKey() != $request->getVal( 'title' ) )
+ && !count( array_diff( array_keys( $request->getValues() ), array( 'action', 'title' ) ) ) )
{
- if( !$wgUser->isLoggedIn() ) {
- $pref = $wgContLang->getPreferredVariant( false, $fromHeader = true );
- $targetUrl = $title->getFullURL( '', $variant = $pref );
+ if ( $title->getNamespace() == NS_SPECIAL ) {
+ list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
+ if ( $name ) {
+ $title = SpecialPage::getTitleFor( $name, $subpage );
+ }
}
- else
- $targetUrl = $title->getFullURL();
+ $targetUrl = $title->getFullURL();
// Redirect to canonical url, make it a 301 to allow caching
if( $targetUrl == $request->getFullRequestURL() ) {
$message = "Redirect loop detected!\n\n" .
@@ -304,8 +301,8 @@ class MediaWiki {
$action = $this->getVal( 'action', 'view' );
$article = self::articleFromTitle( $title );
- # NS_MEDIAWIKI has no redirects.
- # It is also used for CSS/JS, so performance matters here...
+ // 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;
@@ -315,23 +312,24 @@ class MediaWiki {
$file = ($title->getNamespace() == NS_FILE) ? $article->getFile() : null;
if( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
&& !$request->getVal( 'oldid' ) && // ... and are not old revisions
+ !$request->getVal( 'diff' ) && // ... and not when showing diff
$request->getVal( 'redirect' ) != 'no' && // ... unless explicitly told not to
// ... and the article is not a non-redirect image page with associated file
!( is_object( $file ) && $file->exists() && !$file->getRedirected() ) )
{
- # Give extensions a change to ignore/handle redirects as needed
+ // Give extensions a change to ignore/handle redirects as needed
$ignoreRedirect = $target = false;
-
+
$dbr = wfGetDB( DB_SLAVE );
$article->loadPageData( $article->pageDataFromTitle( $dbr, $title ) );
- wfRunHooks( 'InitializeArticleMaybeRedirect',
+ wfRunHooks( 'InitializeArticleMaybeRedirect',
array(&$title,&$request,&$ignoreRedirect,&$target,&$article) );
// Follow redirects only for... redirects.
// If $target is set, then a hook wanted to redirect.
if( !$ignoreRedirect && ($target || $article->isRedirect()) ) {
- # Is the target already set by an extension?
+ // Is the target already set by an extension?
$target = $target ? $target : $article->followRedirect();
if( is_string( $target ) ) {
if( !$this->getVal( 'DisableHardRedirects' ) ) {
@@ -363,52 +361,23 @@ class MediaWiki {
* Cleaning up request by doing:
** deferred updates, DB transaction, and the output
*
- * @param $deferredUpdates array of updates to do
* @param $output OutputPage
*/
- function finalCleanup( &$deferredUpdates, &$output ) {
+ function finalCleanup( &$output ) {
wfProfileIn( __METHOD__ );
- # Now commit any transactions, so that unreported errors after
- # output() don't roll back the whole DB transaction
+ // Now commit any transactions, so that unreported errors after
+ // output() don't roll back the whole DB transaction
$factory = wfGetLBFactory();
$factory->commitMasterChanges();
- # Output everything!
+ // Output everything!
$output->output();
- # Do any deferred jobs
- $this->doUpdates( $deferredUpdates );
+ // Do any deferred jobs
+ wfDoUpdates( true );
$this->doJobs();
wfProfileOut( __METHOD__ );
}
/**
- * Deferred updates aren't really deferred anymore. It's important to report
- * errors to the user, and that means doing this before OutputPage::output().
- * Note that for page saves, the client will wait until the script exits
- * anyway before following the redirect.
- *
- * @param $updates array of objects that hold an update to do
- */
- function doUpdates( &$updates ) {
- wfProfileIn( __METHOD__ );
- /* No need to get master connections in case of empty updates array */
- if (!$updates) {
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $dbw = wfGetDB( DB_MASTER );
- foreach( $updates as $up ) {
- $up->doUpdate();
-
- # Commit after every update to prevent lock contention
- if( $dbw->trxLevel() ) {
- $dbw->commit();
- }
- }
- wfProfileOut( __METHOD__ );
- }
-
- /**
* Do a job from the job queue
*/
function doJobs() {
@@ -447,7 +416,7 @@ class MediaWiki {
*/
function restInPeace() {
wfLogProfilingData();
- # Commit and close up!
+ // Commit and close up!
$factory = wfGetLBFactory();
$factory->commitMasterChanges();
$factory->shutdown();
@@ -477,8 +446,8 @@ class MediaWiki {
$action = 'nosuchaction';
}
- # Workaround for bug #20966: inability of IE to provide an action dependent
- # on which submit button is clicked.
+ // Workaround for bug #20966: inability of IE to provide an action dependent
+ // on which submit button is clicked.
if ( $action === 'historysubmit' ) {
if ( $request->getBool( 'revisiondelete' ) ) {
$action = 'revisiondelete';
@@ -566,7 +535,7 @@ class MediaWiki {
$history->history();
break;
case 'revisiondelete':
- # For show/hide submission from history page
+ // For show/hide submission from history page
$special = SpecialPage::getPage( 'Revisiondelete' );
$special->execute( '' );
break;
@@ -579,4 +548,4 @@ class MediaWiki {
}
-}; /* End of class MediaWiki */
+}
diff --git a/includes/WikiError.php b/includes/WikiError.php
index 251c1742..452554c3 100644
--- a/includes/WikiError.php
+++ b/includes/WikiError.php
@@ -1,7 +1,8 @@
<?php
/**
* MediaWiki error classes
- * Copyright (C) 2005 Brion Vibber <brion@pobox.com>
+ *
+ * Copyright © 2005 Brion Vibber <brion@pobox.com>
* http://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +20,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
+ * @file
*/
/**
@@ -58,7 +60,13 @@ class WikiError {
* @return bool
*/
public static function isError( $object ) {
- return $object instanceof WikiError;
+ if ( $object instanceof WikiError ) {
+ return true;
+ } elseif ( $object instanceof Status ) {
+ return !$object->isOK();
+ } else {
+ return false;
+ }
}
}
@@ -71,7 +79,7 @@ class WikiErrorMsg extends WikiError {
* @param $message String: wiki message name
* @param ... parameters to pass to wfMsg()
*/
- function WikiErrorMsg( $message/*, ... */ ) {
+ function __construct( $message/*, ... */ ) {
$args = func_get_args();
array_shift( $args );
$this->mMessage = wfMsgReal( $message, $args, true );
@@ -100,7 +108,7 @@ class WikiXmlError extends WikiError {
* @param $context
* @param $offset Int
*/
- function WikiXmlError( $parser, $message = 'XML parsing error', $context = null, $offset = 0 ) {
+ function __construct( $parser, $message = 'XML parsing error', $context = null, $offset = 0 ) {
$this->mXmlError = xml_get_error_code( $parser );
$this->mColumn = xml_get_current_column_number( $parser );
$this->mLine = xml_get_current_line_number( $parser );
diff --git a/includes/WikiMap.php b/includes/WikiMap.php
index 878e165f..e12f7abe 100644
--- a/includes/WikiMap.php
+++ b/includes/WikiMap.php
@@ -12,7 +12,7 @@ class WikiMap {
* @return WikiReference object or null if the wiki was not found
*/
public static function getWiki( $wikiID ) {
- global $wgConf, $IP;
+ global $wgConf;
$wgConf->loadFullData();
@@ -68,12 +68,14 @@ class WikiMap {
global $wgUser;
$sk = $wgUser->getSkin();
- if ( !$text )
+ if ( !$text ) {
$text = $page;
+ }
$url = self::getForeignURL( $wikiID, $page );
- if ( $url === false )
+ if ( $url === false ) {
return false;
+ }
return $sk->makeExternalLink( $url, $text );
}
@@ -88,8 +90,9 @@ class WikiMap {
public static function getForeignURL( $wikiID, $page ) {
$wiki = WikiMap::getWiki( $wikiID );
- if ( $wiki )
+ if ( $wiki ) {
return $wiki->getUrl( $page );
+ }
return false;
}
diff --git a/includes/Xml.php b/includes/Xml.php
index 464b142c..970444fa 100644
--- a/includes/Xml.php
+++ b/includes/Xml.php
@@ -46,8 +46,9 @@ class Xml {
if( is_null( $attribs ) ) {
return null;
} elseif( is_array( $attribs ) ) {
- foreach( $attribs as $name => $val )
+ foreach( $attribs as $name => $val ) {
$out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"';
+ }
return $out;
} else {
throw new MWException( 'Expected attribute array, got something else in ' . __METHOD__ );
@@ -80,7 +81,7 @@ class Xml {
/**
* This opens an XML element
*
- * @param $element name of the element
+ * @param $element String name of the element
* @param $attribs array of attributes, see Xml::expandAttributes()
* @return string
*/
@@ -90,7 +91,7 @@ class Xml {
/**
* Shortcut to close an XML element
- * @param $element element name
+ * @param $element String element name
* @return string
*/
public static function closeElement( $element ) { return "</$element>"; }
@@ -99,9 +100,9 @@ class Xml {
* Same as Xml::element(), but does not escape contents. Handy when the
* content you have is already valid xml.
*
- * @param $element element name
+ * @param $element String element name
* @param $attribs array of attributes
- * @param $contents content of the element
+ * @param $contents String content of the element
* @return string
*/
public static function tags( $element, $attribs = null, $contents ) {
@@ -133,10 +134,12 @@ class Xml {
if( !is_null( $all ) )
$namespaces = array( $all => wfMsg( 'namespacesall' ) ) + $namespaces;
foreach( $namespaces as $index => $name ) {
- if( $index < NS_MAIN )
+ if( $index < NS_MAIN ) {
continue;
- if( $index === 0 )
+ }
+ if( $index === 0 ) {
$name = wfMsg( 'blanknamespace' );
+ }
$options[] = self::option( $name, $index, $index === $selected );
}
@@ -147,7 +150,7 @@ class Xml {
. "\n"
. Xml::closeElement( 'select' );
if ( !is_null( $label ) ) {
- $ret = Xml::label( $label, $element_name ) . '&nbsp;' . $ret;
+ $ret = Xml::label( $label, $element_name ) . '&#160;' . $ret;
}
return $ret;
}
@@ -173,7 +176,7 @@ class Xml {
. implode( "\n", $options )
. self::closeElement( 'select' );
}
-
+
/**
* @param $year Integer
* @param $month Integer
@@ -211,14 +214,14 @@ class Xml {
* @return array of label and select
*/
public static function languageSelector( $selected, $customisedOnly = true ) {
- global $wgContLanguageCode;
+ global $wgLanguageCode;
/**
* Make sure the site language is in the list; a custom language code
* might not have a defined name...
*/
$languages = Language::getLanguageNames( $customisedOnly );
- if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
- $languages[$wgContLanguageCode] = $wgContLanguageCode;
+ if( !array_key_exists( $wgLanguageCode, $languages ) ) {
+ $languages[$wgLanguageCode] = $wgLanguageCode;
}
ksort( $languages );
@@ -227,7 +230,7 @@ class Xml {
* Otherwise, no default is selected and the user ends up
* with an Afrikaans interface since it's first in the list.
*/
- $selected = isset( $languages[$selected] ) ? $selected : $wgContLanguageCode;
+ $selected = isset( $languages[$selected] ) ? $selected : $wgLanguageCode;
$options = "\n";
foreach( $languages as $code => $name ) {
$options .= Xml::option( "$code - $name", $code, ($code == $selected) ) . "\n";
@@ -245,10 +248,10 @@ class Xml {
/**
* Shortcut to make a span element
- * @param $text content of the element, will be escaped
- * @param $class class name of the span element
+ * @param $text String content of the element, will be escaped
+ * @param $class String class name of the span element
* @param $attribs other attributes
- * @return string
+ * @return string
*/
public static function span( $text, $class, $attribs=array() ) {
return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
@@ -260,7 +263,7 @@ class Xml {
* @param $class class name of the span element
* @param $tag element name
* @param $attribs other attributes
- * @return string
+ * @return string
*/
public static function wrapClass( $text, $class, $tag='span', $attribs=array() ) {
return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
@@ -268,17 +271,24 @@ class Xml {
/**
* Convenience function to build an HTML text input field
- * @param $name value of the name attribute
+ * @param $name String value of the name attribute
* @param $size value of the size attribute
* @param $value value of the value attribute
* @param $attribs other attributes
* @return string HTML
*/
public static function input( $name, $size=false, $value=false, $attribs=array() ) {
- return self::element( 'input', array(
- 'name' => $name,
- 'size' => $size,
- 'value' => $value ) + $attribs );
+ $attributes = array( 'name' => $name );
+
+ if( $size ) {
+ $attributes['size'] = $size;
+ }
+
+ if( $value !== false ) { // maybe 0
+ $attributes['value'] = $value;
+ }
+
+ return self::element( 'input', $attributes + $attribs );
}
/**
@@ -303,9 +313,9 @@ class Xml {
/**
* Convenience function to build an HTML checkbox
- * @param $name value of the name attribute
- * @param $checked Whether the checkbox is checked or not
- * @param $attribs other attributes
+ * @param $name String value of the name attribute
+ * @param $checked Bool Whether the checkbox is checked or not
+ * @param $attribs Array other attributes
* @return string HTML
*/
public static function check( $name, $checked=false, $attribs=array() ) {
@@ -335,19 +345,27 @@ class Xml {
/**
* Convenience function to build an HTML form label
- * @param $label text of the label
- * @param $id
+ * @param $label String text of the label
+ * @param $id
+ * @param $attribs Array an attribute array. This will usuall be
+ * the same array as is passed to the corresponding input element,
+ * so this function will cherry-pick appropriate attributes to
+ * apply to the label as well; currently only class is applied.
* @return string HTML
*/
- public static function label( $label, $id ) {
- return self::element( 'label', array( 'for' => $id ), $label );
+ public static function label( $label, $id, $attribs=array() ) {
+ $a = array( 'for' => $id );
+ if( isset( $attribs['class'] ) ){
+ $a['class'] = $attribs['class'];
+ }
+ return self::element( 'label', $a, $label );
}
/**
* Convenience function to build an HTML text input field with a label
- * @param $label text of the label
- * @param $name value of the name attribute
- * @param $id id of the input
+ * @param $label String text of the label
+ * @param $name String value of the name attribute
+ * @param $id String id of the input
* @param $size value of the size attribute
* @param $value value of the value attribute
* @param $attribs other attributes
@@ -355,7 +373,7 @@ class Xml {
*/
public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
- return $label . '&nbsp;' . $input;
+ return $label . '&#160;' . $input;
}
/**
@@ -363,7 +381,7 @@ class Xml {
*/
public static function inputLabelSep( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
return array(
- Xml::label( $label, $id ),
+ Xml::label( $label, $id, $attribs ),
self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
);
}
@@ -374,8 +392,8 @@ class Xml {
*/
public static function checkLabel( $label, $name, $id, $checked=false, $attribs=array() ) {
return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
- '&nbsp;' .
- self::label( $label, $id );
+ '&#160;' .
+ self::label( $label, $id, $attribs );
}
/**
@@ -384,8 +402,8 @@ class Xml {
*/
public static function radioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) {
return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
- '&nbsp;' .
- self::label( $label, $id );
+ '&#160;' .
+ self::label( $label, $id, $attribs );
}
/**
@@ -436,7 +454,6 @@ class Xml {
* @return string
*/
public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = Null ) {
- $options = '';
$optgroup = false;
$options = self::option( $other, 'other', $selected === 'other' );
@@ -501,7 +518,7 @@ class Xml {
return $s;
}
-
+
/**
* Shortcut for creating textareas.
*
@@ -566,8 +583,8 @@ class Xml {
$s = $value ? 'true' : 'false';
} elseif ( is_null( $value ) ) {
$s = 'null';
- } elseif ( is_int( $value ) ) {
- $s = $value;
+ } elseif ( is_int( $value ) || is_float( $value ) ) {
+ $s = strval($value);
} elseif ( is_array( $value ) && // Make sure it's not associative.
array_keys($value) === range( 0, count($value) - 1 ) ||
count($value) == 0
@@ -580,6 +597,8 @@ class Xml {
$s .= self::encodeJsVar( $elt );
}
$s .= ']';
+ } elseif ( $value instanceof XmlJsCode ) {
+ $s = $value->value;
} elseif ( is_object( $value ) || is_array( $value ) ) {
// Objects and associative arrays
$s = '{';
@@ -597,6 +616,30 @@ class Xml {
return $s;
}
+ /**
+ * Create a call to a JavaScript function. The supplied arguments will be
+ * encoded using Xml::encodeJsVar().
+ *
+ * @param $name The name of the function to call, or a JavaScript expression
+ * which evaluates to a function object which is called.
+ * @param $args Array of arguments to pass to the function.
+ * @since 1.17
+ */
+ public static function encodeJsCall( $name, $args ) {
+ $s = "$name(";
+ $first = true;
+ foreach ( $args as $arg ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $s .= ', ';
+ }
+ $s .= Xml::encodeJsVar( $arg );
+ }
+ $s .= ");\n";
+ return $s;
+ }
+
/**
* Check if a string is well-formed XML.
@@ -655,18 +698,18 @@ class Xml {
array( '&quot;', '&gt;', '&lt;' ),
$in );
}
-
+
/**
* Generate a form (without the opening form element).
* Output optionally includes a submit button.
- * @param $fields Associative array, key is message corresponding to a description for the field (colon is in the message), value is appropriate input.
- * @param $submitLabel A message containing a label for the submit button.
+ * @param $fields Array Associative array, key is message corresponding to a description for the field (colon is in the message), value is appropriate input.
+ * @param $submitLabel String A message containing a label for the submit button.
* @return string HTML form.
*/
public static function buildForm( $fields, $submitLabel = null ) {
$form = '';
$form .= "<table><tbody>";
-
+
foreach( $fields as $labelmsg => $input ) {
$id = "mw-$labelmsg";
$form .= Xml::openElement( 'tr', array( 'id' => $id ) );
@@ -681,13 +724,13 @@ class Xml {
$form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMsg( $submitLabel ) ) . Xml::closeElement( 'td' );
$form .= Xml::closeElement( 'tr' );
}
-
+
$form .= "</tbody></table>";
-
+
return $form;
}
-
+
/**
* Build a table of data
* @param $rows An array of arrays of strings, each to be a row in a table
@@ -712,7 +755,7 @@ class Xml {
$s .= Xml::closeElement( 'table' );
return $s;
}
-
+
/**
* Build a row for a table
* @param $attribs An array of attributes to apply to the tr tag
@@ -737,9 +780,15 @@ class XmlSelect {
protected $attributes = array();
public function __construct( $name = false, $id = false, $default = false ) {
- if ( $name ) $this->setAttribute( 'name', $name );
- if ( $id ) $this->setAttribute( 'id', $id );
- if ( $default ) $this->default = $default;
+ if ( $name ) {
+ $this->setAttribute( 'name', $name );
+ }
+ if ( $id ) {
+ $this->setAttribute( 'id', $id );
+ }
+ if ( $default !== false ) {
+ $this->default = $default;
+ }
}
public function setDefault( $default ) {
@@ -763,7 +812,7 @@ class XmlSelect {
$value = ($value !== false) ? $value : $name;
$this->options[] = Xml::option( $name, $value, $value === $this->default );
}
-
+
// This accepts an array of form
// label => value
// label => ( label => value, label => value )
@@ -773,7 +822,7 @@ class XmlSelect {
// This accepts an array of form
// label => value
- // label => ( label => value, label => value )
+ // label => ( label => value, label => value )
static function formatOptions( $options, $default = false ) {
$data = '';
foreach( $options as $label => $value ) {
@@ -784,7 +833,7 @@ class XmlSelect {
$data .= Xml::option( $label, $value, $value === $default ) . "\n";
}
}
-
+
return $data;
}
@@ -793,3 +842,23 @@ class XmlSelect {
}
}
+
+/**
+ * A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to
+ * interpret a given string as being a JavaScript expression, instead of string
+ * data.
+ *
+ * Example:
+ *
+ * Xml::encodeJsVar( new XmlJsCode( 'a + b' ) );
+ *
+ * Returns "a + b".
+ * @since 1.17
+ */
+class XmlJsCode {
+ public $value;
+
+ function __construct( $value ) {
+ $this->value = $value;
+ }
+}
diff --git a/includes/XmlFunctions.php b/includes/XmlFunctions.php
deleted file mode 100644
index 8cb8f3f5..00000000
--- a/includes/XmlFunctions.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-/**
- * Aliases for functions in the Xml module
- * Look at the Xml class (Xml.php) for the implementations.
- */
-function wfElement( $element, $attribs = null, $contents = '') {
- wfDeprecated(__FUNCTION__);
- return Xml::element( $element, $attribs, $contents );
-}
-function wfElementClean( $element, $attribs = array(), $contents = '') {
- wfDeprecated(__FUNCTION__);
- return Xml::elementClean( $element, $attribs, $contents );
-}
-function wfOpenElement( $element, $attribs = null ) {
- wfDeprecated(__FUNCTION__);
- return Xml::openElement( $element, $attribs );
-}
-function wfCloseElement( $element ) {
- wfDeprecated(__FUNCTION__);
- return "</$element>";
-}
-function HTMLnamespaceselector($selected = '', $allnamespaces = null ) {
- wfDeprecated(__FUNCTION__);
- return Xml::namespaceSelector( $selected, $allnamespaces );
-}
-function wfSpan( $text, $class, $attribs=array() ) {
- wfDeprecated(__FUNCTION__);
- return Xml::span( $text, $class, $attribs );
-}
-function wfInput( $name, $size=false, $value=false, $attribs=array() ) {
- wfDeprecated(__FUNCTION__);
- return Xml::input( $name, $size, $value, $attribs );
-}
-function wfAttrib( $name, $present = true ) {
- wfDeprecated(__FUNCTION__);
- return Xml::attrib( $name, $present );
-}
-function wfCheck( $name, $checked=false, $attribs=array() ) {
- wfDeprecated(__FUNCTION__);
- return Xml::check( $name, $checked, $attribs );
-}
-function wfRadio( $name, $value, $checked=false, $attribs=array() ) {
- wfDeprecated(__FUNCTION__);
- return Xml::radio( $name, $value, $checked, $attribs );
-}
-function wfLabel( $label, $id ) {
- wfDeprecated(__FUNCTION__);
- return Xml::label( $label, $id );
-}
-function wfInputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
- wfDeprecated(__FUNCTION__);
- return Xml::inputLabel( $label, $name, $id, $size, $value, $attribs );
-}
-function wfCheckLabel( $label, $name, $id, $checked=false, $attribs=array() ) {
- wfDeprecated(__FUNCTION__);
- return Xml::checkLabel( $label, $name, $id, $checked, $attribs );
-}
-function wfRadioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) {
- wfDeprecated(__FUNCTION__);
- return Xml::radioLabel( $label, $name, $value, $id, $checked, $attribs );
-}
-function wfSubmitButton( $value, $attribs=array() ) {
- wfDeprecated(__FUNCTION__);
- return Xml::submitButton( $value, $attribs );
-}
-function wfHidden( $name, $value, $attribs=array() ) {
- wfDeprecated(__FUNCTION__);
- return Xml::hidden( $name, $value, $attribs );
-}
-function wfEscapeJsString( $string ) {
- wfDeprecated(__FUNCTION__);
- return Xml::escapeJsString( $string );
-}
-function wfIsWellFormedXml( $text ) {
- wfDeprecated(__FUNCTION__);
- return Xml::isWellFormed( $text );
-}
-function wfIsWellFormedXmlFragment( $text ) {
- wfDeprecated(__FUNCTION__);
- return Xml::isWellFormedXmlFragment( $text );
-}
-
-function wfBuildForm( $fields, $submitLabel ) {
- wfDeprecated(__FUNCTION__);
- return Xml::buildForm( $fields, $submitLabel );
-}
diff --git a/includes/ZhClient.php b/includes/ZhClient.php
index 61faa8df..a04220c6 100644
--- a/includes/ZhClient.php
+++ b/includes/ZhClient.php
@@ -12,7 +12,7 @@ class ZhClient {
*
* @access private
*/
- function ZhClient($host, $port) {
+ function __construct($host, $port) {
$this->mHost = $host;
$this->mPort = $port;
$this->mConnected = $this->connect();
@@ -35,7 +35,7 @@ class ZhClient {
$errno = $errstr = '';
$this->mFP = fsockopen($this->mHost, $this->mPort, $errno, $errstr, 30);
wfRestoreWarnings();
- if(!$this->mFP) {
+ if ( !$this->mFP ) {
return false;
}
return true;
@@ -47,8 +47,9 @@ class ZhClient {
* @access private
*/
function query($request) {
- if(!$this->mConnected)
+ if ( !$this->mConnected ) {
return false;
+ }
fwrite($this->mFP, $request);
@@ -68,8 +69,9 @@ class ZhClient {
$data .= $str;
}
//data should be of length $len. otherwise something is wrong
- if(strlen($data) != $len)
+ if ( strlen($data) != $len ) {
return false;
+ }
return $data;
}
@@ -84,8 +86,9 @@ class ZhClient {
$len = strlen($text);
$q = "CONV $tolang $len\n$text";
$result = $this->query($q);
- if(!$result)
+ if ( !$result ) {
$result = $text;
+ }
return $result;
}
@@ -99,8 +102,9 @@ class ZhClient {
$len = strlen($text);
$q = "CONV ALL $len\n$text";
$result = $this->query($q);
- if(!$result)
+ if ( !$result ) {
return false;
+ }
list($infoline, $data) = explode('|', $result, 2);
$info = explode(";", $infoline);
$ret = array();
@@ -122,8 +126,8 @@ class ZhClient {
$len = strlen($text);
$q = "SEG $len\n$text";
$result = $this->query($q);
- if(!$result) {// fallback to character based segmentation
- $result = ZhClientFake::segment($text);
+ if ( !$result ) {// fallback to character based segmentation
+ $result = $this->segment($text);
}
return $result;
}
diff --git a/includes/ZhConversion.php b/includes/ZhConversion.php
index 400cdd2e..7ffdeab8 100644
--- a/includes/ZhConversion.php
+++ b/includes/ZhConversion.php
@@ -4,6 +4,8 @@
*
* Automatically generated using code and data in includes/zhtable/
* Do not modify directly!
+ *
+ * @file
*/
$zh2Hant = array(
@@ -82,6 +84,7 @@ $zh2Hant = array(
'䴗' => '鶪',
'䴘' => '鷈',
'䴙' => '鷿',
+'䶮' => '龑',
'万' => '萬',
'与' => '與',
'专' => '專',
@@ -203,6 +206,7 @@ $zh2Hant = array(
'击' => '擊',
'凿' => '鑿',
'刍' => '芻',
+'划' => '劃',
'刘' => '劉',
'则' => '則',
'刚' => '剛',
@@ -272,6 +276,7 @@ $zh2Hant = array(
'号' => '號',
'叹' => '嘆',
'叽' => '嘰',
+'后' => '後',
'吓' => '嚇',
'吕' => '呂',
'吗' => '嗎',
@@ -2745,40 +2750,82 @@ $zh2Hant = array(
'𫛢' => '鸋',
'𫛶' => '鶒',
'𫛸' => '鶗',
+'0只支持' => '0只支持',
+'0只支援' => '0只支援',
+'0周后' => '0周後',
'0多只' => '0多隻',
'0天后' => '0天後',
+'0年' => '0年',
'0只' => '0隻',
'0余' => '0餘',
+'1只支持' => '1只支持',
+'1只支援' => '1只支援',
+'1周后' => '1周後',
'1天后' => '1天後',
+'1年' => '1年',
'1只' => '1隻',
'1余' => '1餘',
+'2只支持' => '2只支持',
+'2只支援' => '2只支援',
+'2周后' => '2周後',
'2天后' => '2天後',
+'2年' => '2年',
'2只' => '2隻',
'2余' => '2餘',
+'3只支持' => '3只支持',
+'3只支援' => '3只支援',
+'3周后' => '3周後',
'3天后' => '3天後',
+'3年' => '3年',
'3只' => '3隻',
'3余' => '3餘',
+'4只支持' => '4只支持',
+'4只支援' => '4只支援',
+'4周后' => '4周後',
'4天后' => '4天後',
+'4年' => '4年',
'4只' => '4隻',
'4余' => '4餘',
+'5只支持' => '5只支持',
+'5只支援' => '5只支援',
+'5周后' => '5周後',
'5天后' => '5天後',
+'5年' => '5年',
'5只' => '5隻',
'5余' => '5餘',
+'6只支持' => '6只支持',
+'6只支援' => '6只支援',
+'6周后' => '6周後',
'6天后' => '6天後',
+'6年' => '6年',
'6只' => '6隻',
'6余' => '6餘',
+'7只支持' => '7只支持',
+'7只支援' => '7只支援',
+'7周后' => '7周後',
'7天后' => '7天後',
+'7年' => '7年',
'7只' => '7隻',
'7余' => '7餘',
+'8只支持' => '8只支持',
+'8只支援' => '8只支援',
+'8周后' => '8周後',
'8天后' => '8天後',
+'8年' => '8年',
'8只' => '8隻',
'8余' => '8餘',
+'9只支持' => '9只支持',
+'9只支援' => '9只支援',
+'9周后' => '9周後',
'9天后' => '9天後',
+'9年' => '9年',
'9只' => '9隻',
'9余' => '9餘',
'·范' => '·范',
'、克制' => '、剋制',
'。克制' => '。剋制',
+'〇周后' => '〇周後',
+'〇年' => '〇年',
'〇只' => '〇隻',
'〇余' => '〇餘',
'一干二净' => '一乾二淨',
@@ -2794,10 +2841,11 @@ $zh2Hant = array(
'一出生' => '一出生',
'一出祁山' => '一出祁山',
'一出逃' => '一出逃',
-'一前一后' => '一前一後',
-'一划' => '一劃',
+'一划' => '一划',
'一半只' => '一半只',
+'一吊錢' => '一吊錢',
'一吊钱' => '一吊錢',
+'一周后' => '一周後',
'一地里' => '一地裡',
'一伙' => '一夥',
'一天后' => '一天後',
@@ -2807,6 +2855,8 @@ $zh2Hant = array(
'一干弟兄' => '一干弟兄',
'一干弟子' => '一干弟子',
'一干部下' => '一干部下',
+'一年' => '一年',
+'一年里' => '一年裡',
'一吊' => '一弔',
'一别头' => '一彆頭',
'一斗斗' => '一斗斗',
@@ -2820,9 +2870,11 @@ $zh2Hant = array(
'一锅面' => '一鍋麵',
'一只' => '一隻',
'一面食' => '一面食',
+'一头长发' => '一頭長髮',
'一余' => '一餘',
'一发千钧' => '一髮千鈞',
'一哄而散' => '一鬨而散',
+'一出子' => '一齣子',
'丁丁当当' => '丁丁當當',
'丁丑' => '丁丑',
'七个' => '七個',
@@ -2832,8 +2884,9 @@ $zh2Hant = array(
'七出生' => '七出生',
'七出祁山' => '七出祁山',
'七出逃' => '七出逃',
-'七划' => '七劃',
+'七周后' => '七周後',
'七天后' => '七天後',
+'七年' => '七年',
'七情六欲' => '七情六慾',
'七扎' => '七紮',
'七只' => '七隻',
@@ -2847,7 +2900,9 @@ $zh2Hant = array(
'三出生' => '三出生',
'三出祁山' => '三出祁山',
'三出逃' => '三出逃',
+'三周后' => '三周後',
'三天后' => '三天後',
+'三年' => '三年',
'三征七辟' => '三徵七辟',
'三准' => '三準',
'三扎' => '三紮',
@@ -2890,7 +2945,6 @@ $zh2Hant = array(
'不准许' => '不准許',
'不准谁' => '不准誰',
'不克制' => '不剋制',
-'不前不后' => '不前不後',
'不加自制' => '不加自制',
'不占凶吉' => '不占凶吉',
'不占卜' => '不占卜',
@@ -2918,6 +2972,7 @@ $zh2Hant = array(
'不干' => '不幹',
'不吊' => '不弔',
'不采' => '不採',
+'不斗膽' => '不斗膽',
'不斗胆' => '不斗膽',
'不断发' => '不斷發',
'不每只' => '不每只',
@@ -2930,6 +2985,7 @@ $zh2Hant = array(
'不通吊庆' => '不通弔慶',
'不丑' => '不醜',
'不采声' => '不采聲',
+'不采聲' => '不采聲',
'不锈钢' => '不鏽鋼',
'不食干腊' => '不食乾腊',
'不斗' => '不鬥',
@@ -2950,12 +3006,15 @@ $zh2Hant = array(
'世纪钟表' => '世紀鐘錶',
'丢丑' => '丟醜',
'并不准' => '並不准',
+'并不准确' => '並不準確',
'并存着' => '並存著',
'并曰入淀' => '並曰入澱',
'并发动' => '並發動',
'并发展' => '並發展',
+'并发布' => '並發布',
'并发现' => '並發現',
'并发表' => '並發表',
+'并行' => '並行',
'中国国际信托投资公司' => '中國國際信托投資公司',
'中型钟' => '中型鐘',
'中型钟表面' => '中型鐘表面',
@@ -2963,11 +3022,13 @@ $zh2Hant = array(
'中型钟面' => '中型鐘面',
'中仑' => '中崙',
'中岳' => '中嶽',
+'中庄子' => '中庄子',
'中文里' => '中文裡',
'中于' => '中於',
'中签' => '中籤',
'中美发表' => '中美發表',
'中药' => '中藥',
+'中风后' => '中風後',
'丰儀' => '丰儀',
'丰仪' => '丰儀',
'丰南' => '丰南',
@@ -2994,7 +3055,6 @@ $zh2Hant = array(
'之一只' => '之一只',
'之二只' => '之二只',
'之八九只' => '之八九只',
-'之后' => '之後',
'之征' => '之徵',
'之托' => '之託',
'之钟' => '之鐘',
@@ -3008,13 +3068,15 @@ $zh2Hant = array(
'九出生' => '九出生',
'九出祁山' => '九出祁山',
'九出逃' => '九出逃',
-'九划' => '九劃',
+'九周后' => '九周後',
'九天后' => '九天後',
+'九年' => '九年',
'九谷' => '九穀',
'九扎' => '九紮',
'九只' => '九隻',
'九余' => '九餘',
'九龙表行' => '九龍表行',
+'九龍表行' => '九龍表行',
'也克制' => '也剋制',
'也斗了胆' => '也斗了膽',
'干干' => '乾乾',
@@ -3176,7 +3238,7 @@ $zh2Hant = array(
'乱哄' => '亂鬨',
'乱哄不过来' => '亂鬨不過來',
'了克制' => '了剋制',
-'事后' => '事後',
+'了然后' => '了然後',
'事情干脆' => '事情干脆',
'事有斗巧' => '事有鬥巧',
'事迹' => '事迹',
@@ -3189,10 +3251,11 @@ $zh2Hant = array(
'二出生' => '二出生',
'二出祁山' => '二出祁山',
'二出逃' => '二出逃',
-'二划' => '二劃',
'二只得' => '二只得',
+'二周后' => '二周後',
'二天后' => '二天後',
'二仑' => '二崙',
+'二年' => '二年',
'二缶钟惑' => '二缶鐘惑',
'二老板' => '二老板',
'二虎相斗' => '二虎相鬥',
@@ -3203,31 +3266,42 @@ $zh2Hant = array(
'于丹' => '于丹',
'于于' => '于于',
'于仁泰' => '于仁泰',
+'于仲文' => '于仲文',
'于佳卉' => '于佳卉',
+'于来山' => '于來山',
'于伟国' => '于偉國',
'于偉國' => '于偉國',
+'于光新' => '于光新',
'于光远' => '于光遠',
'于光遠' => '于光遠',
'于克-蘭多縣' => '于克-蘭多縣',
'于克-兰多县' => '于克-蘭多縣',
'于克勒' => '于克勒',
+'于再清' => '于再清',
'于冕' => '于冕',
+'于冠华' => '于冠華',
'于凌奎' => '于凌奎',
+'于凌辰' => '于凌辰',
'于勒' => '于勒',
'于化虎' => '于化虎',
'于占元' => '于占元',
+'于友泽' => '于友澤',
'于台煙' => '于台煙',
'于台烟' => '于台煙',
'于右任' => '于右任',
'于吉' => '于吉',
+'于和伟' => '于和偉',
'于品海' => '于品海',
'于国桢' => '于國楨',
'于國楨' => '于國楨',
+'于国治' => '于國治',
+'于國治' => '于國治',
'于坚' => '于堅',
'于堅' => '于堅',
'于大寶' => '于大寶',
'于大宝' => '于大寶',
'于天仁' => '于天仁',
+'于天龙' => '于天龍',
'于奇库杜克' => '于奇庫杜克',
'于奇庫杜克' => '于奇庫杜克',
'于姓' => '于姓',
@@ -3242,6 +3316,8 @@ $zh2Hant = array(
'于小伟' => '于小偉',
'于小偉' => '于小偉',
'于小彤' => '于小彤',
+'于小惠' => '于小惠',
+'于少保' => '于少保',
'于山' => '于山',
'于山国' => '于山國',
'于山國' => '于山國',
@@ -3258,6 +3334,7 @@ $zh2Hant = array(
'于德海' => '于德海',
'于志宁' => '于志寧',
'于志寧' => '于志寧',
+'于忠肃集' => '于忠肅集',
'于思' => '于思',
'于慎行' => '于慎行',
'于慧' => '于慧',
@@ -3269,8 +3346,8 @@ $zh2Hant = array(
'于敏中' => '于敏中',
'于斌' => '于斌',
'于斯塔德' => '于斯塔德',
-'于斯納爾斯貝里' => '于斯納爾斯貝里',
'于斯纳尔斯贝里' => '于斯納爾斯貝里',
+'于斯納爾斯貝里' => '于斯納爾斯貝里',
'于斯达尔' => '于斯達爾',
'于斯達爾' => '于斯達爾',
'于明涛' => '于明濤',
@@ -3283,9 +3360,13 @@ $zh2Hant = array(
'于根伟' => '于根偉',
'于根偉' => '于根偉',
'于格' => '于格',
+'于楓' => '于楓',
+'于枫' => '于楓',
+'于荣光' => '于榮光',
'于樂' => '于樂',
'于树洁' => '于樹潔',
'于樹潔' => '于樹潔',
+'于欣' => '于欣',
'于欣源' => '于欣源',
'于正升' => '于正昇',
'于正昇' => '于正昇',
@@ -3294,22 +3375,26 @@ $zh2Hant = array(
'于永波' => '于永波',
'于江震' => '于江震',
'于波' => '于波',
+'于洋' => '于洋',
'于洪区' => '于洪區',
'于洪區' => '于洪區',
'于浩威' => '于浩威',
+'于海' => '于海',
'于海洋' => '于海洋',
'于湘兰' => '于湘蘭',
'于湘蘭' => '于湘蘭',
'于漢超' => '于漢超',
'于汉超' => '于漢超',
+'于澄' => '于澄',
'于泽尔' => '于澤爾',
'于澤爾' => '于澤爾',
'于涛' => '于濤',
'于濤' => '于濤',
+'于熙珍' => '于熙珍',
'于爾岑' => '于爾岑',
'于尔岑' => '于爾岑',
-'于尔根' => '于爾根',
'于爾根' => '于爾根',
+'于尔根' => '于爾根',
'于尔里克' => '于爾里克',
'于爾里克' => '于爾里克',
'于特森' => '于特森',
@@ -3317,8 +3402,10 @@ $zh2Hant = array(
'于田' => '于田',
'于禁' => '于禁',
'于秀敏' => '于秀敏',
+'于立成' => '于立成',
'于素秋' => '于素秋',
'于美人' => '于美人',
+'于耘婕' => '于耘婕',
'于若木' => '于若木',
'于蔭霖' => '于蔭霖',
'于荫霖' => '于蔭霖',
@@ -3326,6 +3413,7 @@ $zh2Hant = array(
'于西翰' => '于西翰',
'于謙' => '于謙',
'于谦' => '于謙',
+'于谨' => '于謹',
'于貝爾' => '于貝爾',
'于贝尔' => '于貝爾',
'于赠' => '于贈',
@@ -3342,6 +3430,8 @@ $zh2Hant = array(
'于阗' => '于闐',
'于雙戈' => '于雙戈',
'于双戈' => '于雙戈',
+'于云鹤' => '于雲鶴',
+'于震' => '于震',
'于震寰' => '于震寰',
'于震环' => '于震環',
'于震環' => '于震環',
@@ -3352,11 +3442,15 @@ $zh2Hant = array(
'于风政' => '于風政',
'于風政' => '于風政',
'于飞' => '于飛',
+'于飛島' => '于飛島',
+'于飞岛' => '于飛島',
'于余曲折' => '于餘曲折',
+'于鬯' => '于鬯',
+'于魁智' => '于魁智',
'于凤桐' => '于鳳桐',
'于鳳桐' => '于鳳桐',
-'于鳳至' => '于鳳至',
'于凤至' => '于鳳至',
+'于鳳至' => '于鳳至',
'于默奥' => '于默奧',
'于默奧' => '于默奧',
'云乎' => '云乎',
@@ -3374,9 +3468,10 @@ $zh2Hant = array(
'五出生' => '五出生',
'五出祁山' => '五出祁山',
'五出逃' => '五出逃',
-'五划' => '五劃',
+'五周后' => '五周後',
'五天后' => '五天後',
'五岳' => '五嶽',
+'五年' => '五年',
'五谷' => '五穀',
'五扎' => '五紮',
'五行生克' => '五行生剋',
@@ -3416,38 +3511,44 @@ $zh2Hant = array(
'人参选' => '人參選',
'人参酌' => '人參酌',
'人参阅' => '人參閱',
-'人后' => '人後',
+'人如风后入江云' => '人如風後入江雲',
'人欲' => '人慾',
'人物志' => '人物誌',
'人参' => '人蔘',
'什锦面' => '什錦麵',
'什么' => '什麼',
'仇仇' => '仇讎',
-'今后' => '今後',
'他克制' => '他剋制',
'他钟' => '他鐘',
'付托' => '付託',
-'仙后座' => '仙后座',
+'仙后' => '仙后',
'仙药' => '仙藥',
'代码表' => '代碼表',
+'代表' => '代表',
'令人发指' => '令人髮指',
-'以后' => '以後',
'以自制' => '以自制',
'仰药' => '仰藥',
'件钟' => '件鐘',
+'任何表演' => '任何表演',
+'任何表示' => '任何表示',
+'任何表達' => '任何表達',
+'任何表达' => '任何表達',
'任何表' => '任何錶',
'任何钟' => '任何鐘',
'任何钟表' => '任何鐘錶',
'任教于' => '任教於',
'任于' => '任於',
'仿制' => '仿製',
-'企划' => '企劃',
'伊于湖底' => '伊于湖底',
'伊府面' => '伊府麵',
'伊斯兰教历' => '伊斯蘭教曆',
'伊斯兰教历史' => '伊斯蘭教歷史',
'伊斯兰历' => '伊斯蘭曆',
'伊斯兰历史' => '伊斯蘭歷史',
+'伊东怜' => '伊東怜',
+'伊尔汗历表' => '伊爾汗曆表',
+'伊达里子' => '伊達里子',
+'伊适杰' => '伊適杰',
'伊郁' => '伊鬱',
'伏几' => '伏几',
'伐罪吊民' => '伐罪弔民',
@@ -3712,8 +3813,17 @@ $zh2Hant = array(
'占x' => '佔x',
'占y' => '佔y',
'占z' => '佔z',
+'何杰' => '何杰',
+'余三胜' => '余三勝',
+'余三勝' => '余三勝',
'余光中' => '余光中',
'余光生' => '余光生',
+'余力為' => '余力為',
+'余力为' => '余力為',
+'余姓' => '余姓',
+'余威德' => '余威德',
+'余子明' => '余子明',
+'余思敏' => '余思敏',
'佛罗棱萨' => '佛羅稜薩',
'佛钟' => '佛鐘',
'作品里' => '作品裡',
@@ -3726,6 +3836,7 @@ $zh2Hant = array(
'佣金收益' => '佣金收益',
'佣金费用' => '佣金費用',
'佳肴' => '佳肴',
+'佳里鎮' => '佳里鎮',
'并一不二' => '併一不二',
'并入' => '併入',
'并兼' => '併兼',
@@ -3742,7 +3853,10 @@ $zh2Hant = array(
'并产' => '併產',
'并当' => '併當',
'并叠' => '併疊',
-'并发' => '併發',
+'并发型模式' => '併發型模式',
+'并发模式' => '併發模式',
+'并发症' => '併發症',
+'并发重症' => '併發重症',
'并科' => '併科',
'并网' => '併網',
'并线' => '併線',
@@ -3768,8 +3882,8 @@ $zh2Hant = array(
'保险柜' => '保險柜',
'信托贸易' => '信托貿易',
'信托' => '信託',
-'修改后' => '修改後',
'修杰楷' => '修杰楷',
+'修杰麟' => '修杰麟',
'修炼' => '修鍊',
'修胡刀' => '修鬍刀',
'俯冲' => '俯衝',
@@ -3790,7 +3904,6 @@ $zh2Hant = array(
'假托' => '假託',
'假发' => '假髮',
'偎干' => '偎乾',
-'偏后' => '偏後',
'做庄' => '做莊',
'停停当当' => '停停當當',
'停征' => '停徵',
@@ -3810,8 +3923,10 @@ $zh2Hant = array(
'佣懒' => '傭懶',
'佣书' => '傭書',
'佣金' => '傭金',
+'傲游' => '傲遊',
'傲霜斗雪' => '傲霜鬥雪',
'传位于四太子' => '傳位于四太子',
+'傳位于四太子' => '傳位于四太子',
'传于' => '傳於',
'伤痕累累' => '傷痕纍纍',
'傻里傻气' => '傻裡傻氣',
@@ -3824,6 +3939,7 @@ $zh2Hant = array(
'仆固怀恩' => '僕固懷恩',
'仆夫' => '僕夫',
'仆姑' => '僕姑',
+'仆婢' => '僕婢',
'仆妇' => '僕婦',
'仆射' => '僕射',
'仆少' => '僕少',
@@ -3837,6 +3953,7 @@ $zh2Hant = array(
'僮仆' => '僮僕',
'雇主' => '僱主',
'雇人' => '僱人',
+'雇佣' => '僱傭',
'雇到' => '僱到',
'雇员' => '僱員',
'雇工' => '僱工',
@@ -3845,8 +3962,10 @@ $zh2Hant = array(
'仪范' => '儀範',
'仪表' => '儀錶',
'亿个' => '億個',
+'亿周后' => '億周後',
'亿多只' => '億多隻',
'亿天后' => '億天後',
+'亿年' => '億年',
'亿只' => '億隻',
'亿余' => '億餘',
'俭仆' => '儉僕',
@@ -3895,19 +4014,7 @@ $zh2Hant = array(
'凶相' => '兇相',
'凶险' => '兇險',
'先占' => '先佔',
-'先后' => '先後',
-'先忧后乐' => '先憂後樂',
'先采' => '先採',
-'先攻后守' => '先攻後守',
-'先盛后衰' => '先盛後衰',
-'先礼后兵' => '先禮後兵',
-'先义后利' => '先義後利',
-'先声后实' => '先聲後實',
-'先苦后甘' => '先苦後甘',
-'先赢后输' => '先贏後輸',
-'先进后出' => '先進後出',
-'先开花后结果' => '先開花後結果',
-'光前裕后' => '光前裕後',
'光致致' => '光緻緻',
'克药' => '克藥',
'克复' => '克複',
@@ -3916,8 +4023,8 @@ $zh2Hant = array(
'党太尉' => '党太尉',
'党怀英' => '党懷英',
'党进' => '党進',
+'党項' => '党項',
'党项' => '党項',
-'入夜后' => '入夜後',
'内制' => '內製',
'内面包' => '內面包',
'内面包的' => '內面包的',
@@ -3927,8 +4034,10 @@ $zh2Hant = array(
'全面包围' => '全面包圍',
'全面包裹' => '全面包裹',
'两个' => '兩個',
+'两周后' => '兩周後',
'两天后' => '兩天後',
'两天晒网' => '兩天晒網',
+'两年' => '兩年',
'两扎' => '兩紮',
'两虎共斗' => '兩虎共鬥',
'两只' => '兩隻',
@@ -3941,16 +4050,17 @@ $zh2Hant = array(
'八出生' => '八出生',
'八出祁山' => '八出祁山',
'八出逃' => '八出逃',
+'八周后' => '八周後',
'八大胡同' => '八大胡同',
'八天后' => '八天後',
'八字胡' => '八字鬍',
+'八年' => '八年',
'八扎' => '八紮',
'八蜡' => '八蜡',
'八只' => '八隻',
'八余' => '八餘',
'公仔面' => '公仔麵',
'公仆' => '公僕',
-'公元后' => '公元後',
'公孙丑' => '公孫丑',
'公干' => '公幹',
'公历' => '公曆',
@@ -3964,8 +4074,9 @@ $zh2Hant = array(
'六出生' => '六出生',
'六出祁山' => '六出祁山',
'六出逃' => '六出逃',
-'六划' => '六劃',
+'六周后' => '六周後',
'六天后' => '六天後',
+'六年' => '六年',
'六谷' => '六穀',
'六扎' => '六紮',
'六冲' => '六衝',
@@ -3977,7 +4088,6 @@ $zh2Hant = array(
'其一只' => '其一只',
'其二只' => '其二只',
'其八九只' => '其八九只',
-'其后' => '其後',
'其次辟地' => '其次辟地',
'其余' => '其餘',
'典范' => '典範',
@@ -3994,6 +4104,7 @@ $zh2Hant = array(
'冷庄子' => '冷莊子',
'冷面相' => '冷面相',
'冷面' => '冷麵',
+'准三后' => '准三后',
'准不准他' => '准不准他',
'准不准你' => '准不准你',
'准不准她' => '准不准她',
@@ -4001,8 +4112,10 @@ $zh2Hant = array(
'准不准我' => '准不准我',
'准不准许' => '准不准許',
'准不准谁' => '准不准誰',
+'准保護' => '准保護',
'准保护' => '准保護',
'准保释' => '准保釋',
+'准保釋' => '准保釋',
'凌蒙初' => '凌濛初',
'凝炼' => '凝鍊',
'几上' => '几上',
@@ -4025,7 +4138,6 @@ $zh2Hant = array(
'出乖露丑' => '出乖露醜',
'出征收' => '出征收',
'出于' => '出於',
-'出谋划策' => '出謀劃策',
'出游' => '出遊',
'出丑' => '出醜',
'出锤' => '出鎚',
@@ -4034,6 +4146,7 @@ $zh2Hant = array(
'分半钟' => '分半鐘',
'分多钟' => '分多鐘',
'分子钟' => '分子鐘',
+'分子云' => '分子雲',
'分布圖' => '分布圖',
'分布图' => '分布圖',
'分布于' => '分布於',
@@ -4041,16 +4154,40 @@ $zh2Hant = array(
'分钟' => '分鐘',
'刑余' => '刑餘',
'划一桨' => '划一槳',
+'划上' => '划上',
+'划下' => '划下',
+'划不来' => '划不來',
+'划了' => '划了',
'划了一会' => '划了一會',
'划来划去' => '划來划去',
+'划具' => '划具',
+'划出' => '划出',
+'划到' => '划到',
'划到岸' => '划到岸',
'划到江心' => '划到江心',
+'划动' => '划動',
+'划去' => '划去',
+'划子' => '划子',
'划得来' => '划得來',
+'划拳' => '划拳',
+'划桨' => '划槳',
+'划水' => '划水',
+'划算' => '划算',
+'划船' => '划船',
+'划艇' => '划艇',
'划着' => '划著',
'划着走' => '划著走',
+'划行' => '划行',
+'划走' => '划走',
+'划起' => '划起',
+'划进' => '划進',
+'划进来' => '划進來',
+'划进去' => '划進去',
+'划过' => '划過',
+'划过来' => '划過來',
+'划过去' => '划過去',
'划龙舟' => '划龍舟',
'判断发' => '判斷發',
-'别后' => '別後',
'别日南鸿才北去' => '別日南鴻纔北去',
'别致' => '別緻',
'别庄' => '別莊',
@@ -4059,8 +4196,6 @@ $zh2Hant = array(
'利欲' => '利慾',
'利于' => '利於',
'利欲熏心' => '利欲熏心',
-'删后留位' => '刪後留位',
-'删后缩位' => '刪後縮位',
'刮来刮去' => '刮來刮去',
'刮着' => '刮著',
'刮起来' => '刮起來',
@@ -4070,7 +4205,6 @@ $zh2Hant = array(
'制签' => '制籤',
'制钟' => '制鐘',
'刺绣' => '刺繡',
-'刻划' => '刻劃',
'刻半钟' => '刻半鐘',
'刻多钟' => '刻多鐘',
'刻钟' => '刻鐘',
@@ -4086,15 +4220,7 @@ $zh2Hant = array(
'克期' => '剋期',
'克死' => '剋死',
'克薄' => '剋薄',
-'前仰后合' => '前仰後合',
-'前倨后恭' => '前倨後恭',
-'前前后后' => '前前後後',
-'前呼后拥' => '前呼後擁',
-'前后' => '前後',
-'前思后想' => '前思後想',
-'前挽后推' => '前挽後推',
-'前短后长' => '前短後長',
-'前言不对后语' => '前言不對後語',
+'前往' => '前往',
'前言不答后语' => '前言不答後語',
'前面店' => '前面店',
'剔庄货' => '剔莊貨',
@@ -4115,36 +4241,13 @@ $zh2Hant = array(
'铲平' => '剷平',
'铲除' => '剷除',
'铲头' => '剷頭',
-'划一' => '劃一',
-'划上' => '劃上',
-'划下' => '劃下',
-'划了' => '劃了',
-'划出' => '劃出',
-'划分' => '劃分',
-'划到' => '劃到',
-'划划' => '劃劃',
-'划去' => '劃去',
-'划在' => '劃在',
-'划地' => '劃地',
-'划定' => '劃定',
-'划得' => '劃得',
-'划成' => '劃成',
-'划掉' => '劃掉',
-'划拨' => '劃撥',
-'划时代' => '劃時代',
-'划款' => '劃款',
-'划归' => '劃歸',
-'划法' => '劃法',
-'划清' => '劃清',
+'划入' => '劃入',
'划为' => '劃為',
-'划界' => '劃界',
-'划破' => '劃破',
-'划线' => '劃線',
-'划足' => '劃足',
-'划过' => '劃過',
-'划开' => '劃開',
'剧药' => '劇藥',
+'刘佳怜' => '劉佳怜',
+'劉佳怜' => '劉佳怜',
'刘克庄' => '劉克莊',
+'刘芸后' => '劉芸后',
'力克制' => '力剋制',
'力拼' => '力拚',
'力拼众敌' => '力拼眾敵',
@@ -4156,7 +4259,6 @@ $zh2Hant = array(
'加注' => '加註',
'劣于' => '劣於',
'助于' => '助於',
-'劫后余生' => '劫後餘生',
'劫余' => '劫餘',
'勃郁' => '勃鬱',
'动荡' => '動蕩',
@@ -4181,7 +4283,6 @@ $zh2Hant = array(
'匡复' => '匡複',
'匪干' => '匪幹',
'匿于' => '匿於',
-'区划' => '區劃',
'十个' => '十個',
'十出刊' => '十出刊',
'十出口' => '十出口',
@@ -4189,21 +4290,26 @@ $zh2Hant = array(
'十出生' => '十出生',
'十出祁山' => '十出祁山',
'十出逃' => '十出逃',
-'十划' => '十劃',
+'十周后' => '十周後',
'十多只' => '十多隻',
'十天后' => '十天後',
+'十年' => '十年',
'十扎' => '十紮',
'十只' => '十隻',
'十余' => '十餘',
'十出' => '十齣',
'千个' => '千個',
'千只可' => '千只可',
+'千只夠' => '千只夠',
'千只够' => '千只夠',
'千只怕' => '千只怕',
'千只能' => '千只能',
'千只足够' => '千只足夠',
+'千只足夠' => '千只足夠',
+'千周后' => '千周後',
'千多只' => '千多隻',
'千天后' => '千天後',
+'千年' => '千年',
'千扎' => '千紮',
'千丝万缕' => '千絲萬縷',
'千回百折' => '千迴百折',
@@ -4212,12 +4318,13 @@ $zh2Hant = array(
'千只' => '千隻',
'千余' => '千餘',
'升官发财' => '升官發財',
-'午后' => '午後',
'半制品' => '半制品',
'半只可' => '半只可',
'半只够' => '半只夠',
'半于' => '半於',
'半只' => '半隻',
+'协调' => '協調',
+'协防' => '協防',
'南京钟' => '南京鐘',
'南京钟表' => '南京鐘錶',
'南宫适' => '南宮适',
@@ -4229,6 +4336,7 @@ $zh2Hant = array(
'南游' => '南遊',
'博汇' => '博彙',
'博采' => '博採',
+'卜云吉' => '卜云吉',
'卞庄' => '卞莊',
'卞庄子' => '卞莊子',
'占了卜' => '占了卜',
@@ -4247,7 +4355,6 @@ $zh2Hant = array(
'原子钟' => '原子鐘',
'原钟' => '原鐘',
'历物之意' => '厤物之意',
-'厥后' => '厥後',
'参合' => '參合',
'参考价值' => '參考價值',
'参与' => '參與',
@@ -4293,9 +4400,12 @@ $zh2Hant = array(
'只占算' => '只占算',
'只采' => '只採',
'只冲' => '只衝',
+'只要功夫深,铁杵磨成锈花针' => '只要功夫深,鐵杵磨成鏽花針',
'只身上已' => '只身上已',
'只身上有' => '只身上有',
+'只身上沒' => '只身上沒',
'只身上没' => '只身上沒',
+'只身上無' => '只身上無',
'只身上无' => '只身上無',
'只身上的' => '只身上的',
'只身世' => '只身世',
@@ -4306,14 +4416,18 @@ $zh2Hant = array(
'只身形' => '只身形',
'只身影' => '只身影',
'只身后' => '只身後',
+'只身後' => '只身後',
'只身心' => '只身心',
'只身旁' => '只身旁',
'只身材' => '只身材',
'只身段' => '只身段',
'只身为' => '只身為',
+'只身為' => '只身為',
'只身边' => '只身邊',
+'只身邊' => '只身邊',
'只身首' => '只身首',
'只身体' => '只身體',
+'只身體' => '只身體',
'只身高' => '只身高',
'只采声' => '只采聲',
'叮叮当当' => '叮叮噹噹',
@@ -4324,7 +4438,6 @@ $zh2Hant = array(
'台子女' => '台子女',
'台子孙' => '台子孫',
'台布景' => '台布景',
-'台后' => '台後',
'台历史' => '台歷史',
'台钟' => '台鐘',
'台面前' => '台面前',
@@ -4336,9 +4449,10 @@ $zh2Hant = array(
'叱咤咤' => '叱咤咤',
'叱咤乐坛' => '叱咤樂壇',
'叱咤樂壇' => '叱咤樂壇',
-'右后' => '右後',
'叶 恭弘' => '叶 恭弘',
'叶 恭弘' => '叶 恭弘',
+'叶不二子' => '叶不二子',
+'叶志穗' => '叶志穗',
'叶恭弘' => '叶恭弘',
'叶音' => '叶音',
'叶韵' => '叶韻',
@@ -4346,7 +4460,6 @@ $zh2Hant = array(
'吃着不尽' => '吃著不盡',
'吃姜' => '吃薑',
'吃药' => '吃藥',
-'吃药后' => '吃藥後',
'吃里扒外' => '吃裡扒外',
'吃里爬外' => '吃裡爬外',
'吃辣面' => '吃辣麵',
@@ -4374,16 +4487,35 @@ $zh2Hant = array(
'同伙' => '同夥',
'同于' => '同於',
'同余' => '同餘',
+'后冠' => '后冠',
+'后北街' => '后北街',
+'后土' => '后土',
+'后妃' => '后妃',
+'后安路' => '后安路',
+'后平路' => '后平路',
+'后座' => '后座',
+'后海湾' => '后海灣',
+'后海灣' => '后海灣',
+'后瑞站' => '后瑞站',
+'后稷' => '后稷',
+'后羿' => '后羿',
+'后街' => '后街',
+'后角' => '后角',
'后丰' => '后豐',
'后豐' => '后豐',
+'后里' => '后里',
+'后发FK型星' => '后髮FK型星',
+'后髮FK型星' => '后髮FK型星',
+'后髮座' => '后髮座',
'后发座' => '后髮座',
+'后发星系团' => '后髮星系團',
+'后髮星系團' => '后髮星系團',
'吐哺捉发' => '吐哺捉髮',
'吐哺握发' => '吐哺握髮',
'向往来' => '向往來',
'向往常' => '向往常',
'向往日' => '向往日',
'向往时' => '向往時',
-'向后' => '向後',
'向着' => '向著',
'吞并' => '吞併',
'吟游' => '吟遊',
@@ -4392,8 +4524,11 @@ $zh2Hant = array(
'吹发' => '吹髮',
'吹胡' => '吹鬍',
'吾为之范我驰驱' => '吾爲之範我馳驅',
+'吕后' => '呂后',
+'呂后' => '呂后',
'呆呆傻傻' => '呆呆傻傻',
'呆呆挣挣' => '呆呆掙掙',
+'呆呆獸' => '呆呆獸',
'呆呆兽' => '呆呆獸',
'呆呆笨笨' => '呆呆笨笨',
'呆致致' => '呆緻緻',
@@ -4403,6 +4538,7 @@ $zh2Hant = array(
'周二' => '周二',
'周五' => '周五',
'周六' => '周六',
+'周后' => '周后',
'周四' => '周四',
'周历' => '周曆',
'周杰伦' => '周杰倫',
@@ -4429,16 +4565,15 @@ $zh2Hant = array(
'唁吊' => '唁弔',
'呗赞' => '唄讚',
'唇干' => '唇乾',
-'售后' => '售後',
'唯一只' => '唯一只',
'唱游' => '唱遊',
'唾面自干' => '唾面自乾',
'唾余' => '唾餘',
'商历' => '商曆',
'商历史' => '商歷史',
+'启发式' => '啟發式',
'啷当' => '啷噹',
'喂了一声' => '喂了一聲',
-'善后' => '善後',
'善于' => '善於',
'喜向往' => '喜向往',
'喜欢表' => '喜歡錶',
@@ -4452,9 +4587,7 @@ $zh2Hant = array(
'单单于' => '單單於',
'单干' => '單幹',
'单打独斗' => '單打獨鬥',
-'单只' => '單隻',
'嗑药' => '嗑藥',
-'嗣后' => '嗣後',
'嘀嗒的表' => '嘀嗒的錶',
'嘉谷' => '嘉穀',
'嘉肴' => '嘉肴',
@@ -4469,6 +4602,7 @@ $zh2Hant = array(
'向往' => '嚮往',
'向应' => '嚮應',
'向迩' => '嚮邇',
+'严云农' => '嚴云農',
'严于' => '嚴於',
'严丝合缝' => '嚴絲合縫',
'嚼谷' => '嚼穀',
@@ -4485,8 +4619,11 @@ $zh2Hant = array(
'四出逃' => '四出逃',
'四分历' => '四分曆',
'四分历史' => '四分歷史',
+'四周后' => '四周後',
'四天后' => '四天後',
+'四年' => '四年',
'四舍五入' => '四捨五入',
+'四舍六入' => '四捨六入',
'四扎' => '四紮',
'四只' => '四隻',
'四面包' => '四面包',
@@ -4507,6 +4644,7 @@ $zh2Hant = array(
'困兽之斗' => '困獸之鬥',
'困兽犹斗' => '困獸猶鬥',
'困斗' => '困鬥',
+'固定制' => '固定制',
'固征' => '固徵',
'囿于' => '囿於',
'圈占' => '圈佔',
@@ -4520,6 +4658,7 @@ $zh2Hant = array(
'国历任' => '國歷任',
'国历史' => '國歷史',
'国历届' => '國歷屆',
+'国历经' => '國歷經',
'国仇' => '國讎',
'园里' => '園裡',
'园游会' => '園遊會',
@@ -4530,10 +4669,10 @@ $zh2Hant = array(
'土霉素' => '土霉素',
'在制品' => '在制品',
'在克制' => '在剋制',
-'在后' => '在後',
'在于' => '在於',
'地占' => '地佔',
'地克制' => '地剋制',
+'地心历表' => '地心曆表',
'地方志' => '地方志',
'地志' => '地誌',
'地丑德齐' => '地醜德齊',
@@ -4552,11 +4691,15 @@ $zh2Hant = array(
'型范' => '型範',
'埃及历' => '埃及曆',
'埃及历史' => '埃及歷史',
+'埃及艳后' => '埃及豔后',
'埃荣冲' => '埃榮衝',
'埋头寻表' => '埋頭尋錶',
'埋头寻钟' => '埋頭尋鐘',
'埋头寻钟表' => '埋頭尋鐘錶',
'城里' => '城裡',
+'埔裡社撫墾局' => '埔裏社撫墾局',
+'埔裏社撫墾局' => '埔裏社撫墾局',
+'埔里社抚垦局' => '埔裏社撫墾局',
'基干' => '基幹',
'基于' => '基於',
'基准' => '基準',
@@ -4583,6 +4726,8 @@ $zh2Hant = array(
'壶里' => '壺裡',
'壸范' => '壼範',
'寿面' => '壽麵',
+'夏于乔' => '夏于喬',
+'夏于喬' => '夏于喬',
'夏天里' => '夏天裡',
'夏日里' => '夏日裡',
'夏历' => '夏曆',
@@ -4591,15 +4736,17 @@ $zh2Hant = array(
'外强中干' => '外強中乾',
'外制' => '外製',
'多占' => '多佔',
-'多划' => '多劃',
'多半只' => '多半只',
'多只可' => '多只可',
'多只在' => '多只在',
'多只是' => '多只是',
+'多只會' => '多只會',
'多只会' => '多只會',
'多只有' => '多只有',
+'多只用' => '多只用',
'多只能' => '多只能',
'多只需' => '多只需',
+'多周后' => '多周後',
'多天后' => '多天後',
'多于' => '多於',
'多冲' => '多衝',
@@ -4611,6 +4758,7 @@ $zh2Hant = array(
'夜里' => '夜裡',
'夜游' => '夜遊',
'够克制' => '夠剋制',
+'夢有五不占' => '夢有五不占',
'梦有五不占' => '夢有五不占',
'梦里' => '夢裡',
'梦游' => '夢遊',
@@ -4628,6 +4776,7 @@ $zh2Hant = array(
'大只有' => '大只有',
'大只能' => '大只能',
'大只需' => '大只需',
+'大周后' => '大周后',
'大型钟' => '大型鐘',
'大型钟表面' => '大型鐘表面',
'大型钟表' => '大型鐘錶',
@@ -4657,22 +4806,29 @@ $zh2Hant = array(
'大锤' => '大鎚',
'大钟' => '大鐘',
'大只' => '大隻',
+'大风后' => '大風後',
'大曲' => '大麴',
'天干物燥' => '天乾物燥',
'天克地冲' => '天克地衝',
+'天后' => '天后',
'天后宫' => '天后宮',
-'天后庙道' => '天后廟道',
'天地志狼' => '天地志狼',
'天地为范' => '天地為範',
'天干地支' => '天干地支',
-'天后' => '天後',
+'天后来' => '天後來',
+'天后半' => '天後半',
+'天后天' => '天後天',
'天文学钟' => '天文學鐘',
+'天文历表' => '天文曆表',
'天文钟' => '天文鐘',
+'天历' => '天曆',
+'天历史' => '天歷史',
'天翻地覆' => '天翻地覆',
'天覆地载' => '天覆地載',
'太仆' => '太僕',
'太初历' => '太初曆',
'太初历史' => '太初歷史',
+'太后' => '太后',
'夯干' => '夯幹',
'夸人' => '夸人',
'夸克' => '夸克',
@@ -4686,7 +4842,7 @@ $zh2Hant = array(
'夸诞' => '夸誕',
'夸诞不经' => '夸誕不經',
'夸丽' => '夸麗',
-'奇迹' => '奇迹',
+'奇迹' => '奇蹟',
'奇丑' => '奇醜',
'奏折' => '奏摺',
'奥占' => '奧佔',
@@ -4707,6 +4863,7 @@ $zh2Hant = array(
'好斗笠' => '好斗笠',
'好斗篷' => '好斗篷',
'好斗胆' => '好斗膽',
+'好斗膽' => '好斗膽',
'好斗蓬' => '好斗蓬',
'好于' => '好於',
'好呆' => '好獃',
@@ -4716,6 +4873,7 @@ $zh2Hant = array(
'好斗' => '好鬥',
'如果干' => '如果幹',
'如饥似渴' => '如饑似渴',
+'妖后' => '妖后',
'妙药' => '妙藥',
'始于' => '始於',
'委托' => '委託',
@@ -4732,7 +4890,6 @@ $zh2Hant = array(
'奸细' => '姦細',
'奸邪' => '姦邪',
'威棱' => '威稜',
-'婚后' => '婚後',
'婢仆' => '婢僕',
'娲杆' => '媧杆',
'嫁祸于' => '嫁禍於',
@@ -4749,17 +4906,13 @@ $zh2Hant = array(
'存十一于千百' => '存十一於千百',
'存折' => '存摺',
'存于' => '存於',
-'季后赛' => '季後賽',
'孤寡不谷' => '孤寡不穀',
'学里' => '學裡',
'宇宙志' => '宇宙誌',
-'守先待后' => '守先待後',
'安于' => '安於',
'安沈铁路' => '安瀋鐵路',
'安眠药' => '安眠藥',
'安胎药' => '安胎藥',
-'完工后' => '完工後',
-'完成后' => '完成後',
'宗周钟' => '宗周鐘',
'官不怕大只怕管' => '官不怕大只怕管',
'官地为采' => '官地為寀',
@@ -4784,9 +4937,9 @@ $zh2Hant = array(
'家庄' => '家莊',
'家里' => '家裡',
'家丑' => '家醜',
-'容后说明' => '容後說明',
'容于' => '容於',
'容范' => '容範',
+'宿舍' => '宿舍',
'寄托在' => '寄托在',
'寄托' => '寄託',
'密致' => '密緻',
@@ -4814,6 +4967,7 @@ $zh2Hant = array(
'宝里宝气' => '寶裡寶氣',
'寸发千金' => '寸髮千金',
'寺钟' => '寺鐘',
+'封后' => '封后',
'封面里' => '封面裡',
'射雕' => '射鵰',
'将占' => '將佔',
@@ -4821,6 +4975,7 @@ $zh2Hant = array(
'专向往' => '專向往',
'专注' => '專註',
'专辑里' => '專輯裡',
+'尊后' => '尊后',
'对折' => '對摺',
'对于' => '對於',
'对准' => '對準',
@@ -4831,8 +4986,10 @@ $zh2Hant = array(
'对表中' => '對表中',
'对表扬' => '對表揚',
'对表明' => '對表明',
+'对表格' => '對表格',
'对表演' => '對表演',
'对表现' => '對表現',
+'对表示' => '對表示',
'对表达' => '對表達',
'对表' => '對錶',
'导游' => '導遊',
@@ -4847,6 +5004,7 @@ $zh2Hant = array(
'小只有' => '小只有',
'小只能' => '小只能',
'小只需' => '小只需',
+'小周后' => '小周后',
'小型钟' => '小型鐘',
'小型钟表面' => '小型鐘表面',
'小型钟表' => '小型鐘錶',
@@ -4862,8 +5020,11 @@ $zh2Hant = array(
'尸位素餐' => '尸位素餐',
'尸利' => '尸利',
'尸居余气' => '尸居餘氣',
+'尸弃佛' => '尸棄佛',
'尸祝' => '尸祝',
'尸禄' => '尸祿',
+'尸罗精舍' => '尸羅精舍',
+'尸羅精舍' => '尸羅精舍',
'尸臣' => '尸臣',
'尸谏' => '尸諫',
'尸魂界' => '尸魂界',
@@ -4873,6 +5034,7 @@ $zh2Hant = array(
'屋子里' => '屋子裡',
'屋梁' => '屋樑',
'屋里' => '屋裡',
+'屏风后' => '屏風後',
'屑于' => '屑於',
'屡顾尔仆' => '屢顧爾僕',
'属于' => '屬於',
@@ -4881,7 +5043,6 @@ $zh2Hant = array(
'屯里' => '屯裡',
'山崩钟应' => '山崩鐘應',
'山岳' => '山嶽',
-'山后' => '山後',
'山梁' => '山樑',
'山洞里' => '山洞裡',
'山棱' => '山稜',
@@ -4889,6 +5050,7 @@ $zh2Hant = array(
'山庄' => '山莊',
'山药' => '山藥',
'山里' => '山裡',
+'山谷道' => '山谷道',
'山重水复' => '山重水複',
'岱岳' => '岱嶽',
'峰回' => '峰迴',
@@ -4911,7 +5073,6 @@ $zh2Hant = array(
'巡回' => '巡迴',
'巡游' => '巡遊',
'工致' => '工緻',
-'左后' => '左後',
'左冲右突' => '左衝右突',
'巧妇做不得无面馎饦' => '巧婦做不得無麵餺飥',
'巧干' => '巧幹',
@@ -4937,12 +5098,12 @@ $zh2Hant = array(
'希伯来历史' => '希伯來歷史',
'帘子' => '帘子',
'帘布' => '帘布',
+'帝后台' => '帝后臺',
'师范' => '師範',
'席卷' => '席捲',
'带团参加' => '帶團參加',
'带征' => '帶徵',
'带发修行' => '帶髮修行',
-'幕后' => '幕後',
'帮佣' => '幫傭',
'干系' => '干係',
'干着急' => '干著急',
@@ -4950,7 +5111,6 @@ $zh2Hant = array(
'平泉庄' => '平泉莊',
'平准' => '平準',
'年代里' => '年代裡',
-'年后' => '年後',
'年历' => '年曆',
'年历史' => '年歷史',
'年谷' => '年穀',
@@ -4959,7 +5119,6 @@ $zh2Hant = array(
'并吞' => '并吞',
'并州' => '并州',
'并日而食' => '并日而食',
-'并行' => '并行',
'并迭' => '并迭',
'幸免于难' => '幸免於難',
'幸于' => '幸於',
@@ -5019,7 +5178,8 @@ $zh2Hant = array(
'干革命' => '幹革命',
'干头' => '幹頭',
'干么' => '幹麼',
-'几划' => '幾劃',
+'几个' => '幾個',
+'几周后' => '幾周後',
'几天后' => '幾天後',
'几只' => '幾隻',
'几出' => '幾齣',
@@ -5036,17 +5196,17 @@ $zh2Hant = array(
'府干預' => '府干預',
'府干预' => '府干預',
'府干' => '府幹',
-'府后' => '府後',
'座钟' => '座鐘',
'康庄大道' => '康庄大道',
-'康采恩' => '康採恩',
'康庄' => '康莊',
'厨余' => '廚餘',
'厮斗' => '廝鬥',
'庙里' => '廟裡',
+'废后' => '廢后',
+'廢后' => '廢后',
'广征' => '廣徵',
'广舍' => '廣捨',
-'延后' => '延後',
+'延历' => '延曆',
'建于' => '建於',
'弄干' => '弄乾',
'弄丑' => '弄醜',
@@ -5082,6 +5242,7 @@ $zh2Hant = array(
'吊书' => '弔書',
'吊桥' => '弔橋',
'吊死' => '弔死',
+'吊死问孤' => '弔死問孤',
'吊死问疾' => '弔死問疾',
'吊民' => '弔民',
'吊民伐罪' => '弔民伐罪',
@@ -5107,6 +5268,7 @@ $zh2Hant = array(
'张三丰' => '張三丰',
'張三丰' => '張三丰',
'张勋' => '張勳',
+'张乐于张徐' => '張樂于張徐',
'强占' => '強佔',
'强制作用' => '強制作用',
'强奸' => '強姦',
@@ -5131,10 +5293,11 @@ $zh2Hant = array(
'形单影只' => '形單影隻',
'形影相吊' => '形影相弔',
'形于' => '形於',
+'彭于晏' => '彭于晏',
+'影后' => '影后',
'仿佛' => '彷彿',
'役于' => '役於',
'彼此克制' => '彼此剋制',
-'往后' => '往後',
'往日無仇' => '往日無讎',
'往里' => '往裡',
'往复' => '往複',
@@ -5142,180 +5305,10 @@ $zh2Hant = array(
'很凶' => '很兇',
'很丑' => '很醜',
'律历志' => '律曆志',
-'后上' => '後上',
-'后下' => '後下',
-'后世' => '後世',
-'后主' => '後主',
-'后事' => '後事',
-'后人' => '後人',
-'后代' => '後代',
-'后仰' => '後仰',
-'后件' => '後件',
-'后任' => '後任',
-'后作' => '後作',
-'后来' => '後來',
-'后偏' => '後偏',
-'后备' => '後備',
-'后传' => '後傳',
-'后分' => '後分',
-'后到' => '後到',
-'后力不继' => '後力不繼',
-'后劲' => '後勁',
-'后勤' => '後勤',
-'后区' => '後區',
-'后半' => '後半',
'后印' => '後印',
-'后厝路' => '後厝路',
-'后去' => '後去',
-'后台' => '後台',
'后台老板' => '後台老板',
-'后向' => '後向',
-'后周' => '後周',
-'后唐' => '後唐',
-'后嗣' => '後嗣',
-'后园' => '後園',
-'后图' => '後圖',
-'后土' => '後土',
-'后埔' => '後埔',
-'后堂' => '後堂',
-'后尘' => '後塵',
-'后壁' => '後壁',
-'后天' => '後天',
-'后夫' => '後夫',
-'后奏' => '後奏',
-'后妻' => '後妻',
-'后娘' => '後娘',
-'后妇' => '後婦',
-'后学' => '後學',
-'后宫' => '後宮',
-'后山' => '後山',
-'后巷' => '後巷',
-'后市' => '後市',
-'后年' => '後年',
-'后几' => '後幾',
'后庄' => '後庄',
-'后序' => '後序',
-'后座' => '後座',
-'后庭' => '後庭',
-'后悔' => '後悔',
-'后患' => '後患',
-'后房' => '後房',
-'后手' => '後手',
-'后排' => '後排',
-'后掠角' => '後掠角',
-'后接' => '後接',
-'后援' => '後援',
-'后撤' => '後撤',
-'后攻' => '後攻',
-'后放' => '後放',
-'后效' => '後效',
-'后文' => '後文',
-'后方' => '後方',
-'后于' => '後於',
-'后日' => '後日',
-'后昌路' => '後昌路',
-'后晋' => '後晉',
-'后晌' => '後晌',
-'后晚' => '後晚',
-'后景' => '後景',
-'后会' => '後會',
-'后有' => '後有',
-'后望镜' => '後望鏡',
-'后期' => '後期',
-'后果' => '後果',
-'后桅' => '後桅',
-'后梁' => '後梁',
-'后桥' => '後橋',
-'后步' => '後步',
-'后段' => '後段',
-'后殿' => '後殿',
-'后母' => '後母',
-'后派' => '後派',
-'后浪' => '後浪',
-'后凉' => '後涼',
-'后港' => '後港',
-'后汉' => '後漢',
-'后为' => '後為',
-'后无来者' => '後無來者',
-'后照镜' => '後照鏡',
-'后燕' => '後燕',
-'后父' => '後父',
-'后现代' => '後現代',
-'后生' => '後生',
-'后用' => '後用',
-'后由' => '後由',
-'后盾' => '後盾',
-'后知' => '後知',
-'后知后觉' => '後知後覺',
-'后福' => '後福',
-'后秃' => '後禿',
-'后秦' => '後秦',
-'后空翻' => '後空翻',
-'后窗' => '後窗',
-'后站' => '後站',
-'后端' => '後端',
-'后竹围' => '後竹圍',
-'后节' => '後節',
-'后篇' => '後篇',
-'后缀' => '後綴',
-'后继' => '後繼',
-'后续' => '後續',
-'后置' => '後置',
-'后者' => '後者',
-'后肢' => '後肢',
-'后背' => '後背',
-'后脑' => '後腦',
-'后脚' => '後腳',
-'后腿' => '後腿',
-'后膛' => '後膛',
-'后花园' => '後花園',
-'后菜园' => '後菜園',
-'后叶' => '後葉',
-'后行' => '後行',
-'后街' => '後街',
-'后卫' => '後衛',
-'后裔' => '後裔',
-'后补' => '後補',
-'后䙓' => '後襬',
-'后视镜' => '後視鏡',
-'后言' => '後言',
-'后计' => '後計',
-'后记' => '後記',
-'后设' => '後設',
-'后读' => '後讀',
-'后走' => '後走',
-'后起' => '後起',
-'后赵' => '後趙',
-'后足' => '後足',
-'后跟' => '後跟',
-'后路' => '後路',
-'后身' => '後身',
-'后车' => '後車',
-'后辈' => '後輩',
-'后轮' => '後輪',
-'后转' => '後轉',
-'后述' => '後述',
-'后退' => '後退',
-'后送' => '後送',
-'后进' => '後進',
-'后过' => '後過',
-'后遗症' => '後遺症',
-'后边' => '後邊',
-'后部' => '後部',
-'后镜' => '後鏡',
-'后门' => '後門',
-'后防' => '後防',
-'后院' => '後院',
-'后集' => '後集',
-'后面' => '後面',
'后面店' => '後面店',
-'后项' => '後項',
-'后头' => '後頭',
-'后颈' => '後頸',
-'后顾' => '後顧',
-'后魏' => '後魏',
-'后点' => '後點',
-'后龙' => '後龍',
'徐干' => '徐幹',
'徒托空言' => '徒託空言',
'得克制' => '得剋制',
@@ -5323,6 +5316,7 @@ $zh2Hant = array(
'从里到外' => '從裡到外',
'从里向外' => '從裡向外',
'复始' => '復始',
+'复活节历表' => '復活節曆表',
'征人' => '徵人',
'征令' => '徵令',
'征占' => '徵佔',
@@ -5508,13 +5502,10 @@ $zh2Hant = array(
'快克制' => '快剋制',
'快快当当' => '快快當當',
'快冲' => '快衝',
-'忽前忽后' => '忽前忽後',
'怎么' => '怎麼',
'怎么着' => '怎麼著',
'怒于' => '怒於',
'怒发冲冠' => '怒髮衝冠',
-'思前思后' => '思前思後',
-'思前想后' => '思前想後',
'思如泉涌' => '思如泉湧',
'怠于' => '怠於',
'急于' => '急於',
@@ -5524,9 +5515,7 @@ $zh2Hant = array(
'怪里怪气' => '怪裡怪氣',
'怫郁' => '怫鬱',
'恂栗' => '恂慄',
-'恒生指数' => '恒生指數',
-'恒生股价指数' => '恒生股價指數',
-'恒生银行' => '恒生銀行',
+'恒生' => '恒生',
'恕乏价催' => '恕乏价催',
'息交绝游' => '息交絕遊',
'息谷' => '息穀',
@@ -5585,11 +5574,11 @@ $zh2Hant = array(
'蒙懂' => '懞懂',
'蒙蒙懂懂' => '懞懞懂懂',
'蒙直' => '懞直',
-'惩前毖后' => '懲前毖後',
'惩忿窒欲' => '懲忿窒欲',
'怀里' => '懷裡',
'怀表' => '懷錶',
'怀钟' => '懷鐘',
+'悬挂' => '懸掛',
'悬梁' => '懸樑',
'悬臂梁' => '懸臂樑',
'悬钟' => '懸鐘',
@@ -5602,11 +5591,11 @@ $zh2Hant = array(
'戬谷' => '戩穀',
'截发' => '截髮',
'战天斗地' => '戰天鬥地',
-'战后' => '戰後',
'战栗' => '戰慄',
'战斗' => '戰鬥',
'戏彩娱亲' => '戲綵娛親',
'戏里' => '戲裡',
+'戴表元' => '戴表元',
'戴表' => '戴錶',
'戴发含齿' => '戴髮含齒',
'房里' => '房裡',
@@ -5630,6 +5619,7 @@ $zh2Hant = array(
'手表达' => '手表達',
'手表露' => '手表露',
'手表面' => '手表面',
+'手里剑' => '手裏劍',
'手里' => '手裡',
'手表' => '手錶',
'手松' => '手鬆',
@@ -5658,6 +5648,7 @@ $zh2Hant = array(
'打着钟' => '打著鐘',
'打路庄板' => '打路莊板',
'打钟' => '打鐘',
+'打风后' => '打風後',
'打斗' => '打鬥',
'托管国' => '托管國',
'扛大梁' => '扛大樑',
@@ -5668,8 +5659,6 @@ $zh2Hant = array(
'批复' => '批複',
'批注' => '批註',
'批斗' => '批鬥',
-'承先启后' => '承先啟後',
-'承前启后' => '承前啟後',
'承制' => '承製',
'抑制作用' => '抑制作用',
'抑郁' => '抑鬱',
@@ -5737,7 +5726,6 @@ $zh2Hant = array(
'拿下钟' => '拿下鐘',
'拿准' => '拿準',
'拿破仑' => '拿破崙',
-'挂名' => '挂名',
'挂图' => '挂圖',
'挂帅' => '挂帥',
'挂彩' => '挂彩',
@@ -5745,7 +5733,6 @@ $zh2Hant = array(
'挂号' => '挂號',
'挂车' => '挂車',
'挂面' => '挂面',
-'指手划脚' => '指手劃腳',
'挌斗' => '挌鬥',
'挑大梁' => '挑大樑',
'挑斗' => '挑鬥',
@@ -5817,6 +5804,7 @@ $zh2Hant = array(
'扫荡' => '掃蕩',
'掌柜' => '掌柜',
'排骨面' => '排骨麵',
+'挂名' => '掛名',
'挂帘' => '掛帘',
'挂历' => '掛曆',
'挂钩' => '掛鈎',
@@ -5953,7 +5941,6 @@ $zh2Hant = array(
'撞钟' => '撞鐘',
'撞阵冲军' => '撞陣衝軍',
'撤并' => '撤併',
-'撤后' => '撤後',
'拨谷' => '撥穀',
'撩斗' => '撩鬥',
'播于' => '播於',
@@ -5967,7 +5954,6 @@ $zh2Hant = array(
'担担面' => '擔擔麵',
'担着' => '擔著',
'担负着' => '擔負著',
-'擘划' => '擘劃',
'据云' => '據云',
'据干而窥井底' => '據榦而窺井底',
'擢发' => '擢髮',
@@ -6006,31 +5992,29 @@ $zh2Hant = array(
'敲钟' => '敲鐘',
'整庄' => '整莊',
'整只' => '整隻',
+'整风后' => '整風後',
'整发用品' => '整髮用品',
-'敌后' => '敵後',
'敌忾同仇' => '敵愾同讎',
'敷药' => '敷藥',
'数天后' => '數天後',
-'数字表' => '數字錶',
'数字钟' => '數字鐘',
'数字钟表' => '數字鐘錶',
'数罪并罚' => '數罪併罰',
'数与虏确' => '數與虜确',
'文丑' => '文丑',
'文汇报' => '文匯報',
-'文后' => '文後',
'文征明' => '文徵明',
'文思泉涌' => '文思泉湧',
'文采郁郁' => '文采郁郁',
'斗转参横' => '斗轉參橫',
'斫雕为朴' => '斫雕為樸',
+'新井里美' => '新井里美',
'新历' => '新曆',
'新历史' => '新歷史',
'新扎' => '新紮',
'新庄' => '新莊',
'新庄市' => '新莊市',
'斲雕为朴' => '斲雕為樸',
-'断后' => '斷後',
'断发' => '斷髮',
'断发文身' => '斷髮文身',
'方便面' => '方便麵',
@@ -6113,6 +6097,8 @@ $zh2Hant = array(
'于民' => '於民',
'于水' => '於水',
'于法' => '於法',
+'于海上' => '於海上',
+'于海边' => '於海邊',
'于潜县' => '於潛縣',
'于火' => '於火',
'于焉' => '於焉',
@@ -6138,6 +6124,9 @@ $zh2Hant = array(
'于丑' => '於醜',
'于野' => '於野',
'于陆' => '於陸',
+'于震中' => '於震中',
+'于震前' => '於震前',
+'于震后' => '於震后',
'于0' => '於0',
'于1' => '於1',
'于2' => '於2',
@@ -6162,7 +6151,7 @@ $zh2Hant = array(
'旗杆' => '旗杆',
'日占' => '日佔',
'日子里' => '日子裡',
-'日后' => '日後',
+'日心历表' => '日心曆表',
'日晒' => '日晒',
'日历' => '日曆',
'日历史' => '日歷史',
@@ -6232,6 +6221,7 @@ $zh2Hant = array(
'历书' => '曆書',
'历本' => '曆本',
'历法' => '曆法',
+'历狱' => '曆獄',
'历纪' => '曆紀',
'历象' => '曆象',
'曝晒' => '曝晒',
@@ -6240,34 +6230,42 @@ $zh2Hant = array(
'更仆难数' => '更僕難數',
'更签' => '更籤',
'更钟' => '更鐘',
-'书后' => '書後',
'书呆子' => '書獃子',
'书签' => '書籤',
'曼谷人' => '曼谷人',
'曾朴' => '曾樸',
'最多' => '最多',
-'最后' => '最後',
'会上签署' => '會上簽署',
'会上签订' => '會上簽訂',
'会占' => '會佔',
'会占卜' => '會占卜',
+'会干扰' => '會干擾',
+'會干擾' => '會干擾',
'会干' => '會幹',
'会吊' => '會弔',
-'会后' => '會後',
'会里' => '會裡',
-'月后' => '月後',
'月历' => '月曆',
'月历史' => '月歷史',
+'月球历表' => '月球曆表',
'月离于毕' => '月離於畢',
'月面' => '月面',
'月丽于箕' => '月麗於箕',
'有事之无范' => '有事之無範',
'有仆' => '有僕',
+'有只不' => '有只不',
+'有只允' => '有只允',
+'有只容' => '有只容',
+'有只採' => '有只採',
+'有只采' => '有只採',
+'有只是' => '有只是',
+'有只用' => '有只用',
'有够赞' => '有夠讚',
'有征伐' => '有征伐',
+'有征戰' => '有征戰',
'有征战' => '有征戰',
'有征服' => '有征服',
'有征讨' => '有征討',
+'有征討' => '有征討',
'有征' => '有徵',
'有恒街' => '有恒街',
'有栖川' => '有栖川',
@@ -6279,11 +6277,11 @@ $zh2Hant = array(
'服于' => '服於',
'服药' => '服藥',
'望了望' => '望了望',
+'望后石' => '望后石',
'望着表' => '望著錶',
'望着钟' => '望著鐘',
'望着钟表' => '望著鐘錶',
'朝乾夕惕' => '朝乾夕惕',
-'朝后' => '朝後',
'朝钟' => '朝鐘',
'朦胧' => '朦朧',
'蒙胧' => '朦朧',
@@ -6302,6 +6300,7 @@ $zh2Hant = array(
'朱理安历' => '朱理安曆',
'朱理安历史' => '朱理安歷史',
'杆子' => '杆子',
+'李志喜' => '李志喜',
'李連杰' => '李連杰',
'李连杰' => '李連杰',
'材干' => '材幹',
@@ -6309,19 +6308,22 @@ $zh2Hant = array(
'村庄' => '村莊',
'村落发' => '村落發',
'村里' => '村裡',
+'村里长' => '村里長',
+'村里長' => '村里長',
'杜老志道' => '杜老誌道',
'杞宋无征' => '杞宋無徵',
'束发' => '束髮',
'杯干' => '杯乾',
'杯面' => '杯麵',
'杰伦' => '杰倫',
+'杰威爾音樂' => '杰威爾音樂',
+'杰威尔音乐' => '杰威爾音樂',
'杰特' => '杰特',
'东周钟' => '東周鐘',
'东岳' => '東嶽',
'东冲西突' => '東衝西突',
'东游' => '東遊',
'松山庄' => '松山庄',
-'松柏后凋' => '松柏後凋',
'板着脸' => '板著臉',
'板荡' => '板蕩',
'林宏岳' => '林宏嶽',
@@ -6336,6 +6338,7 @@ $zh2Hant = array(
'架钟' => '架鐘',
'某只' => '某隻',
'染指于' => '染指於',
+'染殿后' => '染殿后',
'染发' => '染髮',
'柜上' => '柜上',
'柜子' => '柜子',
@@ -6353,7 +6356,6 @@ $zh2Hant = array(
'格斗' => '格鬥',
'桂圆干' => '桂圓乾',
'桅杆' => '桅杆',
-'案发后' => '案發後',
'桌几' => '桌几',
'桌历' => '桌曆',
'桌历史' => '桌歷史',
@@ -6374,18 +6376,20 @@ $zh2Hant = array(
'棺材里' => '棺材裡',
'植发' => '植髮',
'椰枣干' => '椰棗乾',
+'楊雅筑' => '楊雅筑',
+'杨雅筑' => '楊雅筑',
'楚庄问鼎' => '楚莊問鼎',
'楚庄王' => '楚莊王',
'楚庄绝缨' => '楚莊絕纓',
'桢干' => '楨幹',
'业余' => '業餘',
'榨干' => '榨乾',
-'荣登后座' => '榮登后座',
'杠杆' => '槓桿',
'乐器钟' => '樂器鐘',
'樊于期' => '樊於期',
'梁上' => '樑上',
'梁柱' => '樑柱',
+'樗里子' => '樗里子',
'标杆' => '標杆',
'标标致致' => '標標致致',
'标准' => '標準',
@@ -6395,6 +6399,11 @@ $zh2Hant = array(
'标志' => '標誌',
'模棱' => '模稜',
'模范' => '模範',
+'模范14棒' => '模范14棒',
+'模范21棒' => '模范21棒',
+'模范七棒' => '模范七棒',
+'模范三军' => '模范三軍',
+'模范三軍' => '模范三軍',
'模范棒棒堂' => '模范棒棒堂',
'模制' => '模製',
'样范' => '樣範',
@@ -6441,7 +6450,7 @@ $zh2Hant = array(
'栏杆' => '欄杆',
'欲海难填' => '欲海難填',
'欺蒙' => '欺矇',
-'歇后' => '歇後',
+'歌后' => '歌后',
'歌钟' => '歌鐘',
'欧游' => '歐遊',
'止咳药' => '止咳藥',
@@ -6450,10 +6459,9 @@ $zh2Hant = array(
'止血药' => '止血藥',
'正在叱咤' => '正在叱咤',
'正官庄' => '正官庄',
-'正后' => '正後',
'正当着' => '正當著',
-'此后' => '此後',
'武丑' => '武丑',
+'武后' => '武后',
'武斗' => '武鬥',
'岁聿云暮' => '歲聿云暮',
'历史里' => '歷史裡',
@@ -6461,7 +6469,6 @@ $zh2Hant = array(
'归于' => '歸於',
'归余' => '歸餘',
'歹斗' => '歹鬥',
-'死后' => '死後',
'死于' => '死於',
'死胡同' => '死胡同',
'死里求生' => '死裡求生',
@@ -6473,17 +6480,17 @@ $zh2Hant = array(
'殷师牛斗' => '殷師牛鬥',
'杀虫药' => '殺蟲藥',
'壳里' => '殼裡',
-'殿后' => '殿後',
'殿钟自鸣' => '殿鐘自鳴',
'毁于' => '毀於',
'毁钟为铎' => '毀鐘為鐸',
'殴斗' => '毆鬥',
+'母后' => '母后',
'母范' => '母範',
'母丑' => '母醜',
'每每只' => '每每只',
'每只' => '每隻',
'毒药' => '毒藥',
-'比划' => '比劃',
+'毗婆尸佛' => '毗婆尸佛',
'毛坏' => '毛坏',
'毛姜' => '毛薑',
'毛发' => '毛髮',
@@ -6494,7 +6501,10 @@ $zh2Hant = array(
'氤郁' => '氤鬱',
'水来汤里去' => '水來湯裡去',
'水准' => '水準',
+'水无怜奈' => '水無怜奈',
'水里' => '水裡',
+'水里溪' => '水里溪',
+'水里浊水溪' => '水里濁水溪',
'水里鄉' => '水里鄉',
'水里乡' => '水里鄉',
'永历' => '永曆',
@@ -6543,7 +6553,6 @@ $zh2Hant = array(
'泱郁' => '泱鬱',
'泳气钟' => '泳氣鐘',
'洄游' => '洄遊',
-'洋面' => '洋麵',
'洒家' => '洒家',
'洒扫' => '洒掃',
'洒水' => '洒水',
@@ -6580,11 +6589,15 @@ $zh2Hant = array(
'海上布雷' => '海上佈雷',
'海干' => '海乾',
'海湾布雷' => '海灣佈雷',
+'涂善妮' => '涂善妮',
'涂坤' => '涂坤',
-'涂壮勋' => '涂壯勳',
'涂壯勳' => '涂壯勳',
+'涂壮勋' => '涂壯勳',
'涂天相' => '涂天相',
+'涂姓' => '涂姓',
'涂序瑄' => '涂序瑄',
+'涂敏恒' => '涂敏恆',
+'涂敏恆' => '涂敏恆',
'涂澤民' => '涂澤民',
'涂泽民' => '涂澤民',
'涂绍煃' => '涂紹煃',
@@ -6671,6 +6684,7 @@ $zh2Hant = array(
'准会' => '準會',
'准决赛' => '準決賽',
'准的' => '準的',
+'准直' => '準直',
'准确' => '準確',
'准线' => '準線',
'准绳' => '準繩',
@@ -6712,6 +6726,7 @@ $zh2Hant = array(
'潭里' => '潭裡',
'潮涌' => '潮湧',
'溃于' => '潰於',
+'涩谷区' => '澀谷區',
'澄澹精致' => '澄澹精致',
'澒蒙' => '澒濛',
'泽渗漓而下降' => '澤滲灕而下降',
@@ -6732,6 +6747,7 @@ $zh2Hant = array(
'蒙雾' => '濛霧',
'蒙松雨' => '濛鬆雨',
'蒙鸿' => '濛鴻',
+'滨田里佳子' => '濱田里佳子',
'泻药' => '瀉藥',
'沈吉线' => '瀋吉線',
'沈山线' => '瀋山線',
@@ -6766,7 +6782,6 @@ $zh2Hant = array(
'炮制' => '炮製',
'炸药' => '炸藥',
'炸酱面' => '炸醬麵',
-'为后' => '為後',
'为准' => '為準',
'为着' => '為著',
'乌发' => '烏髮',
@@ -6776,7 +6791,6 @@ $zh2Hant = array(
'烤干' => '烤乾',
'烤晒' => '烤晒',
'焙干' => '焙乾',
-'无后' => '無後',
'无征不信' => '無徵不信',
'无业游民' => '無業游民',
'无梁楼盖' => '無樑樓蓋',
@@ -6784,7 +6798,6 @@ $zh2Hant = array(
'无药可救' => '無藥可救',
'無言不仇' => '無言不讎',
'无余' => '無餘',
-'然后' => '然後',
'然身死才数月耳' => '然身死纔數月耳',
'炼药' => '煉藥',
'炼制' => '煉製',
@@ -6798,6 +6811,7 @@ $zh2Hant = array(
'照相干片' => '照相乾片',
'煨干' => '煨乾',
'煮面' => '煮麵',
+'熊杰' => '熊杰',
'荧郁' => '熒鬱',
'熬药' => '熬藥',
'炖药' => '燉藥',
@@ -6816,7 +6830,7 @@ $zh2Hant = array(
'烫面' => '燙麵',
'营干' => '營幹',
'烬余' => '燼餘',
-'争先恐后' => '爭先恐後',
+'爆发指数' => '爆發指數',
'争奇斗妍' => '爭奇鬥妍',
'争奇斗异' => '爭奇鬥異',
'争奇斗艳' => '爭奇鬥豔',
@@ -6827,11 +6841,9 @@ $zh2Hant = array(
'爰定祥历' => '爰定祥厤',
'爽荡' => '爽蕩',
'尔冬升' => '爾冬陞',
-'尔后' => '爾後',
'墙里' => '牆裡',
'片言只语' => '片言隻語',
'牙签' => '牙籤',
-'牛后' => '牛後',
'牛肉面' => '牛肉麵',
'牛只' => '牛隻',
'物欲' => '物慾',
@@ -6844,12 +6856,12 @@ $zh2Hant = array(
'特效药' => '特效藥',
'特制' => '特製',
'牵一发' => '牽一髮',
-'牵挂' => '牽挂',
'牵系' => '牽繫',
'荦确' => '犖确',
'狂占' => '狂佔',
'狂并潮' => '狂併潮',
'狃于' => '狃於',
+'狄志杰' => '狄志杰',
'狐借虎威' => '狐藉虎威',
'猛于' => '猛於',
'猛冲' => '猛衝',
@@ -6886,6 +6898,8 @@ $zh2Hant = array(
'率团参加' => '率團參加',
'玉历' => '玉曆',
'玉历史' => '玉歷史',
+'王侯后' => '王侯后',
+'王后' => '王后',
'王庄' => '王莊',
'王余鱼' => '王餘魚',
'珍肴异馔' => '珍肴異饌',
@@ -6902,6 +6916,8 @@ $zh2Hant = array(
'瑞征' => '瑞徵',
'瑶签' => '瑤籤',
'环游' => '環遊',
+'瓷制' => '瓷製',
+'甄后' => '甄后',
'瓮安' => '甕安',
'甚于' => '甚於',
'甚么' => '甚麼',
@@ -6912,11 +6928,11 @@ $zh2Hant = array(
'生殖洄游' => '生殖洄游',
'生物钟' => '生物鐘',
'生发生' => '生發生',
+'生华发' => '生華髮',
'生姜' => '生薑',
'生锈' => '生鏽',
'生发' => '生髮',
'产卵洄游' => '產卵洄游',
-'产后' => '產後',
'用药' => '用藥',
'甩发' => '甩髮',
'田谷' => '田穀',
@@ -6925,11 +6941,11 @@ $zh2Hant = array(
'由余' => '由余',
'由于' => '由於',
'由表及里' => '由表及裡',
+'甲后路' => '甲后路',
'男佣人' => '男佣人',
'男仆' => '男僕',
'男用表' => '男用錶',
'畏于' => '畏於',
-'留后' => '留後',
'留发' => '留髮',
'毕于' => '畢於',
'毕业于' => '畢業於',
@@ -6944,8 +6960,6 @@ $zh2Hant = array(
'疑凶' => '疑兇',
'疲于' => '疲於',
'疲困' => '疲睏',
-'病后' => '病後',
-'病后初愈' => '病後初癒',
'病征' => '病徵',
'病愈' => '病癒',
'病余' => '病餘',
@@ -6985,11 +6999,15 @@ $zh2Hant = array(
'白霉' => '白黴',
'百个' => '百個',
'百只可' => '百只可',
+'百只夠' => '百只夠',
'百只够' => '百只夠',
'百只怕' => '百只怕',
'百只足够' => '百只足夠',
+'百只足夠' => '百只足夠',
+'百周后' => '百周後',
'百多只' => '百多隻',
'百天后' => '百天後',
+'百年' => '百年',
'百拙千丑' => '百拙千醜',
'百科里' => '百科裡',
'百谷' => '百穀',
@@ -7003,9 +7021,10 @@ $zh2Hant = array(
'的克制' => '的剋制',
'的钟' => '的鐘',
'的钟表' => '的鐘錶',
+'的长发' => '的長髮',
'皆可作淀' => '皆可作澱',
'皆准' => '皆準',
-'皇天后土' => '皇天后土',
+'皇后' => '皇后',
'皇历' => '皇曆',
'皇极历' => '皇極曆',
'皇极历史' => '皇極歷史',
@@ -7054,6 +7073,7 @@ $zh2Hant = array(
'看钟' => '看鐘',
'真凶' => '真兇',
'真个' => '真箇',
+'眼干' => '眼乾',
'眼帘' => '眼帘',
'眼眶里' => '眼眶裡',
'眼睛里' => '眼睛裡',
@@ -7067,7 +7087,6 @@ $zh2Hant = array(
'瞄准' => '瞄準',
'瞅下表' => '瞅下錶',
'瞅下钟' => '瞅下鐘',
-'瞠乎后矣' => '瞠乎後矣',
'瞧着表' => '瞧著錶',
'瞧着钟' => '瞧著鐘',
'瞧着钟表' => '瞧著鐘錶',
@@ -7075,7 +7094,6 @@ $zh2Hant = array(
'了然' => '瞭然',
'了若指掌' => '瞭若指掌',
'瞳蒙' => '瞳矇',
-'瞻前顾后' => '瞻前顧後',
'蒙事' => '矇事',
'蒙昧无知' => '矇昧無知',
'蒙混' => '矇混',
@@ -7152,7 +7170,6 @@ $zh2Hant = array(
'私斗' => '私鬥',
'秋假里' => '秋假裡',
'秋天里' => '秋天裡',
-'秋后' => '秋後',
'秋日里' => '秋日裡',
'秋裤' => '秋褲',
'秋游' => '秋遊',
@@ -7167,11 +7184,9 @@ $zh2Hant = array(
'秒表示' => '秒表示',
'秒表' => '秒錶',
'秒钟' => '秒鐘',
+'秦庄襄王' => '秦莊襄王',
'移祸于' => '移禍於',
'稀松' => '稀鬆',
-'税后' => '稅後',
-'税后净利' => '稅後淨利',
-'稍后' => '稍後',
'棱台' => '稜台',
'棱子' => '稜子',
'棱层' => '稜層',
@@ -7243,6 +7258,7 @@ $zh2Hant = array(
'穷追不舍' => '窮追不捨',
'穷发' => '窮髮',
'窃钟掩耳' => '竊鐘掩耳',
+'立后综' => '立后綜',
'立于' => '立於',
'立范' => '立範',
'站干岸儿' => '站乾岸兒',
@@ -7252,10 +7268,10 @@ $zh2Hant = array(
'竹几' => '竹几',
'竹林之游' => '竹林之遊',
'竹签' => '竹籤',
+'竹制' => '竹製',
'笑里藏刀' => '笑裡藏刀',
'笨笨呆呆' => '笨笨呆呆',
'第四出局' => '第四出局',
-'笔划' => '筆劃',
'笔秃墨干' => '筆禿墨乾',
'等于' => '等於',
'笋干' => '筍乾',
@@ -7273,7 +7289,6 @@ $zh2Hant = array(
'筑阳' => '筑陽',
'答复' => '答覆',
'答覆' => '答覆',
-'策划' => '策劃',
'筵几' => '筵几',
'个中原因' => '箇中原因',
'个中奥妙' => '箇中奧妙',
@@ -7302,15 +7317,17 @@ $zh2Hant = array(
'范字' => '範字',
'范式' => '範式',
'范性形变' => '範性形變',
+'范数' => '範數',
'范文' => '範文',
'范本' => '範本',
'范畴' => '範疇',
'范金' => '範金',
'简并' => '簡併',
'简朴' => '簡樸',
+'简筑翎' => '簡筑翎',
+'簡筑翎' => '簡筑翎',
'簸荡' => '簸蕩',
'签着' => '簽著',
-'筹划' => '籌劃',
'签幐' => '籤幐',
'签押' => '籤押',
'签条' => '籤條',
@@ -7340,11 +7357,11 @@ $zh2Hant = array(
'系列里' => '系列裡',
'系着' => '系著',
'系里' => '系裡',
-'纪元后' => '紀元後',
'纪历' => '紀曆',
'纪历史' => '紀歷史',
'约占' => '約佔',
'红绳系足' => '紅繩繫足',
+'红色长发' => '紅色長髮',
'红钟' => '紅鐘',
'红霉素' => '紅霉素',
'红发' => '紅髮',
@@ -7390,10 +7407,10 @@ $zh2Hant = array(
'结余' => '結餘',
'结发' => '結髮',
'绝对参照' => '絕對參照',
-'绝后' => '絕後',
'绝于' => '絕於',
'绞干' => '絞乾',
'络腮胡' => '絡腮鬍',
+'給我干脆' => '給我干脆',
'给我干脆' => '給我干脆',
'给于' => '給於',
'丝来线去' => '絲來線去',
@@ -7405,6 +7422,7 @@ $zh2Hant = array(
'丝线' => '絲線',
'丝织厂' => '絲織廠',
'丝虫' => '絲蟲',
+'丝制' => '絲製',
'丝发' => '絲髮',
'绑扎' => '綁紮',
'綑扎' => '綑紮',
@@ -7416,6 +7434,7 @@ $zh2Hant = array(
'绾发' => '綰髮',
'网里' => '網裡',
'网志' => '網誌',
+'网游' => '網遊',
'彩带' => '綵帶',
'彩排' => '綵排',
'彩楼' => '綵樓',
@@ -7453,7 +7472,6 @@ $zh2Hant = array(
'纵欲' => '縱慾',
'纤夫' => '縴夫',
'纤手' => '縴手',
-'总后' => '總後',
'总裁制' => '總裁制',
'繁复' => '繁複',
'繁钟' => '繁鐘',
@@ -7543,18 +7561,17 @@ $zh2Hant = array(
'老姜' => '老薑',
'老板' => '老闆',
'老面皮' => '老面皮',
-'考后' => '考後',
'考征' => '考徵',
'而克制' => '而剋制',
-'而后' => '而後',
'耍斗' => '耍鬥',
'耕佣' => '耕傭',
'耕获' => '耕穫',
-'耳后' => '耳後',
'耳余' => '耳餘',
'耿于' => '耿於',
'聊斋志异' => '聊齋志異',
+'圣后' => '聖后',
'聘雇' => '聘僱',
+'闻风后' => '聞風後',
'联系' => '聯繫',
'听于' => '聽於',
'肉干' => '肉乾',
@@ -7573,7 +7590,6 @@ $zh2Hant = array(
'胃里' => '胃裡',
'背向着' => '背向著',
'背地里' => '背地裡',
-'背后' => '背後',
'胎发' => '胎髮',
'胜肽' => '胜肽',
'胜键' => '胜鍵',
@@ -7607,7 +7623,6 @@ $zh2Hant = array(
'腕表' => '腕錶',
'脑子里' => '腦子裡',
'脑干' => '腦幹',
-'脑后' => '腦後',
'腰里' => '腰裡',
'脚注' => '腳註',
'脚炼' => '腳鍊',
@@ -7656,8 +7671,8 @@ $zh2Hant = array(
'旧钟' => '舊鐘',
'旧钟表' => '舊鐘錶',
'舌干唇焦' => '舌乾唇焦',
-'舌后' => '舌後',
'舒卷' => '舒捲',
+'舞后' => '舞后',
'航海历' => '航海曆',
'航海历史' => '航海歷史',
'船只得' => '船只得',
@@ -7668,6 +7683,8 @@ $zh2Hant = array(
'舰只' => '艦隻',
'良药' => '良藥',
'色欲' => '色慾',
+'艷后' => '艷后',
+'艳后' => '艷后',
'艸木丰丰' => '艸木丰丰',
'芍药' => '芍藥',
'芒果干' => '芒果乾',
@@ -7699,6 +7716,8 @@ $zh2Hant = array(
'范文藤' => '范文藤',
'范文虎' => '范文虎',
'范登堡' => '范登堡',
+'范贤惠' => '范賢惠',
+'范賢惠' => '范賢惠',
'茶几' => '茶几',
'茶庄' => '茶莊',
'茶余' => '茶餘',
@@ -7734,6 +7753,7 @@ $zh2Hant = array(
'庄院' => '莊院',
'庄骚' => '莊騷',
'茎干' => '莖幹',
+'莜面' => '莜麵',
'莽荡' => '莽蕩',
'菌丝体' => '菌絲體',
'菜干' => '菜乾',
@@ -7742,10 +7762,14 @@ $zh2Hant = array(
'菠萝干' => '菠蘿乾',
'华严钟' => '華嚴鐘',
'华发' => '華髮',
+'萬一只' => '萬一只',
'万一只' => '萬一只',
'万个' => '萬個',
+'万周后' => '萬周後',
'万多只' => '萬多隻',
'万天后' => '萬天後',
+'万年' => '萬年',
+'万年历' => '萬年曆',
'万年历表' => '萬年曆錶',
'万历' => '萬曆',
'万历史' => '萬歷史',
@@ -7754,9 +7778,9 @@ $zh2Hant = array(
'万象' => '萬象',
'万只' => '萬隻',
'万余' => '萬餘',
-'落后' => '落後',
'落腮胡' => '落腮鬍',
'落发' => '落髮',
+'叶叶琴' => '葉叶琴',
'叶叶琹' => '葉叶琹',
'着儿' => '著兒',
'着克制' => '著剋制',
@@ -7819,8 +7843,8 @@ $zh2Hant = array(
'薙发' => '薙髮',
'薝卜' => '薝蔔',
'苧悴' => '薴悴',
-'苧烯' => '薴烯',
'薴烯' => '薴烯',
+'苧烯' => '薴烯',
'借以' => '藉以',
'借助' => '藉助',
'借寇兵' => '藉寇兵',
@@ -7913,6 +7937,7 @@ $zh2Hant = array(
'蚊动牛斗' => '蚊動牛鬥',
'蛇发女妖' => '蛇髮女妖',
'蛔虫药' => '蛔蟲藥',
+'蜂后' => '蜂后',
'蜂涌' => '蜂湧',
'蜂准' => '蜂準',
'蜜里调油' => '蜜裡調油',
@@ -7922,6 +7947,8 @@ $zh2Hant = array(
'蝎谮' => '蝎譖',
'虮蝨相吊' => '蟣蝨相弔',
'蛏干' => '蟶乾',
+'蚁后' => '蟻后',
+'蟻后' => '蟻后',
'蠁干' => '蠁幹',
'蛮干' => '蠻幹',
'血拼' => '血拚',
@@ -7930,11 +7957,11 @@ $zh2Hant = array(
'行事历史' => '行事歷史',
'行凶' => '行兇',
'行凶前' => '行兇前',
-'行凶后' => '行兇後',
'行凶後' => '行兇後',
'行于' => '行於',
'行百里者半于九十' => '行百里者半於九十',
'胡同' => '衚衕',
+'卫后庄公' => '衛後莊公',
'卫星钟' => '衛星鐘',
'冲上' => '衝上',
'冲下' => '衝下',
@@ -8009,8 +8036,6 @@ $zh2Hant = array(
'补注' => '補註',
'装折' => '裝摺',
'里勾外连' => '裡勾外連',
-'里外' => '裡外',
-'里子' => '裡子',
'里屋' => '裡屋',
'里层' => '裡層',
'里布' => '裡布',
@@ -8118,7 +8143,6 @@ $zh2Hant = array(
'褒赞' => '褒讚',
'衬里' => '襯裡',
'西占' => '西佔',
-'西元后' => '西元後',
'西周钟' => '西周鐘',
'西岳' => '西嶽',
'西晒' => '西晒',
@@ -8126,6 +8150,7 @@ $zh2Hant = array(
'西历史' => '西歷史',
'西米谷' => '西米谷',
'西药' => '西藥',
+'西西里' => '西西里',
'西谷米' => '西谷米',
'西游' => '西遊',
'要占' => '要佔',
@@ -8148,7 +8173,6 @@ $zh2Hant = array(
'见棱见角' => '見稜見角',
'见素抱朴' => '見素抱樸',
'见钟不打' => '見鐘不打',
-'规划' => '規劃',
'规范' => '規範',
'視如寇仇' => '視如寇讎',
'视于' => '視於',
@@ -8167,7 +8191,6 @@ $zh2Hant = array(
'言大而夸' => '言大而夸',
'言辩而确' => '言辯而确',
'订制' => '訂製',
-'计划' => '計劃',
'计时表' => '計時錶',
'托了' => '託了',
'托事' => '託事',
@@ -8197,6 +8220,7 @@ $zh2Hant = array(
'托过' => '託過',
'托附' => '託附',
'许愿起经' => '許愿起經',
+'许虬' => '許虬',
'诉说着' => '訴說著',
'注上' => '註上',
'注册' => '註冊',
@@ -8249,7 +8273,6 @@ $zh2Hant = array(
'诬蔑' => '誣衊',
'说着' => '說著',
'谁干的' => '誰幹的',
-'课后' => '課後',
'课征' => '課徵',
'课余' => '課餘',
'调准' => '調準',
@@ -8262,9 +8285,9 @@ $zh2Hant = array(
'请托' => '請託',
'咨询' => '諮詢',
'诸余' => '諸餘',
-'谋定后动' => '謀定後動',
'谋干' => '謀幹',
'谢绝参观' => '謝絕參觀',
+'谢华后' => '謝華后',
'谬采虚声' => '謬採虛聲',
'谬赞' => '謬讚',
'謷丑' => '謷醜',
@@ -8275,7 +8298,6 @@ $zh2Hant = array(
'警钟' => '警鐘',
'译注' => '譯註',
'护发' => '護髮',
-'读后' => '讀後',
'变征' => '變徵',
'变丑' => '變醜',
'变脏' => '變髒',
@@ -8320,12 +8342,16 @@ $zh2Hant = array(
'费占' => '費佔',
'贻范' => '貽範',
'资金占用' => '資金占用',
-'贾后' => '賈後',
+'贾后' => '賈后',
+'賈后' => '賈后',
'赈饥' => '賑饑',
'赏赞' => '賞讚',
+'贤后' => '賢后',
+'賢后' => '賢后',
'卖断发' => '賣斷發',
'卖呆' => '賣獃',
'质朴' => '質樸',
+'赌后' => '賭后',
'赌台' => '賭檯',
'赌斗' => '賭鬥',
'賸余' => '賸餘',
@@ -8336,19 +8362,19 @@ $zh2Hant = array(
'赤绳系足' => '赤繩繫足',
'赤霉素' => '赤霉素',
'走回路' => '走回路',
-'走后' => '走後',
'起复' => '起複',
'起哄' => '起鬨',
'超级杯' => '超級盃',
'赶制' => '趕製',
'赶面棍' => '趕麵棍',
+'赵威后' => '趙威后',
+'赵惠后' => '趙惠后',
'赵治勋' => '趙治勳',
'赵庄' => '趙莊',
'趱干' => '趲幹',
'足于' => '足於',
'跌扑' => '跌扑',
'跌荡' => '跌蕩',
-'跟前跟后' => '跟前跟後',
'路签' => '路籤',
'跳梁小丑' => '跳樑小丑',
'跳荡' => '跳蕩',
@@ -8356,7 +8382,6 @@ $zh2Hant = array(
'蹪于' => '蹪於',
'蹭棱子' => '蹭稜子',
'躁郁' => '躁鬱',
-'身后' => '身後',
'身于' => '身於',
'身体发肤' => '身體髮膚',
'躯干' => '軀幹',
@@ -8431,15 +8456,20 @@ $zh2Hant = array(
'迷魂药' => '迷魂藥',
'追凶' => '追兇',
'退伙' => '退夥',
-'退后' => '退後',
'退烧药' => '退燒藥',
'退藏于密' => '退藏於密',
'逆钟' => '逆鐘',
'逆钟向' => '逆鐘向',
+'逆风后' => '逆風後',
'逋发' => '逋髮',
'逍遥游' => '逍遙遊',
'透辟' => '透闢',
+'这只不' => '這只不',
+'这只允' => '這只允',
+'这只容' => '這只容',
+'这只采' => '這只採',
'这只是' => '這只是',
+'这只用' => '這只用',
'这伙人' => '這夥人',
'这里' => '這裡',
'这钟' => '這鐘',
@@ -8464,7 +8494,10 @@ $zh2Hant = array(
'连庄' => '連莊',
'周游世界' => '週遊世界',
'进占' => '進佔',
+'進制' => '進制',
+'进制' => '進制',
'逼并' => '逼併',
+'遇风后' => '遇風後',
'游了' => '遊了',
'游人' => '遊人',
'游仙' => '遊仙',
@@ -8514,7 +8547,6 @@ $zh2Hant = array(
'游离' => '遊離',
'游骑兵' => '遊騎兵',
'游魂' => '遊魂',
-'过后' => '過後',
'过于' => '過於',
'过杆' => '過杆',
'过水面' => '過水麵',
@@ -8526,6 +8558,7 @@ $zh2Hant = array(
'遨游' => '遨遊',
'遮丑' => '遮醜',
'迁于' => '遷於',
+'选手' => '選手',
'选手表明' => '選手表明',
'选手表决' => '選手表決',
'选手表现' => '選手表現',
@@ -8548,14 +8581,17 @@ $zh2Hant = array(
'那只' => '那隻',
'那么' => '那麼',
'那么着' => '那麼著',
+'邱于庭' => '邱于庭',
'郁朴' => '郁樸',
'郁郁菲菲' => '郁郁菲菲',
+'郁郁青青' => '郁郁青青',
'郊游' => '郊遊',
'郘钟' => '郘鐘',
'部落发' => '部落發',
+'郭后' => '郭后',
'都于' => '都於',
+'鄉愿' => '鄉愿',
'乡愿' => '鄉愿',
-'邓后' => '鄧後',
'鄭凱云' => '鄭凱云',
'郑凯云' => '鄭凱云',
'郑庄公' => '鄭莊公',
@@ -8565,7 +8601,6 @@ $zh2Hant = array(
'配药' => '配藥',
'配制' => '配製',
'酒帘' => '酒帘',
-'酒后' => '酒後',
'酒坛' => '酒罈',
'酒肴' => '酒肴',
'酒药' => '酒藥',
@@ -8638,7 +8673,6 @@ $zh2Hant = array(
'采石矶' => '采石磯',
'釉药' => '釉藥',
'里程表' => '里程錶',
-'重划' => '重劃',
'重回' => '重回',
'重折' => '重摺',
'重于' => '重於',
@@ -8660,6 +8694,7 @@ $zh2Hant = array(
'金仑溪' => '金崙溪',
'金布道' => '金布道',
'金范' => '金範',
+'金色长发' => '金色長髮',
'金表情' => '金表情',
'金表态' => '金表態',
'金表扬' => '金表揚',
@@ -8677,7 +8712,6 @@ $zh2Hant = array(
'金发' => '金髮',
'钉锤' => '釘鎚',
'钩心斗角' => '鈎心鬥角',
-'铃响后' => '鈴響後',
'银朱' => '銀硃',
'银发' => '銀髮',
'铜范' => '銅範',
@@ -8825,7 +8859,6 @@ $zh2Hant = array(
'长历史' => '長歷史',
'长生药' => '長生藥',
'长胡' => '長鬍',
-'门前门后' => '門前門後',
'门帘' => '門帘',
'门吊儿' => '門弔兒',
'门里' => '門裡',
@@ -8874,6 +8907,7 @@ $zh2Hant = array(
'阿呆瓜' => '阿呆瓜',
'阿斯图里亚斯' => '阿斯圖里亞斯',
'阿呆' => '阿獃',
+'阿里' => '阿里',
'附于' => '附於',
'附注' => '附註',
'降压药' => '降壓藥',
@@ -8886,6 +8920,9 @@ $zh2Hant = array(
'阴历史' => '陰歷史',
'阴沟里翻船' => '陰溝裡翻船',
'阴郁' => '陰鬱',
+'陈冲' => '陳冲',
+'陳冲' => '陳冲',
+'陈有后' => '陳有后',
'陈炼' => '陳鍊',
'陆游' => '陸遊',
'阳春面' => '陽春麵',
@@ -8893,7 +8930,6 @@ $zh2Hant = array(
'阳历史' => '陽歷史',
'隆准许' => '隆准許',
'隆准' => '隆準',
-'随后' => '隨後',
'随于' => '隨於',
'隐占' => '隱佔',
'隐几' => '隱几',
@@ -8909,7 +8945,6 @@ $zh2Hant = array(
'雅致' => '雅緻',
'集于' => '集於',
'集游法' => '集遊法',
-'雇佣' => '雇傭',
'雕梁画栋' => '雕樑畫棟',
'双折射' => '雙折射',
'双折' => '雙摺',
@@ -8929,7 +8964,6 @@ $zh2Hant = array(
'离于' => '離於',
'难舍' => '難捨',
'难于' => '難於',
-'雨后' => '雨後',
'雪窗萤几' => '雪窗螢几',
'雪里' => '雪裡',
'雪里红' => '雪裡紅',
@@ -8939,8 +8973,10 @@ $zh2Hant = array(
'云游' => '雲遊',
'云须' => '雲鬚',
'零个' => '零個',
+'零周后' => '零周後',
'零多只' => '零多隻',
'零天后' => '零天後',
+'零年' => '零年',
'零只' => '零隻',
'零余' => '零餘',
'电子表格' => '電子表格',
@@ -8948,6 +8984,7 @@ $zh2Hant = array(
'电子钟' => '電子鐘',
'电子钟表' => '電子鐘錶',
'电杆' => '電杆',
+'电波钟' => '電波鐘',
'电码表' => '電碼表',
'电线杆' => '電線杆',
'电冲' => '電衝',
@@ -8967,7 +9004,6 @@ $zh2Hant = array(
'青霉素' => '青霉素',
'青霉' => '青黴',
'非占不可' => '非佔不可',
-'靠后' => '靠後',
'面包住' => '面包住',
'面包含' => '面包含',
'面包围' => '面包圍',
@@ -9014,6 +9050,7 @@ $zh2Hant = array(
'项庄' => '項莊',
'顺于' => '順於',
'顺钟向' => '順鐘向',
+'顺风后' => '順風後',
'须根据' => '須根據',
'颂系' => '頌繫',
'颂赞' => '頌讚',
@@ -9032,14 +9069,16 @@ $zh2Hant = array(
'颠干倒坤' => '顛乾倒坤',
'颠覆' => '顛覆',
'颠颠仆仆' => '顛顛仆仆',
-'顾前不顾后' => '顧前不顧後',
+'顛顛仆仆' => '顛顛仆仆',
'颤栗' => '顫慄',
'显示表' => '顯示錶',
'显示钟' => '顯示鐘',
'显示钟表' => '顯示鐘錶',
'显著标志' => '顯著標志',
'风干' => '風乾',
+'风后' => '風后',
'风土志' => '風土誌',
+'风后,' => '風後,',
'风卷残云' => '風捲殘雲',
'风物志' => '風物誌',
'风范' => '風範',
@@ -9048,6 +9087,7 @@ $zh2Hant = array(
'风采' => '風采',
'風采' => '風采',
'台风' => '颱風',
+'台风后' => '颱風後',
'刮了' => '颳了',
'刮倒' => '颳倒',
'刮去' => '颳去',
@@ -9056,6 +9096,7 @@ $zh2Hant = array(
'刮起' => '颳起',
'刮雪' => '颳雪',
'刮风' => '颳風',
+'刮风后' => '颳風後',
'飘荡' => '飄蕩',
'飘游' => '飄遊',
'飘飘荡荡' => '飄飄蕩蕩',
@@ -9066,7 +9107,6 @@ $zh2Hant = array(
'食欲不振' => '食欲不振',
'食野之苹' => '食野之苹',
'食面' => '食麵',
-'饭后' => '飯後',
'饭后钟' => '飯後鐘',
'饭团' => '飯糰',
'饭庄' => '飯莊',
@@ -9167,6 +9207,7 @@ $zh2Hant = array(
'余辉' => '餘輝',
'余辜' => '餘辜',
'余酲' => '餘酲',
+'余量' => '餘量',
'余闰' => '餘閏',
'余闲' => '餘閒',
'余零' => '餘零',
@@ -9191,8 +9232,6 @@ $zh2Hant = array(
'余8' => '餘8',
'余9' => '餘9',
'馄饨面' => '餛飩麵',
-'馆后一街' => '館後一街',
-'馆后二街' => '館後二街',
'馆谷' => '館穀',
'馆里' => '館裡',
'喂乳' => '餵乳',
@@ -9225,11 +9264,14 @@ $zh2Hant = array(
'马干' => '馬乾',
'马占山' => '馬占山',
'馬占山' => '馬占山',
-'马后' => '馬後',
'马杆' => '馬杆',
+'馬格里布' => '馬格里布',
+'马格里布' => '馬格里布',
'马表' => '馬錶',
'驻扎' => '駐紮',
'骀荡' => '駘蕩',
+'腾格里' => '騰格里',
+'騰格里' => '騰格里',
'腾冲' => '騰衝',
'惊赞' => '驚讚',
'惊钟' => '驚鐘',
@@ -9260,6 +9302,7 @@ $zh2Hant = array(
'高干预' => '高干預',
'高干' => '高幹',
'高度自制' => '高度自制',
+'高清愿' => '高清愿',
'髡发' => '髡髮',
'髭胡' => '髭鬍',
'髭须' => '髭鬚',
@@ -9483,6 +9526,7 @@ $zh2Hant = array(
'鬼谷子' => '鬼谷子',
'魂牵梦系' => '魂牽夢繫',
'魏征' => '魏徵',
+'魔杰座' => '魔杰座',
'魔表' => '魔錶',
'鱼干' => '魚乾',
'鱼松' => '魚鬆',
@@ -9579,13 +9623,18 @@ $zh2Hant = array(
'黄历' => '黃曆',
'黄曲霉' => '黃曲霉',
'黄历史' => '黃歷史',
+'黃詩杰' => '黃詩杰',
+'黄诗杰' => '黃詩杰',
'黄金表' => '黃金表',
'黃鈺筑' => '黃鈺筑',
'黄钰筑' => '黃鈺筑',
'黄钟' => '黃鐘',
'黄发' => '黃髮',
'黄曲毒素' => '黃麴毒素',
+'黎吉云' => '黎吉雲',
+'黎吉雲' => '黎吉雲',
'黑奴吁天录' => '黑奴籲天錄',
+'黑色长发' => '黑色長髮',
'黑发' => '黑髮',
'点半钟' => '點半鐘',
'点多钟' => '點多鐘',
@@ -9612,7 +9661,6 @@ $zh2Hant = array(
'出剧' => '齣劇',
'出动画' => '齣動畫',
'出卡通' => '齣卡通',
-'出子' => '齣子',
'出戏' => '齣戲',
'出节目' => '齣節目',
'出电影' => '齣電影',
@@ -9624,35 +9672,55 @@ $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餘',
':克制' => ':剋制',
@@ -10616,6 +10684,7 @@ $zh2Hans = array(
'產' => '产',
'産' => '产',
'甦' => '苏',
+'甯' => '宁',
'畝' => '亩',
'畢' => '毕',
'畫' => '画',
@@ -10670,6 +10739,7 @@ $zh2Hans = array(
'盤' => '盘',
'盧' => '卢',
'盪' => '荡',
+'眞' => '真',
'眥' => '眦',
'眾' => '众',
'睍' => '𪾢',
@@ -12566,6 +12636,7 @@ $zh2Hans = array(
'龍' => '龙',
'龎' => '厐',
'龐' => '庞',
+'龑' => '䶮',
'龔' => '龚',
'龕' => '龛',
'龜' => '龟',
@@ -12656,11 +12727,14 @@ $zh2Hans = array(
'與著者' => '与著者',
'與著述' => '与著述',
'丑著' => '丑着',
+'丑著书' => '丑著书',
'丑著書' => '丑著书',
'丑著作' => '丑著作',
'丑著名' => '丑著名',
+'丑著录' => '丑著录',
'丑著錄' => '丑著录',
'丑著稱' => '丑著称',
+'丑著称' => '丑著称',
'丑著者' => '丑著者',
'丑著述' => '丑著述',
'專著' => '专著',
@@ -12689,13 +12763,20 @@ $zh2Hans = array(
'樂著者' => '乐著者',
'樂著述' => '乐著述',
'乘著' => '乘着',
+'乘著书' => '乘著书',
'乘著書' => '乘著书',
'乘著作' => '乘著作',
'乘著名' => '乘著名',
+'乘著录' => '乘著录',
'乘著錄' => '乘著录',
'乘著稱' => '乘著称',
+'乘著称' => '乘著称',
'乘著者' => '乘著者',
'乘著述' => '乘著述',
+'乾一坛' => '乾一坛',
+'乾一壇' => '乾一坛',
+'乾一组' => '乾一组',
+'乾一組' => '乾一组',
'乾上乾下' => '乾上乾下',
'乾為天' => '乾为天',
'乾為陽' => '乾为阳',
@@ -12765,8 +12846,8 @@ $zh2Hans = array(
'乾旦' => '乾旦',
'乾明' => '乾明',
'乾昧' => '乾昧',
-'乾暉' => '乾晖',
'乾晖' => '乾晖',
+'乾暉' => '乾晖',
'乾景' => '乾景',
'乾晷' => '乾晷',
'乾曜' => '乾曜',
@@ -12830,8 +12911,8 @@ $zh2Hans = array(
'乾贶' => '乾贶',
'乾车' => '乾车',
'乾車' => '乾车',
-'乾轴' => '乾轴',
'乾軸' => '乾轴',
+'乾轴' => '乾轴',
'乾通' => '乾通',
'乾造' => '乾造',
'乾道' => '乾道',
@@ -12870,26 +12951,35 @@ $zh2Hans = array(
'爭著述' => '争著述',
'五箇山' => '五箇山',
'亮著' => '亮着',
+'亮著书' => '亮著书',
'亮著書' => '亮著书',
'亮著作' => '亮著作',
'亮著名' => '亮著名',
'亮著錄' => '亮著录',
+'亮著录' => '亮著录',
+'亮著称' => '亮著称',
'亮著稱' => '亮著称',
'亮著者' => '亮著者',
'亮著述' => '亮著述',
'仗著' => '仗着',
+'仗著书' => '仗著书',
'仗著書' => '仗著书',
'仗著作' => '仗著作',
'仗著名' => '仗著名',
+'仗著录' => '仗著录',
'仗著錄' => '仗著录',
'仗著稱' => '仗著称',
+'仗著称' => '仗著称',
'仗著者' => '仗著者',
'仗著述' => '仗著述',
'代表著' => '代表着',
'代表著書' => '代表著书',
+'代表著书' => '代表著书',
'代表著作' => '代表著作',
'代表著名' => '代表著名',
'代表著錄' => '代表著录',
+'代表著录' => '代表著录',
+'代表著称' => '代表著称',
'代表著稱' => '代表著称',
'代表著者' => '代表著者',
'代表著述' => '代表著述',
@@ -12906,26 +12996,35 @@ $zh2Hans = array(
'傳著者' => '传著者',
'傳著述' => '传著述',
'伴著' => '伴着',
+'伴著书' => '伴著书',
'伴著書' => '伴著书',
'伴著作' => '伴著作',
'伴著名' => '伴著名',
+'伴著录' => '伴著录',
'伴著錄' => '伴著录',
'伴著稱' => '伴著称',
+'伴著称' => '伴著称',
'伴著者' => '伴著者',
'伴著述' => '伴著述',
'低著' => '低着',
'低著書' => '低著书',
+'低著书' => '低著书',
'低著作' => '低著作',
'低著名' => '低著名',
+'低著录' => '低著录',
'低著錄' => '低著录',
'低著稱' => '低著称',
+'低著称' => '低著称',
'低著者' => '低著者',
'低著述' => '低著述',
'住著' => '住着',
'住著書' => '住著书',
+'住著书' => '住著书',
'住著作' => '住著作',
'住著名' => '住著名',
'住著錄' => '住著录',
+'住著录' => '住著录',
+'住著称' => '住著称',
'住著稱' => '住著称',
'住著者' => '住著者',
'住著述' => '住著述',
@@ -12941,28 +13040,37 @@ $zh2Hans = array(
'側著述' => '侧著述',
'保護著' => '保护着',
'保障著' => '保障着',
+'保障著书' => '保障著书',
'保障著書' => '保障著书',
'保障著作' => '保障著作',
'保障著名' => '保障著名',
'保障著錄' => '保障著录',
+'保障著录' => '保障著录',
'保障著稱' => '保障著称',
+'保障著称' => '保障著称',
'保障著者' => '保障著者',
'保障著述' => '保障著述',
'信著' => '信着',
+'信著书' => '信著书',
'信著書' => '信著书',
'信著作' => '信著作',
'信著名' => '信著名',
+'信著录' => '信著录',
'信著錄' => '信著录',
+'信著称' => '信著称',
'信著稱' => '信著称',
'信著者' => '信著者',
'信著述' => '信著述',
'修鍊' => '修炼',
'候著' => '候着',
'候著書' => '候著书',
+'候著书' => '候著书',
'候著作' => '候著作',
'候著名' => '候著名',
+'候著录' => '候著录',
'候著錄' => '候著录',
'候著稱' => '候著称',
+'候著称' => '候著称',
'候著者' => '候著者',
'候著述' => '候著述',
'藉助' => '借助',
@@ -12977,36 +13085,48 @@ $zh2Hans = array(
'藉著' => '借着',
'藉端' => '借端',
'借著書' => '借著书',
+'借著书' => '借著书',
'借著作' => '借著作',
'借著名' => '借著名',
+'借著录' => '借著录',
'借著錄' => '借著录',
+'借著称' => '借著称',
'借著稱' => '借著称',
'借著者' => '借著者',
'借著述' => '借著述',
'藉詞' => '借词',
'做著' => '做着',
'做著書' => '做著书',
+'做著书' => '做著书',
'做著作' => '做著作',
'做著名' => '做著名',
'做著錄' => '做著录',
+'做著录' => '做著录',
'做著稱' => '做著称',
+'做著称' => '做著称',
'做著者' => '做著者',
'做著述' => '做著述',
'偷著' => '偷着',
'偷著書' => '偷著书',
+'偷著书' => '偷著书',
'偷著作' => '偷著作',
'偷著名' => '偷著名',
'偷著錄' => '偷著录',
+'偷著录' => '偷著录',
'偷著稱' => '偷著称',
+'偷著称' => '偷著称',
'偷著者' => '偷著者',
'偷著述' => '偷著述',
'傢俬' => '傢俬',
'光著' => '光着',
'光著書' => '光著书',
+'光著书' => '光著书',
'光著作' => '光著作',
'光著名' => '光著名',
'光著錄' => '光著录',
+'光著录' => '光著录',
'光著稱' => '光著称',
+'光著称' => '光著称',
'光著者' => '光著者',
'光著述' => '光著述',
'關著' => '关着',
@@ -13019,18 +13139,24 @@ $zh2Hans = array(
'關著述' => '关著述',
'冀著' => '冀着',
'冀著書' => '冀著书',
+'冀著书' => '冀著书',
'冀著作' => '冀著作',
'冀著名' => '冀著名',
'冀著錄' => '冀著录',
+'冀著录' => '冀著录',
'冀著稱' => '冀著称',
+'冀著称' => '冀著称',
'冀著者' => '冀著者',
'冀著述' => '冀著述',
'冒著' => '冒着',
+'冒著书' => '冒著书',
'冒著書' => '冒著书',
'冒著作' => '冒著作',
'冒著名' => '冒著名',
+'冒著录' => '冒著录',
'冒著錄' => '冒著录',
'冒著稱' => '冒著称',
+'冒著称' => '冒著称',
'冒著者' => '冒著者',
'冒著述' => '冒著述',
'寫著' => '写着',
@@ -13051,19 +13177,25 @@ $zh2Hans = array(
'涼著述' => '凉著述',
'憑藉' => '凭借',
'制著' => '制着',
+'制著书' => '制著书',
'制著書' => '制著书',
'制著作' => '制著作',
'制著名' => '制著名',
'制著錄' => '制著录',
+'制著录' => '制著录',
+'制著称' => '制著称',
'制著稱' => '制著称',
'制著者' => '制著者',
'制著述' => '制著述',
'刻著' => '刻着',
'刻著書' => '刻著书',
+'刻著书' => '刻著书',
'刻著作' => '刻著作',
'刻著名' => '刻著名',
+'刻著录' => '刻著录',
'刻著錄' => '刻著录',
'刻著稱' => '刻著称',
+'刻著称' => '刻著称',
'刻著者' => '刻著者',
'刻著述' => '刻著述',
'辦著' => '办着',
@@ -13084,26 +13216,35 @@ $zh2Hans = array(
'動著述' => '动著述',
'努力著' => '努力着',
'努力著書' => '努力著书',
+'努力著书' => '努力著书',
'努力著作' => '努力著作',
'努力著名' => '努力著名',
'努力著錄' => '努力著录',
+'努力著录' => '努力著录',
+'努力著称' => '努力著称',
'努力著稱' => '努力著称',
'努力著者' => '努力著者',
'努力著述' => '努力著述',
'努著' => '努着',
'努著書' => '努著书',
+'努著书' => '努著书',
'努著作' => '努著作',
'努著名' => '努著名',
'努著錄' => '努著录',
+'努著录' => '努著录',
+'努著称' => '努著称',
'努著稱' => '努著称',
'努著者' => '努著者',
'努著述' => '努著述',
'卓著' => '卓著',
'印著' => '印着',
+'印著书' => '印著书',
'印著書' => '印著书',
'印著作' => '印著作',
'印著名' => '印著名',
+'印著录' => '印著录',
'印著錄' => '印著录',
+'印著称' => '印著称',
'印著稱' => '印著称',
'印著者' => '印著者',
'印著述' => '印著述',
@@ -13118,21 +13259,27 @@ $zh2Hans = array(
'壓著述' => '压著述',
'原著' => '原著',
'去著' => '去着',
+'去著书' => '去著书',
'去著書' => '去著书',
'去著作' => '去著作',
'去著名' => '去著名',
+'去著录' => '去著录',
'去著錄' => '去著录',
'去著稱' => '去著称',
+'去著称' => '去著称',
'去著者' => '去著者',
'去著述' => '去著述',
'反反覆覆' => '反反复复',
'反覆' => '反复',
'受著' => '受着',
'受著書' => '受著书',
+'受著书' => '受著书',
'受著作' => '受著作',
'受著名' => '受著名',
'受著錄' => '受著录',
+'受著录' => '受著录',
'受著稱' => '受著称',
+'受著称' => '受著称',
'受著者' => '受著者',
'受著述' => '受著述',
'變著' => '变着',
@@ -13144,10 +13291,13 @@ $zh2Hans = array(
'變著者' => '变著者',
'變著述' => '变著述',
'叫著' => '叫着',
+'叫著书' => '叫著书',
'叫著書' => '叫著书',
'叫著作' => '叫著作',
'叫著名' => '叫著名',
+'叫著录' => '叫著录',
'叫著錄' => '叫著录',
+'叫著称' => '叫著称',
'叫著稱' => '叫著称',
'叫著者' => '叫著者',
'叫著述' => '叫著述',
@@ -13161,17 +13311,23 @@ $zh2Hans = array(
'名著' => '名著',
'向著' => '向着',
'向著書' => '向著书',
+'向著书' => '向著书',
'向著作' => '向著作',
'向著名' => '向著名',
'向著錄' => '向著录',
+'向著录' => '向著录',
+'向著称' => '向著称',
'向著稱' => '向著称',
'向著者' => '向著者',
'向著述' => '向著述',
'含著' => '含着',
'含著書' => '含著书',
+'含著书' => '含著书',
'含著作' => '含著作',
'含著名' => '含著名',
'含著錄' => '含著录',
+'含著录' => '含著录',
+'含著称' => '含著称',
'含著稱' => '含著称',
'含著者' => '含著者',
'含著述' => '含著述',
@@ -13189,18 +13345,24 @@ $zh2Hans = array(
'吳其濬' => '吴其濬',
'吹著' => '吹着',
'吹著書' => '吹著书',
+'吹著书' => '吹著书',
'吹著作' => '吹著作',
'吹著名' => '吹著名',
+'吹著录' => '吹著录',
'吹著錄' => '吹著录',
'吹著稱' => '吹著称',
+'吹著称' => '吹著称',
'吹著者' => '吹著者',
'吹著述' => '吹著述',
'周易乾' => '周易乾',
'味著' => '味着',
+'味著书' => '味著书',
'味著書' => '味著书',
'味著作' => '味著作',
'味著名' => '味著名',
+'味著录' => '味著录',
'味著錄' => '味著录',
+'味著称' => '味著称',
'味著稱' => '味著称',
'味著者' => '味著者',
'味著述' => '味著述',
@@ -13216,26 +13378,35 @@ $zh2Hans = array(
'哪吒' => '哪吒',
'哭著' => '哭着',
'哭著書' => '哭著书',
+'哭著书' => '哭著书',
'哭著作' => '哭著作',
'哭著名' => '哭著名',
'哭著錄' => '哭著录',
+'哭著录' => '哭著录',
'哭著稱' => '哭著称',
+'哭著称' => '哭著称',
'哭著者' => '哭著者',
'哭著述' => '哭著述',
'唱著' => '唱着',
+'唱著书' => '唱著书',
'唱著書' => '唱著书',
'唱著作' => '唱著作',
'唱著名' => '唱著名',
+'唱著录' => '唱著录',
'唱著錄' => '唱著录',
+'唱著称' => '唱著称',
'唱著稱' => '唱著称',
'唱著者' => '唱著者',
'唱著述' => '唱著述',
'喝著' => '喝着',
+'喝著书' => '喝著书',
'喝著書' => '喝著书',
'喝著作' => '喝著作',
'喝著名' => '喝著名',
+'喝著录' => '喝著录',
'喝著錄' => '喝著录',
'喝著稱' => '喝著称',
+'喝著称' => '喝著称',
'喝著者' => '喝著者',
'喝著述' => '喝著述',
'嗅不著' => '嗅不着',
@@ -13243,9 +13414,12 @@ $zh2Hans = array(
'嗅著' => '嗅着',
'嚷著' => '嚷着',
'嚷著書' => '嚷著书',
+'嚷著书' => '嚷著书',
'嚷著作' => '嚷著作',
'嚷著名' => '嚷著名',
'嚷著錄' => '嚷著录',
+'嚷著录' => '嚷著录',
+'嚷著称' => '嚷著称',
'嚷著稱' => '嚷著称',
'嚷著者' => '嚷著者',
'嚷著述' => '嚷著述',
@@ -13254,17 +13428,23 @@ $zh2Hans = array(
'因著〈' => '因著〈',
'因著《' => '因著《',
'因著書' => '因著书',
+'因著书' => '因著书',
'因著作' => '因著作',
'因著名' => '因著名',
'因著錄' => '因著录',
+'因著录' => '因著录',
'因著稱' => '因著称',
+'因著称' => '因著称',
'因著者' => '因著者',
'因著述' => '因著述',
'困著' => '困着',
'困著書' => '困著书',
+'困著书' => '困著书',
'困著作' => '困著作',
'困著名' => '困著名',
'困著錄' => '困著录',
+'困著录' => '困著录',
+'困著称' => '困著称',
'困著稱' => '困著称',
'困著者' => '困著者',
'困著述' => '困著述',
@@ -13279,17 +13459,23 @@ $zh2Hans = array(
'土著' => '土著',
'在著' => '在着',
'在著書' => '在著书',
+'在著书' => '在著书',
'在著作' => '在著作',
'在著名' => '在著名',
'在著錄' => '在著录',
+'在著录' => '在著录',
'在著稱' => '在著称',
+'在著称' => '在著称',
'在著者' => '在著者',
'在著述' => '在著述',
'坐著' => '坐着',
+'坐著书' => '坐著书',
'坐著書' => '坐著书',
'坐著作' => '坐著作',
'坐著名' => '坐著名',
+'坐著录' => '坐著录',
'坐著錄' => '坐著录',
+'坐著称' => '坐著称',
'坐著稱' => '坐著称',
'坐著者' => '坐著者',
'坐著述' => '坐著述',
@@ -13302,8 +13488,11 @@ $zh2Hans = array(
'備著稱' => '备著称',
'備著者' => '备著者',
'備著述' => '备著述',
+'覆查' => '复查',
+'覆核' => '复核',
'天道为乾' => '天道为乾',
'天道為乾' => '天道为乾',
+'太閤' => '太阁',
'夾著' => '夹着',
'夾著書' => '夹著书',
'夾著作' => '夹著作',
@@ -13314,12 +13503,16 @@ $zh2Hans = array(
'夾著述' => '夹著述',
'奧區' => '奧区',
'姓幺' => '姓幺',
+'字乾生' => '字乾生',
'存摺' => '存摺',
'孤著' => '孤着',
+'孤著书' => '孤著书',
'孤著書' => '孤著书',
'孤著作' => '孤著作',
'孤著名' => '孤著名',
'孤著錄' => '孤著录',
+'孤著录' => '孤著录',
+'孤著称' => '孤著称',
'孤著稱' => '孤著称',
'孤著者' => '孤著者',
'孤著述' => '孤著述',
@@ -13333,17 +13526,23 @@ $zh2Hans = array(
'學著述' => '学著述',
'守著' => '守着',
'守著書' => '守著书',
+'守著书' => '守著书',
'守著作' => '守著作',
'守著名' => '守著名',
+'守著录' => '守著录',
'守著錄' => '守著录',
+'守著称' => '守著称',
'守著稱' => '守著称',
'守著者' => '守著者',
'守著述' => '守著述',
'定著' => '定着',
'定著書' => '定著书',
+'定著书' => '定著书',
'定著作' => '定著作',
'定著名' => '定著名',
'定著錄' => '定著录',
+'定著录' => '定著录',
+'定著称' => '定著称',
'定著稱' => '定著称',
'定著者' => '定著者',
'定著述' => '定著述',
@@ -13367,12 +13566,16 @@ $zh2Hans = array(
'尼乾陀' => '尼乾陀',
'展著' => '展着',
'展著書' => '展著书',
+'展著书' => '展著书',
'展著作' => '展著作',
'展著名' => '展著名',
'展著錄' => '展著录',
+'展著录' => '展著录',
'展著稱' => '展著称',
+'展著称' => '展著称',
'展著者' => '展著者',
'展著述' => '展著述',
+'峯岸南' => '峯岸南',
'巨著' => '巨著',
'帶著' => '带着',
'帶著書' => '带著书',
@@ -13404,8 +13607,8 @@ $zh2Hans = array(
'幺半群' => '幺半群',
'幺廝' => '幺厮',
'幺厮' => '幺厮',
-'么叔' => '幺叔',
'幺叔' => '幺叔',
+'么叔' => '幺叔',
'么媽' => '幺妈',
'幺媽' => '幺妈',
'么妹' => '幺妹',
@@ -13449,10 +13652,13 @@ $zh2Hans = array(
'應著述' => '应著述',
'康乾' => '康乾',
'康著' => '康着',
+'康著书' => '康著书',
'康著書' => '康著书',
'康著作' => '康著作',
'康著名' => '康著名',
+'康著录' => '康著录',
'康著錄' => '康著录',
+'康著称' => '康著称',
'康著稱' => '康著称',
'康著者' => '康著者',
'康著述' => '康著述',
@@ -13465,6 +13671,7 @@ $zh2Hans = array(
'開著者' => '开著者',
'開著述' => '开著述',
'張法乾' => '张法乾',
+'张法乾' => '张法乾',
'當著' => '当着',
'當著書' => '当著书',
'當著作' => '当著作',
@@ -13475,58 +13682,79 @@ $zh2Hans = array(
'當著述' => '当著述',
'彰明較著' => '彰明较著',
'待著' => '待着',
+'待著书' => '待著书',
'待著書' => '待著书',
'待著作' => '待著作',
'待著名' => '待著名',
+'待著录' => '待著录',
'待著錄' => '待著录',
'待著稱' => '待著称',
+'待著称' => '待著称',
'待著者' => '待著者',
'待著述' => '待著述',
'得著' => '得着',
'得著書' => '得著书',
+'得著书' => '得著书',
'得著作' => '得著作',
'得著名' => '得著名',
'得著錄' => '得著录',
+'得著录' => '得著录',
'得著稱' => '得著称',
+'得著称' => '得著称',
'得著者' => '得著者',
'得著述' => '得著述',
'循著' => '循着',
+'循著书' => '循著书',
'循著書' => '循著书',
'循著作' => '循著作',
'循著名' => '循著名',
+'循著录' => '循著录',
'循著錄' => '循著录',
+'循著称' => '循著称',
'循著稱' => '循著称',
'循著者' => '循著者',
'循著述' => '循著述',
'心著' => '心着',
+'心著书' => '心著书',
'心著書' => '心著书',
'心著作' => '心著作',
'心著名' => '心著名',
+'心著录' => '心著录',
'心著錄' => '心著录',
'心著稱' => '心著称',
+'心著称' => '心著称',
'心著者' => '心著者',
'心著述' => '心著述',
'忍著' => '忍着',
+'忍著书' => '忍著书',
'忍著書' => '忍著书',
'忍著作' => '忍著作',
'忍著名' => '忍著名',
+'忍著录' => '忍著录',
'忍著錄' => '忍著录',
'忍著稱' => '忍著称',
+'忍著称' => '忍著称',
'忍著者' => '忍著者',
'忍著述' => '忍著述',
'志著' => '志着',
'志著書' => '志著书',
+'志著书' => '志著书',
'志著作' => '志著作',
'志著名' => '志著名',
'志著錄' => '志著录',
+'志著录' => '志著录',
+'志著称' => '志著称',
'志著稱' => '志著称',
'志著者' => '志著者',
'志著述' => '志著述',
'忙著' => '忙着',
+'忙著书' => '忙著书',
'忙著書' => '忙著书',
'忙著作' => '忙著作',
'忙著名' => '忙著名',
+'忙著录' => '忙著录',
'忙著錄' => '忙著录',
+'忙著称' => '忙著称',
'忙著稱' => '忙著称',
'忙著者' => '忙著者',
'忙著述' => '忙著述',
@@ -13539,18 +13767,24 @@ $zh2Hans = array(
'懷著者' => '怀著者',
'懷著述' => '怀著述',
'急著' => '急着',
+'急著书' => '急著书',
'急著書' => '急著书',
'急著作' => '急著作',
'急著名' => '急著名',
+'急著录' => '急著录',
'急著錄' => '急著录',
+'急著称' => '急著称',
'急著稱' => '急著称',
'急著者' => '急著者',
'急著述' => '急著述',
'性著' => '性着',
+'性著书' => '性著书',
'性著書' => '性著书',
'性著作' => '性著作',
'性著名' => '性著名',
+'性著录' => '性著录',
'性著錄' => '性著录',
+'性著称' => '性著称',
'性著稱' => '性著称',
'性著者' => '性著者',
'性著述' => '性著述',
@@ -13565,9 +13799,12 @@ $zh2Hans = array(
'恩威並著' => '恩威并著',
'悠著' => '悠着',
'悠著書' => '悠著书',
+'悠著书' => '悠著书',
'悠著作' => '悠著作',
'悠著名' => '悠著名',
'悠著錄' => '悠著录',
+'悠著录' => '悠著录',
+'悠著称' => '悠著称',
'悠著稱' => '悠著称',
'悠著者' => '悠著者',
'悠著述' => '悠著述',
@@ -13581,9 +13818,12 @@ $zh2Hans = array(
'慣著述' => '惯著述',
'想著' => '想着',
'想著書' => '想著书',
+'想著书' => '想著书',
'想著作' => '想著作',
'想著名' => '想著名',
'想著錄' => '想著录',
+'想著录' => '想著录',
+'想著称' => '想著称',
'想著稱' => '想著称',
'想著者' => '想著者',
'想著述' => '想著述',
@@ -13597,33 +13837,45 @@ $zh2Hans = array(
'戰著述' => '战著述',
'戴著' => '戴着',
'戴著書' => '戴著书',
+'戴著书' => '戴著书',
'戴著作' => '戴著作',
'戴著名' => '戴著名',
'戴著錄' => '戴著录',
+'戴著录' => '戴著录',
'戴著稱' => '戴著称',
+'戴著称' => '戴著称',
'戴著者' => '戴著者',
'戴著述' => '戴著述',
'扎著' => '扎着',
'扎著書' => '扎著书',
+'扎著书' => '扎著书',
'扎著作' => '扎著作',
'扎著名' => '扎著名',
'扎著錄' => '扎著录',
+'扎著录' => '扎著录',
+'扎著称' => '扎著称',
'扎著稱' => '扎著称',
'扎著者' => '扎著者',
'扎著述' => '扎著述',
'打著' => '打着',
'打著書' => '打著书',
+'打著书' => '打著书',
'打著作' => '打著作',
'打著名' => '打著名',
'打著錄' => '打著录',
+'打著录' => '打著录',
+'打著称' => '打著称',
'打著稱' => '打著称',
'打著者' => '打著者',
'打著述' => '打著述',
'扛著' => '扛着',
+'扛著书' => '扛著书',
'扛著書' => '扛著书',
'扛著作' => '扛著作',
'扛著名' => '扛著名',
+'扛著录' => '扛著录',
'扛著錄' => '扛著录',
+'扛著称' => '扛著称',
'扛著稱' => '扛著称',
'扛著者' => '扛著者',
'扛著述' => '扛著述',
@@ -13633,7 +13885,9 @@ $zh2Hans = array(
'抓著' => '抓着',
'抓著作' => '抓著作',
'抓著名' => '抓著名',
+'抓著录' => '抓著录',
'抓著錄' => '抓著录',
+'抓著称' => '抓著称',
'抓著稱' => '抓著称',
'抓著者' => '抓著者',
'抓著述' => '抓著述',
@@ -13646,32 +13900,42 @@ $zh2Hans = array(
'護著者' => '护著者',
'護著述' => '护著述',
'披著' => '披着',
+'披著书' => '披著书',
'披著書' => '披著书',
'披著作' => '披著作',
'披著名' => '披著名',
+'披著录' => '披著录',
'披著錄' => '披著录',
'披著稱' => '披著称',
+'披著称' => '披著称',
'披著者' => '披著者',
'披著述' => '披著述',
'抬著' => '抬着',
'抬著作' => '抬著作',
'抬著名' => '抬著名',
+'抬著录' => '抬著录',
'抬著錄' => '抬著录',
'抬著稱' => '抬著称',
+'抬著称' => '抬著称',
'抬著者' => '抬著者',
'抬著述' => '抬著述',
'抱著' => '抱着',
'抱著作' => '抱著作',
'抱著名' => '抱著名',
+'抱著录' => '抱著录',
'抱著錄' => '抱著录',
'抱著稱' => '抱著称',
+'抱著称' => '抱著称',
'抱著者' => '抱著者',
'抱著述' => '抱著述',
'拉著' => '拉着',
+'拉著书' => '拉著书',
'拉著書' => '拉著书',
'拉著作' => '拉著作',
'拉著名' => '拉著名',
+'拉著录' => '拉著录',
'拉著錄' => '拉著录',
+'拉著称' => '拉著称',
'拉著稱' => '拉著称',
'拉著者' => '拉著者',
'拉著述' => '拉著述',
@@ -13680,6 +13944,8 @@ $zh2Hans = array(
'拎著作' => '拎著作',
'拎著名' => '拎著名',
'拎著錄' => '拎著录',
+'拎著录' => '拎著录',
+'拎著称' => '拎著称',
'拎著稱' => '拎著称',
'拎著者' => '拎著者',
'拎著述' => '拎著述',
@@ -13687,7 +13953,9 @@ $zh2Hans = array(
'拖著作' => '拖著作',
'拖著名' => '拖著名',
'拖著錄' => '拖著录',
+'拖著录' => '拖著录',
'拖著稱' => '拖著称',
+'拖著称' => '拖著称',
'拖著者' => '拖著者',
'拖著述' => '拖著述',
'拙著' => '拙著',
@@ -13697,14 +13965,18 @@ $zh2Hans = array(
'拼著' => '拼着',
'拼著作' => '拼著作',
'拼著名' => '拼著名',
+'拼著录' => '拼著录',
'拼著錄' => '拼著录',
'拼著稱' => '拼著称',
+'拼著称' => '拼著称',
'拼著者' => '拼著者',
'拼著述' => '拼著述',
'拿著' => '拿着',
'拿著作' => '拿著作',
'拿著名' => '拿著名',
+'拿著录' => '拿著录',
'拿著錄' => '拿著录',
+'拿著称' => '拿著称',
'拿著稱' => '拿著称',
'拿著者' => '拿著者',
'拿著述' => '拿著述',
@@ -13712,6 +13984,8 @@ $zh2Hans = array(
'持著作' => '持著作',
'持著名' => '持著名',
'持著錄' => '持著录',
+'持著录' => '持著录',
+'持著称' => '持著称',
'持著稱' => '持著称',
'持著者' => '持著者',
'持著述' => '持著述',
@@ -13719,6 +13993,8 @@ $zh2Hans = array(
'挑著作' => '挑著作',
'挑著名' => '挑著名',
'挑著錄' => '挑著录',
+'挑著录' => '挑著录',
+'挑著称' => '挑著称',
'挑著稱' => '挑著称',
'挑著者' => '挑著者',
'挑著述' => '挑著述',
@@ -13748,13 +14024,17 @@ $zh2Hans = array(
'挨著作' => '挨著作',
'挨著名' => '挨著名',
'挨著錄' => '挨著录',
+'挨著录' => '挨著录',
'挨著稱' => '挨著称',
+'挨著称' => '挨著称',
'挨著者' => '挨著者',
'挨著述' => '挨著述',
'捆著' => '捆着',
'捆著作' => '捆著作',
'捆著名' => '捆著名',
'捆著錄' => '捆著录',
+'捆著录' => '捆著录',
+'捆著称' => '捆著称',
'捆著稱' => '捆著称',
'捆著者' => '捆著者',
'捆著述' => '捆著述',
@@ -13770,21 +14050,28 @@ $zh2Hans = array(
'掖著作' => '掖著作',
'掖著名' => '掖著名',
'掖著錄' => '掖著录',
+'掖著录' => '掖著录',
'掖著稱' => '掖著称',
+'掖著称' => '掖著称',
'掖著者' => '掖著者',
'掖著述' => '掖著述',
'接著' => '接着',
'接著作' => '接著作',
'接著名' => '接著名',
'接著錄' => '接著录',
+'接著录' => '接著录',
'接著稱' => '接著称',
+'接著称' => '接著称',
'接著者' => '接著者',
'接著述' => '接著述',
'揉著' => '揉着',
+'揉著书' => '揉著书',
'揉著書' => '揉著书',
'揉著作' => '揉著作',
'揉著名' => '揉著名',
+'揉著录' => '揉著录',
'揉著錄' => '揉著录',
+'揉著称' => '揉著称',
'揉著稱' => '揉著称',
'揉著者' => '揉著者',
'揉著述' => '揉著述',
@@ -13792,7 +14079,9 @@ $zh2Hans = array(
'提著作' => '提著作',
'提著名' => '提著名',
'提著錄' => '提著录',
+'提著录' => '提著录',
'提著稱' => '提著称',
+'提著称' => '提著称',
'提著者' => '提著者',
'提著述' => '提著述',
'摟著' => '搂着',
@@ -13812,9 +14101,12 @@ $zh2Hans = array(
'撰著' => '撰著',
'撼著' => '撼着',
'撼著書' => '撼著书',
+'撼著书' => '撼著书',
'撼著作' => '撼著作',
'撼著名' => '撼著名',
'撼著錄' => '撼著录',
+'撼著录' => '撼著录',
+'撼著称' => '撼著称',
'撼著稱' => '撼著称',
'撼著者' => '撼著者',
'撼著述' => '撼著述',
@@ -13822,7 +14114,9 @@ $zh2Hans = array(
'敞著作' => '敞著作',
'敞著名' => '敞著名',
'敞著錄' => '敞著录',
+'敞著录' => '敞著录',
'敞著稱' => '敞著称',
+'敞著称' => '敞著称',
'敞著者' => '敞著者',
'敞著述' => '敞著述',
'數著' => '数着',
@@ -13834,18 +14128,24 @@ $zh2Hans = array(
'數著述' => '数著述',
'斗著' => '斗着',
'斗著書' => '斗著书',
+'斗著书' => '斗著书',
'斗著作' => '斗著作',
'斗著名' => '斗著名',
'斗著錄' => '斗著录',
+'斗著录' => '斗著录',
+'斗著称' => '斗著称',
'斗著稱' => '斗著称',
'斗著者' => '斗著者',
'斗著述' => '斗著述',
'斥著' => '斥着',
'斥著書' => '斥著书',
+'斥著书' => '斥著书',
'斥著作' => '斥著作',
'斥著名' => '斥著名',
'斥著錄' => '斥著录',
+'斥著录' => '斥著录',
'斥著稱' => '斥著称',
+'斥著称' => '斥著称',
'斥著者' => '斥著者',
'斥著述' => '斥著述',
'新著' => '新著',
@@ -13873,14 +14173,18 @@ $zh2Hans = array(
'於菟' => '於菟',
'於賢德' => '於贤德',
'於除鞬' => '於除鞬',
+'旋乾转坤' => '旋乾转坤',
'旋乾轉坤' => '旋乾转坤',
'曠若發矇' => '旷若发矇',
'昂著' => '昂着',
+'昂著书' => '昂著书',
'昂著書' => '昂著书',
'昂著作' => '昂著作',
'昂著名' => '昂著名',
'昂著錄' => '昂著录',
+'昂著录' => '昂著录',
'昂著稱' => '昂著称',
+'昂著称' => '昂著称',
'昂著者' => '昂著者',
'昂著述' => '昂著述',
'易·乾' => '易·乾',
@@ -13890,10 +14194,13 @@ $zh2Hans = array(
'易经乾' => '易经乾',
'映著' => '映着',
'映著書' => '映著书',
+'映著书' => '映著书',
'映著作' => '映著作',
'映著名' => '映著名',
'映著錄' => '映著录',
+'映著录' => '映著录',
'映著稱' => '映著称',
+'映著称' => '映著称',
'映著者' => '映著者',
'映著述' => '映著述',
'昭著' => '昭著',
@@ -13903,46 +14210,61 @@ $zh2Hans = array(
'晃著作' => '晃著作',
'晃著名' => '晃著名',
'晃著錄' => '晃著录',
+'晃著录' => '晃著录',
+'晃著称' => '晃著称',
'晃著稱' => '晃著称',
'晃著者' => '晃著者',
'晃著述' => '晃著述',
'暗著' => '暗着',
+'暗著书' => '暗著书',
'暗著書' => '暗著书',
'暗著作' => '暗著作',
'暗著名' => '暗著名',
'暗著錄' => '暗著录',
+'暗著录' => '暗著录',
+'暗著称' => '暗著称',
'暗著稱' => '暗著称',
'暗著者' => '暗著者',
'暗著述' => '暗著述',
'有著' => '有着',
'有著書' => '有著书',
+'有著书' => '有著书',
'有著作' => '有著作',
'有著名' => '有著名',
'有著錄' => '有著录',
+'有著录' => '有著录',
+'有著称' => '有著称',
'有著稱' => '有著称',
'有著者' => '有著者',
'有著述' => '有著述',
'望著' => '望着',
'望著作' => '望著作',
'望著名' => '望著名',
+'望著录' => '望著录',
'望著錄' => '望著录',
'望著稱' => '望著称',
+'望著称' => '望著称',
'望著者' => '望著者',
'望著述' => '望著述',
'朝乾夕惕' => '朝乾夕惕',
'朝著' => '朝着',
'朝著作' => '朝著作',
'朝著名' => '朝著名',
+'朝著录' => '朝著录',
'朝著錄' => '朝著录',
'朝著稱' => '朝著称',
+'朝著称' => '朝著称',
'朝著者' => '朝著者',
'朝著述' => '朝著述',
'本著' => '本着',
+'本著书' => '本著书',
'本著書' => '本著书',
'本著作' => '本著作',
'本著名' => '本著名',
+'本著录' => '本著录',
'本著錄' => '本著录',
'本著稱' => '本著称',
+'本著称' => '本著称',
'本著者' => '本著者',
'本著述' => '本著述',
'朴於宇同' => '朴於宇同',
@@ -13979,7 +14301,9 @@ $zh2Hans = array(
'枕著作' => '枕著作',
'枕著名' => '枕著名',
'枕著錄' => '枕著录',
+'枕著录' => '枕著录',
'枕著稱' => '枕著称',
+'枕著称' => '枕著称',
'枕著者' => '枕著者',
'枕著述' => '枕著述',
'柳詒徵' => '柳诒徵',
@@ -13998,16 +14322,21 @@ $zh2Hans = array(
'梳著作' => '梳著作',
'梳著名' => '梳著名',
'梳著錄' => '梳著录',
+'梳著录' => '梳著录',
'梳著稱' => '梳著称',
+'梳著称' => '梳著称',
'梳著者' => '梳著者',
'梳著述' => '梳著述',
'樊於期' => '樊於期',
'氆氌' => '氆氌',
'求著' => '求着',
+'求著书' => '求著书',
'求著書' => '求著书',
'求著作' => '求著作',
'求著名' => '求著名',
+'求著录' => '求著录',
'求著錄' => '求著录',
+'求著称' => '求著称',
'求著稱' => '求著称',
'求著者' => '求著者',
'求著述' => '求著述',
@@ -14016,46 +14345,61 @@ $zh2Hans = array(
'沈積' => '沉积',
'沈船' => '沉船',
'沉著書' => '沉著书',
+'沉著书' => '沉著书',
'沉著作' => '沉著作',
'沉著名' => '沉著名',
'沉著錄' => '沉著录',
+'沉著录' => '沉著录',
+'沉著称' => '沉著称',
'沉著稱' => '沉著称',
'沉著者' => '沉著者',
'沉著述' => '沉著述',
'沈默' => '沉默',
'沿著' => '沿着',
+'沿著书' => '沿著书',
'沿著書' => '沿著书',
'沿著作' => '沿著作',
'沿著名' => '沿著名',
+'沿著录' => '沿著录',
'沿著錄' => '沿著录',
'沿著稱' => '沿著称',
+'沿著称' => '沿著称',
'沿著者' => '沿著者',
'沿著述' => '沿著述',
'氾濫' => '泛滥',
'洗鍊' => '洗练',
'活著' => '活着',
+'活著书' => '活著书',
'活著書' => '活著书',
'活著作' => '活著作',
'活著名' => '活著名',
+'活著录' => '活著录',
'活著錄' => '活著录',
'活著稱' => '活著称',
+'活著称' => '活著称',
'活著者' => '活著者',
'活著述' => '活著述',
'流著' => '流着',
+'流著书' => '流著书',
'流著書' => '流著书',
'流著作' => '流著作',
'流著名' => '流著名',
+'流著录' => '流著录',
'流著錄' => '流著录',
'流著稱' => '流著称',
+'流著称' => '流著称',
'流著者' => '流著者',
'流著述' => '流著述',
'流露著' => '流露着',
'浮著' => '浮着',
+'浮著书' => '浮著书',
'浮著書' => '浮著书',
'浮著作' => '浮著作',
'浮著名' => '浮著名',
+'浮著录' => '浮著录',
'浮著錄' => '浮著录',
'浮著稱' => '浮著称',
+'浮著称' => '浮著称',
'浮著者' => '浮著者',
'浮著述' => '浮著述',
'潤著' => '润着',
@@ -14067,42 +14411,57 @@ $zh2Hans = array(
'潤著者' => '润著者',
'潤著述' => '润著述',
'涵著' => '涵着',
+'涵著书' => '涵著书',
'涵著書' => '涵著书',
'涵著作' => '涵著作',
'涵著名' => '涵著名',
+'涵著录' => '涵著录',
'涵著錄' => '涵著录',
'涵著稱' => '涵著称',
+'涵著称' => '涵著称',
'涵著者' => '涵著者',
'涵著述' => '涵著述',
'渴著' => '渴着',
+'渴著书' => '渴著书',
'渴著書' => '渴著书',
'渴著作' => '渴著作',
'渴著名' => '渴著名',
+'渴著录' => '渴著录',
'渴著錄' => '渴著录',
+'渴著称' => '渴著称',
'渴著稱' => '渴著称',
'渴著者' => '渴著者',
'渴著述' => '渴著述',
'溢著' => '溢着',
'溢著書' => '溢著书',
+'溢著书' => '溢著书',
'溢著作' => '溢著作',
'溢著名' => '溢著名',
'溢著錄' => '溢著录',
+'溢著录' => '溢著录',
+'溢著称' => '溢著称',
'溢著稱' => '溢著称',
'溢著者' => '溢著者',
'溢著述' => '溢著述',
'演著' => '演着',
+'演著书' => '演著书',
'演著書' => '演著书',
'演著作' => '演著作',
'演著名' => '演著名',
+'演著录' => '演著录',
'演著錄' => '演著录',
'演著稱' => '演著称',
+'演著称' => '演著称',
'演著者' => '演著者',
'演著述' => '演著述',
'漫著' => '漫着',
'漫著書' => '漫著书',
+'漫著书' => '漫著书',
'漫著作' => '漫著作',
'漫著名' => '漫著名',
+'漫著录' => '漫著录',
'漫著錄' => '漫著录',
+'漫著称' => '漫著称',
'漫著稱' => '漫著称',
'漫著者' => '漫著者',
'漫著述' => '漫著述',
@@ -14121,10 +14480,13 @@ $zh2Hans = array(
'燒著者' => '烧著者',
'燒著述' => '烧著述',
'照著' => '照着',
+'照著书' => '照著书',
'照著書' => '照著书',
'照著作' => '照著作',
'照著名' => '照著名',
+'照著录' => '照著录',
'照著錄' => '照著录',
+'照著称' => '照著称',
'照著稱' => '照著称',
'照著者' => '照著者',
'照著述' => '照著述',
@@ -14160,41 +14522,60 @@ $zh2Hans = array(
'猜著作' => '猜著作',
'猜著名' => '猜著名',
'猜著錄' => '猜著录',
+'猜著录' => '猜著录',
+'猜著称' => '猜著称',
'猜著稱' => '猜著称',
'猜著者' => '猜著者',
'猜著述' => '猜著述',
'玩著' => '玩着',
'甜著' => '甜着',
'甜著書' => '甜著书',
+'甜著书' => '甜著书',
'甜著作' => '甜著作',
'甜著名' => '甜著名',
+'甜著录' => '甜著录',
'甜著錄' => '甜著录',
'甜著稱' => '甜著称',
+'甜著称' => '甜著称',
'甜著者' => '甜著者',
'甜著述' => '甜著述',
'用不著' => '用不着',
'用得著' => '用得着',
'用著' => '用着',
+'用著书' => '用著书',
'用著書' => '用著书',
'用著作' => '用著作',
'用著名' => '用著名',
+'用著录' => '用著录',
'用著錄' => '用著录',
+'用著称' => '用著称',
'用著稱' => '用著称',
'用著者' => '用著者',
'用著述' => '用著述',
+'男为乾' => '男为乾',
+'男爲乾' => '男为乾',
+'男為乾' => '男为乾',
+'男性為乾' => '男性为乾',
+'男性爲乾' => '男性为乾',
+'男性为乾' => '男性为乾',
'留著' => '留着',
'留著書' => '留着书',
'留著作' => '留著作',
'留著名' => '留著名',
'留著錄' => '留著录',
+'留著录' => '留著录',
'留著稱' => '留著称',
+'留著称' => '留著称',
'留著者' => '留著者',
'留著述' => '留著述',
'疑著' => '疑着',
+'疑著书' => '疑著书',
'疑著書' => '疑著书',
'疑著作' => '疑著作',
'疑著名' => '疑著名',
+'疑著录' => '疑著录',
'疑著錄' => '疑著录',
+'疑著称' => '疑著称',
'疑著稱' => '疑著称',
'疑著者' => '疑著者',
'疑著述' => '疑著述',
@@ -14208,11 +14589,14 @@ $zh2Hans = array(
'皺著者' => '皱著者',
'皺著述' => '皱著述',
'盛著' => '盛着',
+'盛著书' => '盛著书',
'盛著書' => '盛著书',
'盛著作' => '盛著作',
'盛著名' => '盛著名',
'盛著錄' => '盛著录',
+'盛著录' => '盛著录',
'盛著稱' => '盛著称',
+'盛著称' => '盛著称',
'盛著者' => '盛著者',
'盛著述' => '盛著述',
'盯著' => '盯着',
@@ -14220,15 +14604,20 @@ $zh2Hans = array(
'盯著作' => '盯著作',
'盯著名' => '盯著名',
'盯著錄' => '盯著录',
+'盯著录' => '盯著录',
'盯著稱' => '盯著称',
+'盯著称' => '盯著称',
'盯著者' => '盯著者',
'盯著述' => '盯著述',
'盾著' => '盾着',
'盾著書' => '盾著书',
+'盾著书' => '盾著书',
'盾著作' => '盾著作',
'盾著名' => '盾著名',
'盾著錄' => '盾著录',
+'盾著录' => '盾著录',
'盾著稱' => '盾著称',
+'盾著称' => '盾著称',
'盾著者' => '盾著者',
'盾著述' => '盾著述',
'看不著' => '看不着',
@@ -14237,8 +14626,10 @@ $zh2Hans = array(
'看著書' => '看着书',
'看著作' => '看著作',
'看著名' => '看著名',
+'看著录' => '看著录',
'看著錄' => '看著录',
'看著稱' => '看著称',
+'看著称' => '看著称',
'看著者' => '看著者',
'看著述' => '看著述',
'著業' => '着业',
@@ -14347,9 +14738,12 @@ $zh2Hans = array(
'睡得著' => '睡得着',
'睡著' => '睡着',
'睡著書' => '睡著书',
+'睡著书' => '睡著书',
'睡著作' => '睡著作',
'睡著名' => '睡著名',
'睡著錄' => '睡著录',
+'睡著录' => '睡著录',
+'睡著称' => '睡著称',
'睡著稱' => '睡著称',
'睡著者' => '睡著者',
'睡著述' => '睡著述',
@@ -14367,15 +14761,20 @@ $zh2Hans = array(
'瞧著書' => '瞧着书',
'瞧著作' => '瞧著作',
'瞧著名' => '瞧著名',
+'瞧著录' => '瞧著录',
'瞧著錄' => '瞧著录',
+'瞧著称' => '瞧著称',
'瞧著稱' => '瞧著称',
'瞧著者' => '瞧著者',
'瞧著述' => '瞧著述',
'瞪著' => '瞪着',
'瞪著書' => '瞪著书',
+'瞪著书' => '瞪著书',
'瞪著作' => '瞪著作',
'瞪著名' => '瞪著名',
'瞪著錄' => '瞪著录',
+'瞪著录' => '瞪著录',
+'瞪著称' => '瞪著称',
'瞪著稱' => '瞪著称',
'瞪著者' => '瞪著者',
'瞪著述' => '瞪著述',
@@ -14383,27 +14782,36 @@ $zh2Hans = array(
'石碁镇' => '石碁镇',
'石碁鎮' => '石碁镇',
'福著' => '福着',
+'福著书' => '福著书',
'福著書' => '福著书',
'福著作' => '福著作',
'福著名' => '福著名',
'福著錄' => '福著录',
+'福著录' => '福著录',
'福著稱' => '福著称',
+'福著称' => '福著称',
'福著者' => '福著者',
'福著述' => '福著述',
'穀梁' => '穀梁',
'空著' => '空着',
+'空著书' => '空著书',
'空著書' => '空著书',
'空著作' => '空著作',
'空著名' => '空著名',
+'空著录' => '空著录',
'空著錄' => '空著录',
+'空著称' => '空著称',
'空著稱' => '空著称',
'空著者' => '空著者',
'空著述' => '空著述',
'穿著' => '穿着',
+'穿著书' => '穿著书',
'穿著書' => '穿著书',
'穿著作' => '穿著作',
'穿著名' => '穿著名',
+'穿著录' => '穿著录',
'穿著錄' => '穿著录',
+'穿著称' => '穿著称',
'穿著稱' => '穿著称',
'穿著者' => '穿著者',
'穿著述' => '穿著述',
@@ -14416,30 +14824,41 @@ $zh2Hans = array(
'豎著者' => '竖著者',
'豎著述' => '竖著述',
'站著' => '站着',
+'站著书' => '站著书',
'站著書' => '站著书',
'站著作' => '站著作',
'站著名' => '站著名',
'站著錄' => '站著录',
+'站著录' => '站著录',
+'站著称' => '站著称',
'站著稱' => '站著称',
'站著者' => '站著者',
'站著述' => '站著述',
'笑著' => '笑着',
+'笑著书' => '笑著书',
'笑著書' => '笑著书',
'笑著作' => '笑著作',
'笑著名' => '笑著名',
+'笑著录' => '笑著录',
'笑著錄' => '笑著录',
+'笑著称' => '笑著称',
'笑著稱' => '笑著称',
'笑著者' => '笑著者',
'笑著述' => '笑著述',
'答覆' => '答复',
'管著' => '管着',
+'管著书' => '管著书',
'管著書' => '管著书',
'管著作' => '管著作',
'管著名' => '管著名',
+'管著录' => '管著录',
'管著錄' => '管著录',
'管著稱' => '管著称',
+'管著称' => '管著称',
'管著者' => '管著者',
'管著述' => '管著述',
+'米澤瑠美' => '米泽瑠美',
+'米泽瑠美' => '米泽瑠美',
'綁著' => '绑着',
'綁著書' => '绑著书',
'綁著作' => '绑著作',
@@ -14466,45 +14885,60 @@ $zh2Hans = array(
'纏著者' => '缠著者',
'纏著述' => '缠著述',
'罩著' => '罩着',
+'罩著书' => '罩著书',
'罩著書' => '罩著书',
'罩著作' => '罩著作',
'罩著名' => '罩著名',
'罩著錄' => '罩著录',
+'罩著录' => '罩著录',
+'罩著称' => '罩著称',
'罩著稱' => '罩著称',
'罩著者' => '罩著者',
'罩著述' => '罩著述',
'美著' => '美着',
+'美著书' => '美著书',
'美著書' => '美著书',
'美著作' => '美著作',
'美著名' => '美著名',
+'美著录' => '美著录',
'美著錄' => '美著录',
'美著稱' => '美著称',
+'美著称' => '美著称',
'美著者' => '美著者',
'美著述' => '美著述',
'耀著' => '耀着',
'耀著書' => '耀著书',
+'耀著书' => '耀著书',
'耀著作' => '耀著作',
'耀著名' => '耀著名',
'耀著錄' => '耀著录',
+'耀著录' => '耀著录',
+'耀著称' => '耀著称',
'耀著稱' => '耀著称',
'耀著者' => '耀著者',
'耀著述' => '耀著述',
'老幺' => '老幺',
'考著' => '考着',
'考著書' => '考著书',
+'考著书' => '考著书',
'考著作' => '考著作',
'考著名' => '考著名',
'考著錄' => '考著录',
+'考著录' => '考著录',
'考著稱' => '考著称',
+'考著称' => '考著称',
'考著者' => '考著者',
'考著述' => '考著述',
'肉乾乾' => '肉干干',
'肘手鍊足' => '肘手链足',
'背著' => '背着',
+'背著书' => '背著书',
'背著書' => '背著书',
'背著作' => '背著作',
'背著名' => '背著名',
+'背著录' => '背著录',
'背著錄' => '背著录',
+'背著称' => '背著称',
'背著稱' => '背著称',
'背著者' => '背著者',
'背著述' => '背著述',
@@ -14525,11 +14959,14 @@ $zh2Hans = array(
'藝著者' => '艺著者',
'藝著述' => '艺著述',
'苦著' => '苦着',
+'苦著书' => '苦著书',
'苦著書' => '苦著书',
'苦著作' => '苦著作',
'苦著名' => '苦著名',
+'苦著录' => '苦著录',
'苦著錄' => '苦著录',
'苦著稱' => '苦著称',
+'苦著称' => '苦著称',
'苦著者' => '苦著者',
'苦著述' => '苦著述',
'苧烯' => '苧烯',
@@ -14545,11 +14982,14 @@ $zh2Hans = array(
'蕭乾' => '萧乾',
'萧乾' => '萧乾',
'落著' => '落着',
+'落著书' => '落著书',
'落著書' => '落著书',
'落著作' => '落著作',
'落著名' => '落著名',
+'落著录' => '落著录',
'落著錄' => '落著录',
'落著稱' => '落著称',
+'落著称' => '落著称',
'落著者' => '落著者',
'落著述' => '落著述',
'著書' => '著书',
@@ -14565,42 +15005,57 @@ $zh2Hans = array(
'著述' => '著述',
'蒙著' => '蒙着',
'蒙著書' => '蒙著书',
+'蒙著书' => '蒙著书',
'蒙著作' => '蒙著作',
'蒙著名' => '蒙著名',
+'蒙著录' => '蒙著录',
'蒙著錄' => '蒙著录',
'蒙著稱' => '蒙著称',
+'蒙著称' => '蒙著称',
'蒙著者' => '蒙著者',
'蒙著述' => '蒙著述',
'藏著' => '藏着',
'藏著書' => '藏著书',
+'藏著书' => '藏著书',
'藏著作' => '藏著作',
'藏著名' => '藏著名',
'藏著錄' => '藏著录',
+'藏著录' => '藏著录',
+'藏著称' => '藏著称',
'藏著稱' => '藏著称',
'藏著者' => '藏著者',
'藏著述' => '藏著述',
'蘸著' => '蘸着',
'蘸著書' => '蘸著书',
+'蘸著书' => '蘸著书',
'蘸著作' => '蘸著作',
'蘸著名' => '蘸著名',
+'蘸著录' => '蘸著录',
'蘸著錄' => '蘸著录',
'蘸著稱' => '蘸著称',
+'蘸著称' => '蘸著称',
'蘸著者' => '蘸著者',
'蘸著述' => '蘸著述',
'行著' => '行着',
+'行著书' => '行著书',
'行著書' => '行著书',
'行著作' => '行著作',
'行著名' => '行著名',
+'行著录' => '行著录',
'行著錄' => '行著录',
'行著稱' => '行著称',
+'行著称' => '行著称',
'行著者' => '行著者',
'行著述' => '行著述',
'衣著' => '衣着',
+'衣著书' => '衣著书',
'衣著書' => '衣著书',
'衣著作' => '衣著作',
'衣著名' => '衣著名',
+'衣著录' => '衣著录',
'衣著錄' => '衣著录',
'衣著稱' => '衣著称',
+'衣著称' => '衣著称',
'衣著者' => '衣著者',
'衣著述' => '衣著述',
'裝著' => '装着',
@@ -14613,9 +15068,12 @@ $zh2Hans = array(
'裝著述' => '装著述',
'裹著' => '裹着',
'裹著書' => '裹著书',
+'裹著书' => '裹著书',
'裹著作' => '裹著作',
'裹著名' => '裹著名',
+'裹著录' => '裹著录',
'裹著錄' => '裹著录',
+'裹著称' => '裹著称',
'裹著稱' => '裹著称',
'裹著者' => '裹著者',
'裹著述' => '裹著述',
@@ -14658,10 +15116,13 @@ $zh2Hans = array(
'語著者' => '语著者',
'語著述' => '语著述',
'豫著' => '豫着',
+'豫著书' => '豫著书',
'豫著書' => '豫著书',
'豫著作' => '豫著作',
'豫著名' => '豫著名',
+'豫著录' => '豫著录',
'豫著錄' => '豫著录',
+'豫著称' => '豫著称',
'豫著稱' => '豫著称',
'豫著者' => '豫著者',
'豫著述' => '豫著述',
@@ -14675,10 +15136,13 @@ $zh2Hans = array(
'貞著述' => '贞著述',
'走著' => '走着',
'走著書' => '走著书',
+'走著书' => '走著书',
'走著作' => '走著作',
'走著名' => '走著名',
'走著錄' => '走著录',
+'走著录' => '走著录',
'走著稱' => '走著称',
+'走著称' => '走著称',
'走著者' => '走著者',
'走著述' => '走著述',
'趕著' => '赶着',
@@ -14691,9 +15155,12 @@ $zh2Hans = array(
'趕著述' => '赶著述',
'趴著' => '趴着',
'趴著書' => '趴著书',
+'趴著书' => '趴著书',
'趴著作' => '趴著作',
'趴著名' => '趴著名',
+'趴著录' => '趴著录',
'趴著錄' => '趴著录',
+'趴著称' => '趴著称',
'趴著稱' => '趴著称',
'趴著者' => '趴著者',
'趴著述' => '趴著述',
@@ -14707,66 +15174,90 @@ $zh2Hans = array(
'躍著述' => '跃著述',
'跑著' => '跑着',
'跑著書' => '跑著书',
+'跑著书' => '跑著书',
'跑著作' => '跑著作',
'跑著名' => '跑著名',
+'跑著录' => '跑著录',
'跑著錄' => '跑著录',
'跑著稱' => '跑著称',
+'跑著称' => '跑著称',
'跑著者' => '跑著者',
'跑著述' => '跑著述',
'跟著' => '跟着',
+'跟著书' => '跟著书',
'跟著書' => '跟著书',
'跟著作' => '跟著作',
'跟著名' => '跟著名',
+'跟著录' => '跟著录',
'跟著錄' => '跟著录',
+'跟著称' => '跟著称',
'跟著稱' => '跟著称',
'跟著者' => '跟著者',
'跟著述' => '跟著述',
'跪著' => '跪着',
'跪著書' => '跪著书',
+'跪著书' => '跪著书',
'跪著作' => '跪著作',
'跪著名' => '跪著名',
'跪著錄' => '跪著录',
+'跪著录' => '跪著录',
'跪著稱' => '跪著称',
+'跪著称' => '跪著称',
'跪著者' => '跪著者',
'跪著述' => '跪著述',
'跳著' => '跳着',
+'跳著书' => '跳著书',
'跳著書' => '跳著书',
'跳著作' => '跳著作',
'跳著名' => '跳著名',
+'跳著录' => '跳著录',
'跳著錄' => '跳著录',
+'跳著称' => '跳著称',
'跳著稱' => '跳著称',
'跳著者' => '跳著者',
'跳著述' => '跳著述',
'躊躇滿志' => '踌躇滿志',
'踏著' => '踏着',
'踏著書' => '踏著书',
+'踏著书' => '踏著书',
'踏著作' => '踏著作',
'踏著名' => '踏著名',
'踏著錄' => '踏著录',
+'踏著录' => '踏著录',
+'踏著称' => '踏著称',
'踏著稱' => '踏著称',
'踏著者' => '踏著者',
'踏著述' => '踏著述',
'踩著' => '踩着',
+'踩著书' => '踩著书',
'踩著書' => '踩著书',
'踩著作' => '踩著作',
'踩著名' => '踩著名',
+'踩著录' => '踩著录',
'踩著錄' => '踩著录',
'踩著稱' => '踩著称',
+'踩著称' => '踩著称',
'踩著者' => '踩著者',
'踩著述' => '踩著述',
'身著' => '身着',
+'身著书' => '身著书',
'身著書' => '身著书',
'身著作' => '身著作',
'身著名' => '身著名',
+'身著录' => '身著录',
'身著錄' => '身著录',
'身著稱' => '身著称',
+'身著称' => '身著称',
'身著者' => '身著者',
'身著述' => '身著述',
'躺著' => '躺着',
'躺著書' => '躺著书',
+'躺著书' => '躺著书',
'躺著作' => '躺著作',
'躺著名' => '躺著名',
'躺著錄' => '躺著录',
+'躺著录' => '躺著录',
+'躺著称' => '躺著称',
'躺著稱' => '躺著称',
'躺著者' => '躺著者',
'躺著述' => '躺著述',
@@ -14816,34 +15307,46 @@ $zh2Hans = array(
'迫著' => '迫着',
'追著' => '追着',
'追著書' => '追著书',
+'追著书' => '追著书',
'追著作' => '追著作',
'追著名' => '追著名',
'追著錄' => '追著录',
+'追著录' => '追著录',
+'追著称' => '追著称',
'追著稱' => '追著称',
'追著者' => '追著者',
'追著述' => '追著述',
'逆著' => '逆着',
'逆著書' => '逆著书',
+'逆著书' => '逆著书',
'逆著作' => '逆著作',
'逆著名' => '逆著名',
'逆著錄' => '逆著录',
+'逆著录' => '逆著录',
+'逆著称' => '逆著称',
'逆著稱' => '逆著称',
'逆著者' => '逆著者',
'逆著述' => '逆著述',
'逼著' => '逼着',
'逼著書' => '逼著书',
+'逼著书' => '逼著书',
'逼著作' => '逼著作',
'逼著名' => '逼著名',
'逼著錄' => '逼著录',
+'逼著录' => '逼著录',
+'逼著称' => '逼著称',
'逼著稱' => '逼著称',
'逼著者' => '逼著者',
'逼著述' => '逼著述',
'遇著' => '遇着',
'遇著書' => '遇著书',
+'遇著书' => '遇著书',
'遇著作' => '遇著作',
'遇著名' => '遇著名',
'遇著錄' => '遇著录',
+'遇著录' => '遇著录',
'遇著稱' => '遇著称',
+'遇著称' => '遇著称',
'遇著者' => '遇著者',
'遇著述' => '遇著述',
'遺著' => '遗著',
@@ -14851,10 +15354,13 @@ $zh2Hans = array(
'郭子乾' => '郭子乾',
'配著' => '配着',
'配著書' => '配著书',
+'配著书' => '配著书',
'配著作' => '配著作',
'配著名' => '配著名',
'配著錄' => '配著录',
+'配著录' => '配著录',
'配著稱' => '配著称',
+'配著称' => '配著称',
'配著者' => '配著者',
'配著述' => '配著述',
'釀著' => '酿着',
@@ -14867,8 +15373,8 @@ $zh2Hans = array(
'釀著述' => '酿著述',
'醯壺' => '醯壶',
'醯壶' => '醯壶',
-'醯酱' => '醯酱',
'醯醬' => '醯酱',
+'醯酱' => '醯酱',
'醯醋' => '醯醋',
'醯醢' => '醯醢',
'醯鸡' => '醯鸡',
@@ -14913,29 +15419,45 @@ $zh2Hans = array(
'聞不著' => '闻不着',
'聞得著' => '闻得着',
'聞著' => '闻着',
+'阳为乾' => '阳为乾',
+'陽爲乾' => '阳为乾',
+'陽為乾' => '阳为乾',
'阿部正瞭' => '阿部正瞭',
'附著' => '附着',
'附睪' => '附睾',
+'附著书' => '附著书',
'附著書' => '附著书',
'附著作' => '附著作',
'附著名' => '附著名',
'附著錄' => '附著录',
+'附著录' => '附著录',
+'附著称' => '附著称',
'附著稱' => '附著称',
'附著者' => '附著者',
'附著述' => '附著述',
+'陈乾生' => '陈乾生',
+'陳乾生' => '陈乾生',
+'陈公乾生' => '陈公乾生',
+'陳公乾生' => '陈公乾生',
'陋著' => '陋着',
'陋著書' => '陋著书',
+'陋著书' => '陋著书',
'陋著作' => '陋著作',
'陋著名' => '陋著名',
'陋著錄' => '陋著录',
+'陋著录' => '陋著录',
+'陋著称' => '陋著称',
'陋著稱' => '陋著称',
'陋著者' => '陋著者',
'陋著述' => '陋著述',
'陪著' => '陪着',
+'陪著书' => '陪著书',
'陪著書' => '陪著书',
'陪著作' => '陪著作',
'陪著名' => '陪著名',
+'陪著录' => '陪著录',
'陪著錄' => '陪著录',
+'陪著称' => '陪著称',
'陪著稱' => '陪著称',
'陪著者' => '陪著者',
'陪著述' => '陪著述',
@@ -14950,19 +15472,25 @@ $zh2Hans = array(
'隨著者' => '随著者',
'隨著述' => '随著述',
'隔著' => '隔着',
+'隔著书' => '隔著书',
'隔著書' => '隔著书',
'隔著作' => '隔著作',
'隔著名' => '隔著名',
+'隔著录' => '隔著录',
'隔著錄' => '隔著录',
+'隔著称' => '隔著称',
'隔著稱' => '隔著称',
'隔著者' => '隔著者',
'隔著述' => '隔著述',
'隱睪' => '隱睾',
'雅著' => '雅着',
+'雅著书' => '雅著书',
'雅著書' => '雅著书',
'雅著作' => '雅著作',
'雅著名' => '雅著名',
+'雅著录' => '雅著录',
'雅著錄' => '雅著录',
+'雅著称' => '雅著称',
'雅著稱' => '雅著称',
'雅著者' => '雅著者',
'雅著述' => '雅著述',
@@ -14971,7 +15499,9 @@ $zh2Hans = array(
'靠著作' => '靠著作',
'靠著名' => '靠著名',
'靠著錄' => '靠著录',
+'靠著录' => '靠著录',
'靠著稱' => '靠著称',
+'靠著称' => '靠著称',
'靠著者' => '靠著者',
'靠著述' => '靠著述',
'頂著' => '顶着',
@@ -15041,18 +15571,24 @@ $zh2Hans = array(
'騙著者' => '骗著者',
'騙著述' => '骗著述',
'高著' => '高着',
+'高著书' => '高著书',
'高著書' => '高著书',
'高著作' => '高著作',
'高著名' => '高著名',
+'高著录' => '高著录',
'高著錄' => '高著录',
'高著稱' => '高著称',
+'高著称' => '高著称',
'高著者' => '高著者',
'高著述' => '高著述',
'髭著' => '髭着',
+'髭著书' => '髭著书',
'髭著書' => '髭著书',
'髭著作' => '髭著作',
'髭著名' => '髭著名',
'髭著錄' => '髭著录',
+'髭著录' => '髭著录',
+'髭著称' => '髭著称',
'髭著稱' => '髭著称',
'髭著者' => '髭著者',
'髭著述' => '髭著述',
@@ -15068,11 +15604,16 @@ $zh2Hans = array(
'麽氏' => '麽氏',
'麽麽' => '麽麽',
'麼麼' => '麽麽',
+'黄润乾' => '黄润乾',
+'黃潤乾' => '黄润乾',
'黏著' => '黏着',
+'黏著书' => '黏著书',
'黏著書' => '黏著书',
'黏著作' => '黏著作',
'黏著名' => '黏著名',
+'黏著录' => '黏著录',
'黏著錄' => '黏著录',
+'黏著称' => '黏著称',
'黏著稱' => '黏著称',
'黏著者' => '黏著者',
'黏著述' => '黏著述',
@@ -15229,6 +15770,7 @@ $zh2TW = array(
'心裏' => '心裡',
'快闪存储器' => '快閃記憶體',
'闪存' => '快閃記憶體',
+'想象' => '想像',
'传感' => '感測',
'习用' => '慣用',
'戏彩娱亲' => '戲綵娛親',
@@ -15317,7 +15859,6 @@ $zh2TW = array(
'盘片' => '碟片',
'磁盘' => '磁碟',
'磁道' => '磁軌',
-'福士' => '福斯',
'秋假裏' => '秋假裡',
'秋天裏' => '秋天裡',
'秋日裏' => '秋日裡',
@@ -15432,7 +15973,6 @@ $zh2TW = array(
'追凶' => '追凶',
'追兇' => '追凶',
'這裏' => '這裡',
-'信道' => '通道',
'逞凶鬥狠' => '逞凶鬥狠',
'逞兇鬥狠' => '逞凶鬥狠',
'逞凶斗狠' => '逞凶鬥狠',
@@ -15461,6 +16001,7 @@ $zh2TW = array(
'雪裏紅' => '雪裡紅',
'雪裏蕻' => '雪裡蕻',
'雪铁龙' => '雪鐵龍',
+'青霉素' => '青黴素',
'异步' => '非同步',
'声卡' => '音效卡',
'缺省' => '預設',
@@ -16126,6 +16667,7 @@ $zh2HK = array(
'悠著者' => '悠著者',
'悠著述' => '悠著述',
'悠著錄' => '悠著錄',
+'想象' => '想像',
'想著' => '想着',
'想著作' => '想著作',
'想著名' => '想著名',
@@ -17756,6 +18298,7 @@ $zh2CN = array(
'記憶體' => '内存',
'甘比亞' => '冈比亚',
'防寫' => '写保护',
+'軍中樂園' => '军中乐园',
'冷菜' => '凉菜',
'冷盤' => '凉菜',
'幾內亞比索' => '几内亚比绍',
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index 708a3a40..175fa6a1 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -1,10 +1,9 @@
<?php
-
/**
- * Created on Sep 5, 2006
- *
* API for MediaWiki 1.8+
*
+ * Created on Sep 5, 2006
+ *
* Copyright © 2006, 2010 Yuri Astrakhan <Firstname><Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
@@ -19,8 +18,10 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
/**
@@ -50,6 +51,8 @@ abstract class ApiBase {
const PARAM_MIN = 5; // Lowest value allowed for a parameter. Only applies if TYPE='integer'
const PARAM_ALLOW_DUPLICATES = 6; // Boolean, do we allow the same value to be set more than once when ISMULTI=true
const PARAM_DEPRECATED = 7; // Boolean, is the parameter deprecated (will show a warning)
+ const PARAM_REQUIRED = 8; // Boolean, is the parameter required?
+ const PARAM_RANGE_ENFORCE = 9; // Boolean, if MIN/MAX are set, enforce (die) these? Only applies if TYPE='integer' Use with extreme caution
const LIMIT_BIG1 = 500; // Fast query, std user limit
const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit
@@ -179,8 +182,7 @@ abstract class ApiBase {
if ( isset( $data['warnings'][$this->getModuleName()] ) ) {
// Don't add duplicate warnings
$warn_regex = preg_quote( $warning, '/' );
- if ( preg_match( "/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*'] ) )
- {
+ if ( preg_match( "/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*'] ) ) {
return;
}
$oldwarning = $data['warnings'][$this->getModuleName()]['*'];
@@ -224,13 +226,13 @@ abstract class ApiBase {
$msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
if ( $this->isReadMode() ) {
- $msg .= "\nThis module requires read rights.";
+ $msg .= "\nThis module requires read rights";
}
if ( $this->isWriteMode() ) {
- $msg .= "\nThis module requires write rights.";
+ $msg .= "\nThis module requires write rights";
}
if ( $this->mustBePosted() ) {
- $msg .= "\nThis module only accepts POST requests.";
+ $msg .= "\nThis module only accepts POST requests";
}
if ( $this->isReadMode() || $this->isWriteMode() ||
$this->mustBePosted() )
@@ -252,8 +254,11 @@ abstract class ApiBase {
$examples
);
}
- $msg .= 'Example' . ( count( $examples ) > 1 ? 's' : '' ) . ":\n ";
- $msg .= implode( $lnPrfx, $examples ) . "\n";
+
+ if ( count( $examples ) > 0 ) {
+ $msg .= 'Example' . ( count( $examples ) > 1 ? 's' : '' ) . ":\n ";
+ $msg .= implode( $lnPrfx, $examples ) . "\n";
+ }
}
if ( $this->getMain()->getShowVersions() ) {
@@ -295,12 +300,24 @@ abstract class ApiBase {
$desc = implode( $paramPrefix, $desc );
}
+ if ( !is_array( $paramSettings ) ) {
+ $paramSettings = array(
+ self::PARAM_DFLT => $paramSettings,
+ );
+ }
+
$deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ?
$paramSettings[self::PARAM_DEPRECATED] : false;
if ( $deprecated ) {
$desc = "DEPRECATED! $desc";
}
+ $required = isset( $paramSettings[self::PARAM_REQUIRED] ) ?
+ $paramSettings[self::PARAM_REQUIRED] : false;
+ if ( $required ) {
+ $desc .= $paramPrefix . "This parameter is required";
+ }
+
$type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
if ( isset( $type ) ) {
if ( isset( $paramSettings[self::PARAM_ISMULTI] ) ) {
@@ -312,21 +329,26 @@ abstract class ApiBase {
if ( is_array( $type ) ) {
$choices = array();
$nothingPrompt = false;
- foreach ( $type as $t )
+ foreach ( $type as $t ) {
if ( $t === '' ) {
$nothingPrompt = 'Can be empty, or ';
} else {
$choices[] = $t;
}
+ }
$desc .= $paramPrefix . $nothingPrompt . $prompt . implode( ', ', $choices );
} else {
switch ( $type ) {
case 'namespace':
// Special handling because namespaces are type-limited, yet they are not given
- $desc .= $paramPrefix . $prompt . implode( ', ', ApiBase::getValidNamespaces() );
+ $desc .= $paramPrefix . $prompt . implode( ', ', MWNamespace::getValidNamespaces() );
break;
case 'limit':
- $desc .= $paramPrefix . "No more than {$paramSettings[self :: PARAM_MAX]} ({$paramSettings[self::PARAM_MAX2]} for bots) allowed.";
+ $desc .= $paramPrefix . "No more than {$paramSettings[self :: PARAM_MAX]}";
+ if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
+ $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
+ }
+ $desc .= ' allowed';
break;
case 'integer':
$hasMin = isset( $paramSettings[self::PARAM_MIN] );
@@ -344,10 +366,22 @@ abstract class ApiBase {
}
break;
}
+
+ if ( isset( $paramSettings[self::PARAM_ISMULTI] ) ) {
+ $isArray = is_array( $paramSettings[self::PARAM_TYPE] );
+
+ if ( !$isArray
+ || $isArray && count( $paramSettings[self::PARAM_TYPE] ) > self::LIMIT_SML1) {
+ $desc .= $paramPrefix . "Maximum number of values " .
+ self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
+ }
+ }
}
}
- $default = is_array( $paramSettings ) ? ( isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null ) : $paramSettings;
+ $default = is_array( $paramSettings )
+ ? ( isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null )
+ : $paramSettings;
if ( !is_null( $default ) && $default !== false ) {
$desc .= $paramPrefix . "Default: $default";
}
@@ -480,7 +514,7 @@ abstract class ApiBase {
if ( $params ) { // getFinalParams() can return false
foreach ( $params as $paramName => $paramSettings ) {
- $results[$paramName] = $this->getParameterFromSettings(
+ $results[$paramName] = $this->getParameterFromSettings(
$paramName, $paramSettings, $parseLimit );
}
}
@@ -510,8 +544,8 @@ abstract class ApiBase {
array_shift( $required );
$intersection = array_intersect( array_keys( array_filter( $params,
- create_function( '$x', 'return !is_null($x) && $x !== false;' )
- ) ), $required );
+ array( $this, "parameterNotEmpty" ) ) ), $required );
+
if ( count( $intersection ) > 1 ) {
$this->dieUsage( 'The parameters ' . implode( ', ', $intersection ) . ' can not be used together', 'invalidparammix' );
} elseif ( count( $intersection ) == 0 ) {
@@ -520,24 +554,81 @@ abstract class ApiBase {
}
/**
- * Returns an array of the namespaces (by integer id) that exist on the
- * wiki. Used primarily in help documentation.
- * @return array
+ * Callback function used in requireOnlyOneParameter to check whether reequired parameters are set
+ *
+ * @param $x object Parameter to check is not null/false
+ * @return bool
+ */
+ private function parameterNotEmpty( $x ) {
+ return !is_null( $x ) && $x !== false;
+ }
+
+ /**
+ * @deprecated use MWNamespace::getValidNamespaces()
*/
public static function getValidNamespaces() {
- static $mValidNamespaces = null;
-
- if ( is_null( $mValidNamespaces ) ) {
- global $wgContLang;
- $mValidNamespaces = array();
- foreach ( array_keys( $wgContLang->getNamespaces() ) as $ns ) {
- if ( $ns >= 0 ) {
- $mValidNamespaces[] = $ns;
+ return MWNamespace::getValidNamespaces();
+ }
+
+ /**
+ * Return true if we're to watch the page, false if not, null if no change.
+ * @param $watchlist String Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
+ * @param $titleObj Title the page under consideration
+ * @param $userOption String The user option to consider when $watchlist=preferences.
+ * If not set will magically default to either watchdefault or watchcreations
+ * @returns Boolean
+ */
+ protected function getWatchlistValue ( $watchlist, $titleObj, $userOption = null ) {
+
+ $userWatching = $titleObj->userIsWatching();
+
+ global $wgUser;
+ switch ( $watchlist ) {
+ case 'watch':
+ return true;
+
+ case 'unwatch':
+ return false;
+
+ case 'preferences':
+ # If the user is already watching, don't bother checking
+ if ( $userWatching ) {
+ return true;
}
- }
+ # If no user option was passed, use watchdefault or watchcreation
+ if ( is_null( $userOption ) ) {
+ $userOption = $titleObj->exists()
+ ? 'watchdefault' : 'watchcreations';
+ }
+ # Watch the article based on the user preference
+ return (bool)$wgUser->getOption( $userOption );
+
+ case 'nochange':
+ return $userWatching;
+
+ default:
+ return $userWatching;
}
+ }
- return $mValidNamespaces;
+ /**
+ * Set a watch (or unwatch) based the based on a watchlist parameter.
+ * @param $watch String Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
+ * @param $titleObj Title the article's title to change
+ * @param $userOption String The user option to consider when $watch=preferences
+ */
+ protected function setWatch ( $watch, $titleObj, $userOption = null ) {
+ $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
+ if ( $value === null ) {
+ return;
+ }
+
+ $articleObj = new Article( $titleObj );
+ if ( $value ) {
+ $articleObj->doWatch();
+ } else {
+ $articleObj->doUnwatch();
+ }
}
/**
@@ -559,12 +650,14 @@ abstract class ApiBase {
$type = gettype( $paramSettings );
$dupes = false;
$deprecated = false;
+ $required = false;
} else {
$default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
$multi = isset( $paramSettings[self::PARAM_ISMULTI] ) ? $paramSettings[self::PARAM_ISMULTI] : false;
$type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
$dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] ) ? $paramSettings[self::PARAM_ALLOW_DUPLICATES] : false;
$deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ? $paramSettings[self::PARAM_DEPRECATED] : false;
+ $required = isset( $paramSettings[self::PARAM_REQUIRED] ) ? $paramSettings[self::PARAM_REQUIRED] : false;
// When type is not given, and no choices, the type is the same as $default
if ( !isset( $type ) ) {
@@ -587,7 +680,7 @@ abstract class ApiBase {
$value = $this->getMain()->getRequest()->getVal( $encParamName, $default );
if ( isset( $value ) && $type == 'namespace' ) {
- $type = ApiBase::getValidNamespaces();
+ $type = MWNamespace::getValidNamespaces();
}
}
@@ -602,19 +695,28 @@ abstract class ApiBase {
switch ( $type ) {
case 'NULL': // nothing to do
break;
- case 'string': // nothing to do
+ case 'string':
+ if ( $required && $value === '' ) {
+ $this->dieUsageMsg( array( 'missingparam', $paramName ) );
+ }
+
break;
case 'integer': // Force everything using intval() and optionally validate limits
-
- $value = is_array( $value ) ? array_map( 'intval', $value ) : intval( $value );
$min = isset ( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
$max = isset ( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
+ $enforceLimits = isset ( $paramSettings[self::PARAM_RANGE_ENFORCE] )
+ ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
if ( !is_null( $min ) || !is_null( $max ) ) {
- $values = is_array( $value ) ? $value : array( $value );
- foreach ( $values as &$v ) {
- $this->validateLimit( $paramName, $v, $min, $max );
- }
+ if ( is_array( $value ) ) {
+ $value = array_map( 'intval', $value );
+ foreach ( $value as &$v ) {
+ $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
+ }
+ } else {
+ $value = intval( $value );
+ $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
+ }
}
break;
case 'limit':
@@ -631,15 +733,16 @@ abstract class ApiBase {
$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
if ( $value == 'max' ) {
$value = $this->getMain()->canApiHighLimits() ? $paramSettings[self::PARAM_MAX2] : $paramSettings[self::PARAM_MAX];
- $this->getResult()->addValue( 'limits', $this->getModuleName(), $value );
+ $this->getResult()->setParsedLimit( $this->getModuleName(), $value );
} else {
$value = intval( $value );
$this->validateLimit( $paramName, $value, $min, $paramSettings[self::PARAM_MAX], $paramSettings[self::PARAM_MAX2] );
}
break;
case 'boolean':
- if ( $multi )
+ if ( $multi ) {
ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
+ }
break;
case 'timestamp':
if ( $multi ) {
@@ -652,11 +755,21 @@ abstract class ApiBase {
$value = wfTimestamp( TS_MW, $value );
break;
case 'user':
- $title = Title::makeTitleSafe( NS_USER, $value );
- if ( is_null( $title ) ) {
- $this->dieUsage( "Invalid value for user parameter $encParamName", "baduser_{$encParamName}" );
+ if ( !is_array( $value ) ) {
+ $value = array( $value );
+ }
+
+ foreach ( $value as $key => $val ) {
+ $title = Title::makeTitleSafe( NS_USER, $val );
+ if ( is_null( $title ) ) {
+ $this->dieUsage( "Invalid value for user parameter $encParamName", "baduser_{$encParamName}" );
+ }
+ $value[$key] = $title->getText();
+ }
+
+ if ( !$multi ) {
+ $value = $value[0];
}
- $value = $title->getText();
break;
default:
ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
@@ -672,6 +785,8 @@ abstract class ApiBase {
if ( $deprecated && $value !== false ) {
$this->setWarning( "The $encParamName parameter has been deprecated." );
}
+ } else if ( $required ) {
+ $this->dieUsageMsg( array( 'missingparam', $paramName ) );
}
return $value;
@@ -736,10 +851,13 @@ abstract class ApiBase {
* @param $min int Minimum value
* @param $max int Maximum value for users
* @param $botMax int Maximum value for sysops/bots
+ * @param $enforceLimits Boolean Whether to enforce (die) if value is outside limits
*/
- function validateLimit( $paramName, &$value, $min, $max, $botMax = null ) {
+ function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
if ( !is_null( $min ) && $value < $min ) {
- $this->setWarning( $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)" );
+
+ $msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
+ $this->warnOrDie( $msg, $enforceLimits );
$value = $min;
}
@@ -753,17 +871,33 @@ abstract class ApiBase {
if ( !is_null( $max ) && $value > $max ) {
if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
if ( $value > $botMax ) {
- $this->setWarning( $this->encodeParamName( $paramName ) . " may not be over $botMax (set to $value) for bots or sysops" );
+ $msg = $this->encodeParamName( $paramName ) . " may not be over $botMax (set to $value) for bots or sysops";
+ $this->warnOrDie( $msg, $enforceLimits );
$value = $botMax;
}
} else {
- $this->setWarning( $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users" );
+ $msg = $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users";
+ $this->warnOrDie( $msg, $enforceLimits );
$value = $max;
}
}
}
/**
+ * Adds a warning to the output, else dies
+ *
+ * @param $msg String Message to show as a warning, or error message if dying
+ * @param $enforceLimits Boolean Whether this is an enforce (die)
+ */
+ private function warnOrDie( $msg, $enforceLimits = false ) {
+ if ( $enforceLimits ) {
+ $this->dieUsage( $msg, 'integeroutofrange' );
+ } else {
+ $this->setWarning( $msg );
+ }
+ }
+
+ /**
* Truncate an array to a certain length.
* @param $arr array Array to truncate
* @param $limit int Maximum length
@@ -772,7 +906,7 @@ abstract class ApiBase {
public static function truncateArray( &$arr, $limit ) {
$modified = false;
while ( count( $arr ) > $limit ) {
- $junk = array_pop( $arr );
+ array_pop( $arr );
$modified = true;
}
return $modified;
@@ -848,6 +982,8 @@ abstract class ApiBase {
'ipb_blocked_as_range' => array( 'code' => 'blockedasrange', 'info' => "IP address ``\$1'' was blocked as part of range ``\$2''. You can't unblock the IP invidually, but you can unblock the range as a whole." ),
'ipb_cant_unblock' => array( 'code' => 'cantunblock', 'info' => "The block you specified was not found. It may have been unblocked already" ),
'mailnologin' => array( 'code' => 'cantsend', 'info' => "You are not logged in, you do not have a confirmed e-mail address, or you are not allowed to send e-mail to other users, so you cannot send e-mail" ),
+ 'ipbblocked' => array( 'code' => 'ipbblocked', 'info' => 'You cannot block or unblock users while you are yourself blocked' ),
+ 'ipbnounblockself' => array( 'code' => 'ipbnounblockself', 'info' => 'You are not allowed to unblock yourself' ),
'usermaildisabled' => array( 'code' => 'usermaildisabled', 'info' => "User email has been disabled" ),
'blockedemailuser' => array( 'code' => 'blockedfrommail', 'info' => "You have been blocked from sending e-mail" ),
'notarget' => array( 'code' => 'notarget', 'info' => "You have not specified a valid target for this action" ),
@@ -860,6 +996,7 @@ abstract class ApiBase {
'userrights-nodatabase' => array( 'code' => 'nosuchdatabase', 'info' => "Database ``\$1'' does not exist or is not local" ),
'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username ``\$1''" ),
'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username ``\$1''" ),
+ 'summaryrequired' => array( 'code' => 'summaryrequired', 'info' => 'Summary required' ),
// API-specific messages
'readrequired' => array( 'code' => 'readapidenied', 'info' => "You need read permission to use this module" ),
@@ -886,7 +1023,6 @@ abstract class ApiBase {
'createonly-exists' => array( 'code' => 'articleexists', 'info' => "The article you tried to create has been created already" ),
'nocreate-missing' => array( 'code' => 'missingtitle', 'info' => "The article you tried to edit doesn't exist" ),
'nosuchrcid' => array( 'code' => 'nosuchrcid', 'info' => "There is no change with rcid ``\$1''" ),
- 'cantpurge' => array( 'code' => 'cantpurge', 'info' => "Only users with the 'purge' right can purge pages via the API" ),
'protect-invalidaction' => array( 'code' => 'protect-invalidaction', 'info' => "Invalid protection type ``\$1''" ),
'protect-invalidlevel' => array( 'code' => 'protect-invalidlevel', 'info' => "Invalid protection level ``\$1''" ),
'toofewexpiries' => array( 'code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed" ),
@@ -927,6 +1063,7 @@ abstract class ApiBase {
'invalid-session-key' => array( 'code' => 'invalid-session-key', 'info' => 'Not a valid session key' ),
'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ),
+ 'copyuploaddisabled' => array( 'code' => 'copyuploaddisabled', 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' ),
);
/**
@@ -1022,12 +1159,46 @@ abstract class ApiBase {
}
/**
+ * Gets the user for whom to get the watchlist
+ *
+ * @returns User
+ */
+ public function getWatchlistUser( $params ) {
+ global $wgUser;
+ if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
+ $user = User::newFromName( $params['owner'], false );
+ if ( !$user->getId() ) {
+ $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
+ }
+ $token = $user->getOption( 'watchlisttoken' );
+ if ( $token == '' || $token != $params['token'] ) {
+ $this->dieUsage( 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', 'bad_wltoken' );
+ }
+ } else {
+ if ( !$wgUser->isLoggedIn() ) {
+ $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
+ }
+ $user = $wgUser;
+ }
+ return $user;
+ }
+
+ /**
* Returns a list of all possible errors returned by the module
* @return array in the format of array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... )
*/
public function getPossibleErrors() {
$ret = array();
+ $params = $this->getFinalParams();
+ if ( $params ) {
+ foreach ( $params as $paramName => $paramSettings ) {
+ if ( isset( $paramSettings[ApiBase::PARAM_REQUIRED] ) ) {
+ $ret[] = array( 'missingparam', $paramName );
+ }
+ }
+ }
+
if ( $this->mustBePosted() ) {
$ret[] = array( 'mustbeposted', $this->getModuleName() );
}
@@ -1192,6 +1363,6 @@ abstract class ApiBase {
* @return string
*/
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiBase.php 79562 2011-01-04 06:15:54Z tstarling $';
+ return __CLASS__ . ': $Id: ApiBase.php 82730 2011-02-24 16:03:05Z reedy $';
}
}
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index 23de07d6..875b8aeb 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -1,9 +1,9 @@
<?php
-
/**
- * Created on Sep 4, 2007
* API for MediaWiki 1.8+
*
+ * Created on Sep 4, 2007
+ *
* Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
@@ -18,8 +18,10 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
@@ -58,12 +60,16 @@ class ApiBlock extends ApiBase {
return;
}
- if ( is_null( $params['user'] ) ) {
- $this->dieUsageMsg( array( 'missingparam', 'user' ) );
- }
if ( !$wgUser->isAllowed( 'block' ) ) {
$this->dieUsageMsg( array( 'cantblock' ) );
}
+ # bug 15810: blocked admins should have limited access here
+ if ( $wgUser->isBlocked() ) {
+ $status = IPBlockForm::checkUnblockSelf( $params['user'] );
+ if ( $status !== true ) {
+ $this->dieUsageMsg( array( $status ) );
+ }
+ }
if ( $params['hidename'] && !$wgUser->isAllowed( 'hideuser' ) ) {
$this->dieUsageMsg( array( 'canthide' ) );
}
@@ -128,7 +134,10 @@ class ApiBlock extends ApiBase {
public function getAllowedParams() {
return array(
- 'user' => null,
+ 'user' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'token' => null,
'gettoken' => false,
'expiry' => 'never',
@@ -161,20 +170,19 @@ class ApiBlock extends ApiBase {
}
public function getDescription() {
- return array(
- 'Block a user.'
- );
+ return 'Block a user';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'user' ),
array( 'cantblock' ),
array( 'canthide' ),
array( 'cantblock-email' ),
+ array( 'ipbblocked' ),
+ array( 'ipbnounblockself' ),
) );
}
-
+
public function needsToken() {
return true;
}
@@ -186,11 +194,11 @@ class ApiBlock extends ApiBase {
protected function getExamples() {
return array(
'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike',
- 'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate&autoblock&noemail'
+ 'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail='
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiBlock.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiBlock.php 77192 2010-11-23 22:05:27Z btongminh $';
}
}
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index c4550a96..fbf62391 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -1,9 +1,9 @@
<?php
-
/**
- * Created on Jun 30, 2007
* API for MediaWiki 1.8+
*
+ * Created on Jun 30, 2007
+ *
* Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
@@ -18,8 +18,10 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
@@ -28,7 +30,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
/**
- * API module that facilitates deleting pages. The API eqivalent of action=delete.
+ * API module that facilitates deleting pages. The API equivalent of action=delete.
* Requires API write mode to be enabled.
*
* @ingroup API
@@ -47,8 +49,6 @@ class ApiDelete extends ApiBase {
* result object.
*/
public function execute() {
- global $wgUser;
-
$params = $this->extractRequestParams();
$this->requireOnlyOneParameter( $params, 'title', 'pageid' );
@@ -82,17 +82,26 @@ class ApiDelete extends ApiBase {
$this->dieUsageMsg( reset( $retval ) ); // We don't care about multiple errors, just report one of them
}
- if ( $params['watch'] || $wgUser->getOption( 'watchdeletion' ) ) {
- $articleObj->doWatch();
+ // Deprecated parameters
+ if ( $params['watch'] ) {
+ $watch = 'watch';
} elseif ( $params['unwatch'] ) {
- $articleObj->doUnwatch();
+ $watch = 'unwatch';
+ } else {
+ $watch = $params['watchlist'];
}
+ $this->setWatch( $watch, $titleObj, 'watchdeletion' );
}
$r = array( 'title' => $titleObj->getPrefixedText(), 'reason' => $reason );
$this->getResult()->addValue( null, $this->getModuleName(), $r );
}
+ /**
+ *
+ * @param &$title Title
+ * @param $token String
+ */
private static function getPermissionsError( &$title, $token ) {
global $wgUser;
@@ -108,9 +117,9 @@ class ApiDelete extends ApiBase {
/**
* We have our own delete() function, since Article.php's implementation is split in two phases
*
- * @param Article $article - Article object to work on
- * @param string $token - Delete token (same as edit token)
- * @param string $reason - Reason for the deletion. Autogenerated if NULL
+ * @param $article Article object to work on
+ * @param $token String: delete token (same as edit token)
+ * @param $reason String: reason for the deletion. Autogenerated if NULL
* @return Title::getUserPermissionsErrors()-like array
*/
public static function delete( &$article, $token, &$reason = null ) {
@@ -137,8 +146,8 @@ class ApiDelete extends ApiBase {
}
$error = '';
- if ( !wfRunHooks( 'ArticleDelete', array( &$article, &$wgUser, &$reason, $error ) ) ) {
- $this->dieUsageMsg( array( 'hookaborted', $error ) );
+ if ( !wfRunHooks( 'ArticleDelete', array( &$article, &$wgUser, &$reason, &$error ) ) ) {
+ return array( array( 'hookaborted', $error ) );
}
// Luckily, Article.php provides a reusable delete function that does the hard work for us
@@ -149,6 +158,15 @@ class ApiDelete extends ApiBase {
return array( array( 'cannotdelete', $article->mTitle->getPrefixedText() ) );
}
+ /**
+ * @static
+ * @param $token
+ * @param $title
+ * @param $oldimage
+ * @param $reason
+ * @param $suppress bool
+ * @return \type|array|Title
+ */
public static function deleteFile( $token, &$title, $oldimage, &$reason = null, $suppress = false ) {
$errors = self::getPermissionsError( $title, $token );
if ( count( $errors ) ) {
@@ -197,28 +215,43 @@ class ApiDelete extends ApiBase {
),
'token' => null,
'reason' => null,
- 'watch' => false,
- 'unwatch' => false,
- 'oldimage' => null
+ 'watch' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'watchlist' => array(
+ ApiBase::PARAM_DFLT => 'preferences',
+ ApiBase::PARAM_TYPE => array(
+ 'watch',
+ 'unwatch',
+ 'preferences',
+ 'nochange'
+ ),
+ ),
+ 'unwatch' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'oldimage' => null,
);
}
public function getParamDescription() {
+ $p = $this->getModulePrefix();
return array(
- 'title' => 'Title of the page you want to delete. Cannot be used together with pageid',
- 'pageid' => 'Page ID of the page you want to delete. Cannot be used together with title',
+ 'title' => "Title of the page you want to delete. Cannot be used together with {$p}pageid",
+ 'pageid' => "Page ID of the page you want to delete. Cannot be used together with {$p}title",
'token' => 'A delete token previously retrieved through prop=info',
- 'reason' => 'Reason for the deletion. If not set, an automatically generated reason will be used.',
+ 'reason' => 'Reason for the deletion. If not set, an automatically generated reason will be used',
'watch' => 'Add the page to your watchlist',
+ 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
'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 array(
- 'Delete a page.'
- );
+ return 'Delete a page';
}
public function getPossibleErrors() {
@@ -246,6 +279,6 @@ class ApiDelete extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiDelete.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiDelete.php 77141 2010-11-23 10:04:38Z ialex $';
}
} \ No newline at end of file
diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php
index 60e0e7ee..f83bfdc9 100644
--- a/includes/api/ApiDisabled.php
+++ b/includes/api/ApiDisabled.php
@@ -1,9 +1,9 @@
<?php
-
/**
- * Created on Sep 25, 2008
* API for MediaWiki 1.8+
*
+ * Created on Sep 25, 2008
+ *
* Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
@@ -18,8 +18,10 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
@@ -60,9 +62,7 @@ class ApiDisabled extends ApiBase {
}
public function getDescription() {
- return array(
- 'This module has been disabled.'
- );
+ return 'This module has been disabled';
}
protected function getExamples() {
@@ -70,6 +70,6 @@ class ApiDisabled extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiDisabled.php 62783 2010-02-21 18:09:00Z ashley $';
+ return __CLASS__ . ': $Id: ApiDisabled.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index e78f66bc..75cc0ba2 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on August 16, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Iker Labarga <Firstname><Lastname>@gmail.com
+ * Created on August 16, 2007
+ *
+ * Copyright © 2007 Iker Labarga <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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ // Eclipse helper - will be ignored in production
+ require_once( "ApiBase.php" );
}
/**
@@ -38,40 +39,71 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiEditPage extends ApiBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName );
+ parent::__construct( $query, $moduleName );
}
public function execute() {
global $wgUser;
$params = $this->extractRequestParams();
-
- if ( is_null( $params['title'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'title' ) );
if ( is_null( $params['text'] ) && is_null( $params['appendtext'] ) &&
is_null( $params['prependtext'] ) &&
$params['undo'] == 0 )
+ {
$this->dieUsageMsg( array( 'missingtext' ) );
+ }
$titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj || $titleObj->isExternal() )
+ if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
-
+ }
+
+ if( $params['redirect'] ) {
+ if( $titleObj->isRedirect() ) {
+ $oldTitle = $titleObj;
+
+ $titles = Title::newFromRedirectArray( Revision::newFromTitle( $oldTitle )->getText( Revision::FOR_THIS_USER ) );
+
+ $redirValues = array();
+ foreach ( $titles as $id => $newTitle ) {
+
+ if( !isset( $titles[ $id - 1 ] ) ) {
+ $titles[ $id - 1 ] = $oldTitle;
+ }
+
+ $redirValues[] = array(
+ 'from' => $titles[ $id - 1 ]->getPrefixedText(),
+ 'to' => $newTitle->getPrefixedText()
+ );
+
+ $titleObj = $newTitle;
+ }
+
+ $this->getResult()->setIndexedTagName( $redirValues, 'r' );
+ $this->getResult()->addValue( null, 'redirects', $redirValues );
+
+ }
+ }
+
// Some functions depend on $wgTitle == $ep->mTitle
global $wgTitle;
$wgTitle = $titleObj;
- if ( $params['createonly'] && $titleObj->exists() )
+ if ( $params['createonly'] && $titleObj->exists() ) {
$this->dieUsageMsg( array( 'createonly-exists' ) );
- if ( $params['nocreate'] && !$titleObj->exists() )
+ }
+ if ( $params['nocreate'] && !$titleObj->exists() ) {
$this->dieUsageMsg( array( 'nocreate-missing' ) );
+ }
// Now let's check whether we're even allowed to do this
$errors = $titleObj->getUserPermissionsErrors( 'edit', $wgUser );
- if ( !$titleObj->exists() )
+ if ( !$titleObj->exists() ) {
$errors = array_merge( $errors, $titleObj->getUserPermissionsErrors( 'create', $wgUser ) );
- if ( count( $errors ) )
+ }
+ if ( count( $errors ) ) {
$this->dieUsageMsg( $errors[0] );
+ }
$articleObj = new Article( $titleObj );
$toMD5 = $params['text'];
@@ -81,127 +113,126 @@ class ApiEditPage extends ApiBase {
// returns an interface message rather than ''
// We do want getContent()'s behavior for non-existent
// MediaWiki: pages, though
- if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI )
+ if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI ) {
$content = '';
- else
+ } else {
$content = $articleObj->getContent();
-
- if ( !is_null( $params['section'] ) )
- {
+ }
+
+ if ( !is_null( $params['section'] ) ) {
// Process the content for section edits
global $wgParser;
$section = intval( $params['section'] );
$content = $wgParser->getSection( $content, $section, false );
- if ( $content === false )
+ if ( $content === false ) {
$this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
+ }
}
$params['text'] = $params['prependtext'] . $content . $params['appendtext'];
$toMD5 = $params['prependtext'] . $params['appendtext'];
}
-
- if ( $params['undo'] > 0 )
- {
- if ( $params['undoafter'] > 0 )
- {
- if ( $params['undo'] < $params['undoafter'] )
+
+ if ( $params['undo'] > 0 ) {
+ if ( $params['undoafter'] > 0 ) {
+ if ( $params['undo'] < $params['undoafter'] ) {
list( $params['undo'], $params['undoafter'] ) =
array( $params['undoafter'], $params['undo'] );
+ }
$undoafterRev = Revision::newFromID( $params['undoafter'] );
}
$undoRev = Revision::newFromID( $params['undo'] );
- if ( is_null( $undoRev ) || $undoRev->isDeleted( Revision::DELETED_TEXT ) )
+ if ( is_null( $undoRev ) || $undoRev->isDeleted( Revision::DELETED_TEXT ) ) {
$this->dieUsageMsg( array( 'nosuchrevid', $params['undo'] ) );
+ }
- if ( $params['undoafter'] == 0 )
+ if ( $params['undoafter'] == 0 ) {
$undoafterRev = $undoRev->getPrevious();
- if ( is_null( $undoafterRev ) || $undoafterRev->isDeleted( Revision::DELETED_TEXT ) )
+ }
+ if ( is_null( $undoafterRev ) || $undoafterRev->isDeleted( Revision::DELETED_TEXT ) ) {
$this->dieUsageMsg( array( 'nosuchrevid', $params['undoafter'] ) );
+ }
- if ( $undoRev->getPage() != $articleObj->getID() )
+ if ( $undoRev->getPage() != $articleObj->getID() ) {
$this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText() ) );
- if ( $undoafterRev->getPage() != $articleObj->getID() )
+ }
+ if ( $undoafterRev->getPage() != $articleObj->getID() ) {
$this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
-
+ }
+
$newtext = $articleObj->getUndoText( $undoRev, $undoafterRev );
- if ( $newtext === false )
+ if ( $newtext === false ) {
$this->dieUsageMsg( array( 'undo-failure' ) );
+ }
$params['text'] = $newtext;
// If no summary was given and we only undid one rev,
// use an autosummary
- if ( is_null( $params['summary'] ) && $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo'] )
+ if ( is_null( $params['summary'] ) && $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo'] ) {
$params['summary'] = wfMsgForContent( 'undo-summary', $params['undo'], $undoRev->getUserText() );
+ }
}
// See if the MD5 hash checks out
- if ( !is_null( $params['md5'] ) && md5( $toMD5 ) !== $params['md5'] )
+ if ( !is_null( $params['md5'] ) && md5( $toMD5 ) !== $params['md5'] ) {
$this->dieUsageMsg( array( 'hashcheckfailed' ) );
-
+ }
+
$ep = new EditPage( $articleObj );
// EditPage wants to parse its stuff from a WebRequest
// That interface kind of sucks, but it's workable
- $reqArr = array( 'wpTextbox1' => $params['text'],
- 'wpEditToken' => $params['token'],
- 'wpIgnoreBlankSummary' => ''
+ $reqArr = array(
+ 'wpTextbox1' => $params['text'],
+ 'wpEditToken' => $params['token'],
+ 'wpIgnoreBlankSummary' => ''
);
- if ( !is_null( $params['summary'] ) )
+ if ( !is_null( $params['summary'] ) ) {
$reqArr['wpSummary'] = $params['summary'];
+ }
// Watch out for basetimestamp == ''
// wfTimestamp() treats it as NOW, almost certainly causing an edit conflict
- if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' )
+ if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' ) {
$reqArr['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
- else
+ } else {
$reqArr['wpEdittime'] = $articleObj->getTimestamp();
+ }
- if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' )
+ if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' ) {
$reqArr['wpStarttime'] = wfTimestamp( TS_MW, $params['starttimestamp'] );
- else
- $reqArr['wpStarttime'] = $reqArr['wpEdittime']; // Fake wpStartime
+ } else {
+ $reqArr['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
+ }
- if ( $params['minor'] || ( !$params['notminor'] && $wgUser->getOption( 'minordefault' ) ) )
+ if ( $params['minor'] || ( !$params['notminor'] && $wgUser->getOption( 'minordefault' ) ) ) {
$reqArr['wpMinoredit'] = '';
+ }
- if ( $params['recreate'] )
+ if ( $params['recreate'] ) {
$reqArr['wpRecreate'] = '';
+ }
- if ( !is_null( $params['section'] ) )
- {
+ if ( !is_null( $params['section'] ) ) {
$section = intval( $params['section'] );
- if ( $section == 0 && $params['section'] != '0' && $params['section'] != 'new' )
+ if ( $section == 0 && $params['section'] != '0' && $params['section'] != 'new' ) {
$this->dieUsage( "The section parameter must be set to an integer or 'new'", "invalidsection" );
+ }
$reqArr['wpSection'] = $params['section'];
- }
- else
+ } else {
$reqArr['wpSection'] = '';
-
- // Handle watchlist settings
- switch ( $params['watchlist'] )
- {
- case 'watch':
- $watch = true;
- break;
- case 'unwatch':
- $watch = false;
- break;
- case 'preferences':
- if ( $titleObj->exists() )
- $watch = $wgUser->getOption( 'watchdefault' ) || $titleObj->userIsWatching();
- else
- $watch = $wgUser->getOption( 'watchcreations' );
- break;
- case 'nochange':
- default:
- $watch = $titleObj->userIsWatching();
}
+
+ $watch = $this->getWatchlistValue( $params['watchlist'], $titleObj );
+
// Deprecated parameters
- if ( $params['watch'] )
+ if ( $params['watch'] ) {
$watch = true;
- elseif ( $params['unwatch'] )
+ } elseif ( $params['unwatch'] ) {
$watch = false;
-
- if ( $watch )
+ }
+
+ if ( $watch ) {
$reqArr['wpWatchthis'] = '';
+ }
$req = new FauxRequest( $reqArr, true );
$ep->importFormData( $req );
@@ -209,22 +240,22 @@ class ApiEditPage extends ApiBase {
// Run hooks
// Handle CAPTCHA parameters
global $wgRequest;
- if ( !is_null( $params['captchaid'] ) )
+ if ( !is_null( $params['captchaid'] ) ) {
$wgRequest->setVal( 'wpCaptchaId', $params['captchaid'] );
- if ( !is_null( $params['captchaword'] ) )
+ }
+ if ( !is_null( $params['captchaword'] ) ) {
$wgRequest->setVal( 'wpCaptchaWord', $params['captchaword'] );
+ }
$r = array();
- if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $ep->textbox1, &$r ) ) )
- {
- if ( count( $r ) )
- {
- $r['result'] = "Failure";
+ if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $ep->textbox1, &$r ) ) ) {
+ if ( count( $r ) ) {
+ $r['result'] = 'Failure';
$this->getResult()->addValue( null, $this->getModuleName(), $r );
return;
- }
- else
+ } else {
$this->dieUsageMsg( array( 'hookaborted' ) );
+ }
}
// Do the actual save
@@ -237,8 +268,9 @@ class ApiEditPage extends ApiBase {
$retval = $ep->internalAttemptSave( $result, $wgUser->isAllowed( 'bot' ) && $params['bot'] );
$wgRequest = $oldRequest;
- switch( $retval )
- {
+ global $wgMaxArticleSize;
+
+ switch( $retval ) {
case EditPage::AS_HOOK_ERROR:
case EditPage::AS_HOOK_ERROR_EXPECTED:
$this->dieUsageMsg( array( 'hookaborted' ) );
@@ -260,7 +292,6 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
case EditPage::AS_CONTENT_TOO_BIG:
- global $wgMaxArticleSize;
$this->dieUsageMsg( array( 'contenttoobig', $wgMaxArticleSize ) );
case EditPage::AS_READ_ONLY_PAGE_ANON:
@@ -293,8 +324,9 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_SUCCESS_NEW_ARTICLE:
$r['new'] = '';
+
case EditPage::AS_SUCCESS_UPDATE:
- $r['result'] = "Success";
+ $r['result'] = 'Success';
$r['pageid'] = intval( $titleObj->getArticleID() );
$r['title'] = $titleObj->getPrefixedText();
// HACK: We create a new Article object here because getRevIdFetched()
@@ -303,10 +335,9 @@ class ApiEditPage extends ApiBase {
// don't want to do.
$newArticle = new Article( $titleObj );
$newRevId = $newArticle->getRevIdFetched();
- if ( $newRevId == $oldRevId )
+ if ( $newRevId == $oldRevId ) {
$r['nochange'] = '';
- else
- {
+ } else {
$r['oldrevid'] = intval( $oldRevId );
$r['newrevid'] = intval( $newRevId );
$r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
@@ -314,12 +345,15 @@ class ApiEditPage extends ApiBase {
}
break;
+ case EditPage::AS_SUMMARY_NEEDED:
+ $this->dieUsageMsg( array( 'summaryrequired' ) );
+
case EditPage::AS_END:
// This usually means some kind of race condition
- // or DB weirdness occurred. Fall through to throw an unknown
+ // or DB weirdness occurred. Fall through to throw an unknown
// error.
- // This needs fixing higher up, as Article::doEdit should be
+ // This needs fixing higher up, as Article::doEdit should be
// used rather than Article::updateArticle, so that specific
// error conditions can be returned
default:
@@ -339,12 +373,11 @@ class ApiEditPage extends ApiBase {
protected function getDescription() {
return 'Create and edit pages.';
}
-
+
public function getPossibleErrors() {
global $wgMaxArticleSize;
-
+
return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'title' ),
array( 'missingtext' ),
array( 'invalidtitle', 'title' ),
array( 'createonly-exists' ),
@@ -358,6 +391,7 @@ class ApiEditPage extends ApiBase {
array( 'noimageredirect-anon' ),
array( 'noimageredirect-logged' ),
array( 'spamdetected', 'spam' ),
+ array( 'summaryrequired' ),
array( 'filtered' ),
array( 'blockedtext' ),
array( 'contenttoobig', $wgMaxArticleSize ),
@@ -376,8 +410,11 @@ class ApiEditPage extends ApiBase {
}
protected function getAllowedParams() {
- return array (
- 'title' => null,
+ return array(
+ 'title' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'section' => null,
'text' => null,
'token' => null,
@@ -393,16 +430,16 @@ class ApiEditPage extends ApiBase {
'captchaword' => null,
'captchaid' => null,
'watch' => array(
- ApiBase :: PARAM_DFLT => false,
- ApiBase :: PARAM_DEPRECATED => true,
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
),
'unwatch' => array(
- ApiBase :: PARAM_DFLT => false,
- ApiBase :: PARAM_DEPRECATED => true,
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
),
'watchlist' => array(
- ApiBase :: PARAM_DFLT => 'preferences',
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_DFLT => 'preferences',
+ ApiBase::PARAM_TYPE => array(
'watch',
'unwatch',
'preferences',
@@ -413,16 +450,21 @@ class ApiEditPage extends ApiBase {
'prependtext' => null,
'appendtext' => null,
'undo' => array(
- ApiBase :: PARAM_TYPE => 'integer'
+ ApiBase::PARAM_TYPE => 'integer'
),
'undoafter' => array(
- ApiBase :: PARAM_TYPE => 'integer'
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ 'redirect' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DFLT => false,
),
);
}
protected function getParamDescription() {
- return array (
+ $p = $this->getModulePrefix();
+ return array(
'title' => 'Page title',
'section' => 'Section number. 0 for the top section, \'new\' for a new section',
'text' => 'Page content',
@@ -435,7 +477,7 @@ class ApiEditPage extends ApiBase {
'Used to detect edit conflicts; leave unset to ignore conflicts.'
),
'starttimestamp' => array( 'Timestamp when you obtained the edit token.',
- 'Used to detect edit conflicts; leave unset to ignore conflicts.'
+ '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',
@@ -445,15 +487,16 @@ class ApiEditPage extends ApiBase {
'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
'captchaid' => 'CAPTCHA ID from previous request',
'captchaword' => 'Answer to the CAPTCHA',
- 'md5' => array( 'The MD5 hash of the text parameter, or the prependtext and appendtext parameters concatenated.',
+ 'md5' => array( "The MD5 hash of the {$p}text parameter, or the {$p}prependtext and {$p}appendtext parameters concatenated.",
'If set, the edit won\'t be done unless the hash is correct' ),
- 'prependtext' => 'Add this text to the beginning of the page. Overrides text.',
- 'appendtext' => 'Add this text to the end of the page. Overrides text',
- 'undo' => 'Undo this revision. Overrides text, prependtext and appendtext',
+ 'prependtext' => "Add this text to the beginning of the page. Overrides {$p}text",
+ 'appendtext' => "Add this text to the end of the page. Overrides {$p}text",
+ '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',
);
}
-
+
public function needsToken() {
return true;
}
@@ -463,17 +506,17 @@ class ApiEditPage extends ApiBase {
}
protected function getExamples() {
- return array (
- "Edit a page (anonymous user):",
- " api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\",
- "Prepend __NOTOC__ to a page (anonymous user):",
- " api.php?action=edit&title=Test&summary=NOTOC&minor&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\",
- "Undo r13579 through r13585 with autosummary(anonymous user):",
- " api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\",
+ return array(
+ 'Edit a page (anonymous user):',
+ ' api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\',
+ 'Prepend __NOTOC__ to a page (anonymous user):',
+ ' api.php?action=edit&title=Test&summary=NOTOC&minor=&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\',
+ 'Undo r13579 through r13585 with autosummary (anonymous user):',
+ ' api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiEditPage.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiEditPage.php 90492 2011-06-20 22:39:10Z reedy $';
}
}
diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php
index 66f2dff5..ab58eb18 100644
--- a/includes/api/ApiEmailUser.php
+++ b/includes/api/ApiEmailUser.php
@@ -1,10 +1,10 @@
<?php
-
-/*
- * Created on June 1, 2008
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Bryan Tong Minh <Bryan.TongMinh@Gmail.com>
+ * Created on June 1, 2008
+ *
+ * Copyright © 2008 Bryan Tong Minh <Bryan.TongMinh@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
@@ -18,58 +18,74 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
+ * API Module to facilitate sending of emails to users
* @ingroup API
*/
class ApiEmailUser extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
global $wgUser;
- // Check whether email is enabled
- if ( !EmailUserForm::userEmailEnabled() )
- $this->dieUsageMsg( array( 'usermaildisabled' ) );
$params = $this->extractRequestParams();
- // Check required parameters
- if ( !isset( $params['target'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'target' ) );
- if ( !isset( $params['text'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'text' ) );
-
- // Validate target
- $targetUser = EmailUserForm::validateEmailTarget( $params['target'] );
- if ( !( $targetUser instanceof User ) )
+
+ // Validate target
+ $targetUser = SpecialEmailUser::getTarget( $params['target'] );
+ if ( !( $targetUser instanceof User ) ) {
$this->dieUsageMsg( array( $targetUser ) );
-
- // Check permissions
- $error = EmailUserForm::getPermissionsError( $wgUser, $params['token'] );
- if ( $error )
+ }
+
+ // Check permissions and errors
+ $error = SpecialEmailUser::getPermissionsError( $wgUser, $params['token'] );
+ if ( $error ) {
$this->dieUsageMsg( array( $error ) );
+ }
+
+ $data = array(
+ 'Target' => $targetUser->getName(),
+ 'Text' => $params['text'],
+ 'Subject' => $params['subject'],
+ 'CCMe' => $params['ccme'],
+ );
+ $retval = SpecialEmailUser::submit( $data );
- $form = new EmailUserForm( $targetUser, $params['text'], $params['subject'], $params['ccme'] );
- $retval = $form->doSubmit();
- if ( is_null( $retval ) )
+ if ( $retval instanceof Status ) {
+ // SpecialEmailUser sometimes returns a status
+ // sometimes it doesn't.
+ if ( $retval->isGood() ) {
+ $retval = true;
+ } else {
+ $retval = $retval->getErrorsArray();
+ }
+ }
+
+ if ( $retval === true ) {
$result = array( 'result' => 'Success' );
- else
- $result = array( 'result' => 'Failure',
- 'message' => $retval->getMessage() );
-
+ } else {
+ $result = array(
+ 'result' => 'Failure',
+ 'message' => $retval
+ );
+ }
+
$this->getResult()->addValue( null, $this->getModuleName(), $result );
}
-
+
public function mustBePosted() {
return true;
}
@@ -79,17 +95,23 @@ class ApiEmailUser extends ApiBase {
}
public function getAllowedParams() {
- return array (
- 'target' => null,
+ return array(
+ 'target' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'subject' => null,
- 'text' => null,
+ 'text' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'token' => null,
'ccme' => false,
);
}
public function getParamDescription() {
- return array (
+ return array(
'target' => 'User to send email to',
'subject' => 'Subject header',
'text' => 'Mail body',
@@ -99,19 +121,15 @@ class ApiEmailUser extends ApiBase {
}
public function getDescription() {
- return array(
- 'Email a user.'
- );
+ return 'Email a user.';
}
-
- public function getPossibleErrors() {
+
+ public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'usermaildisabled' ),
- array( 'missingparam', 'target' ),
- array( 'missingparam', 'text' ),
- ) );
+ ) );
}
-
+
public function needsToken() {
return true;
}
@@ -121,13 +139,12 @@ class ApiEmailUser extends ApiBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=emailuser&target=WikiSysop&text=Content'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiEmailUser.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiEmailUser.php 85354 2011-04-04 18:25:31Z demon $';
}
}
- \ No newline at end of file
diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php
index d0c00db7..6f2df1b8 100644
--- a/includes/api/ApiExpandTemplates.php
+++ b/includes/api/ApiExpandTemplates.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 05, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Oct 05, 2007
+ *
+ * Copyright © 2007 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -38,7 +39,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiExpandTemplates extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
@@ -49,18 +50,18 @@ class ApiExpandTemplates extends ApiBase {
$params = $this->extractRequestParams();
// Create title for parser
- $title_obj = Title :: newFromText( $params['title'] );
- if ( !$title_obj )
- $title_obj = Title :: newFromText( "API" ); // default
+ $title_obj = Title::newFromText( $params['title'] );
+ if ( !$title_obj ) {
+ $title_obj = Title::newFromText( 'API' ); // default
+ }
$result = $this->getResult();
// Parse text
global $wgParser;
$options = new ParserOptions();
-
- if ( $params['generatexml'] )
- {
+
+ if ( $params['generatexml'] ) {
$wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS );
$dom = $wgParser->preprocessToDom( $params['text'] );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
@@ -81,9 +82,9 @@ class ApiExpandTemplates extends ApiBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'title' => array(
- ApiBase :: PARAM_DFLT => 'API',
+ ApiBase::PARAM_DFLT => 'API',
),
'text' => null,
'generatexml' => false,
@@ -91,7 +92,7 @@ class ApiExpandTemplates extends ApiBase {
}
public function getParamDescription() {
- return array (
+ return array(
'text' => 'Wikitext to convert',
'title' => 'Title of page',
'generatexml' => 'Generate XML parse tree',
@@ -103,12 +104,12 @@ class ApiExpandTemplates extends ApiBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=expandtemplates&text={{Project:Sandbox}}'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiExpandTemplates.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiExpandTemplates.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index 03d12800..e1ba61f6 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 13, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Oct 13, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -38,7 +39,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiFeedWatchlist extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
/**
@@ -48,13 +49,14 @@ class ApiFeedWatchlist extends ApiBase {
return new ApiFormatFeedWrapper( $this->getMain() );
}
+ private $linkToDiffs = false;
+
/**
* Make a nested call to the API to request watchlist items in the last $hours.
* Wrap the result as an RSS/Atom feed.
*/
public function execute() {
-
- global $wgFeedClasses, $wgFeedLimit, $wgSitename, $wgContLanguageCode;
+ global $wgFeedClasses, $wgFeedLimit, $wgSitename, $wgLanguageCode;
try {
$params = $this->extractRequestParams();
@@ -62,16 +64,15 @@ class ApiFeedWatchlist extends ApiBase {
// limit to the number of hours going from now back
$endTime = wfTimestamp( TS_MW, time() - intval( $params['hours'] * 60 * 60 ) );
- $dbr = wfGetDB( DB_SLAVE );
// Prepare parameters for nested request
- $fauxReqArr = array (
+ $fauxReqArr = array(
'action' => 'query',
'meta' => 'siteinfo',
'siprop' => 'general',
'list' => 'watchlist',
'wlprop' => 'title|user|comment|timestamp',
- 'wldir' => 'older', // reverse order - from newest to oldest
- 'wlend' => $dbr->timestamp( $endTime ), // stop at this time
+ 'wldir' => 'older', // reverse order - from newest to oldest
+ 'wlend' => $endTime, // stop at this time
'wllimit' => ( 50 > $wgFeedLimit ) ? $wgFeedLimit : 50
);
@@ -82,13 +83,19 @@ class ApiFeedWatchlist extends ApiBase {
$fauxReqArr['wltoken'] = $params['wltoken'];
}
+ // Support linking to diffs instead of article
+ if ( $params['linktodiffs'] ) {
+ $this->linkToDiffs = true;
+ $fauxReqArr['wlprop'] .= '|ids';
+ }
+
// Check for 'allrev' parameter, and if found, show all revisions to each page on wl.
- if ( !is_null ( $params['allrev'] ) ) {
+ if ( !is_null( $params['allrev'] ) ) {
$fauxReqArr['wlallrev'] = '';
}
// Create the request
- $fauxReq = new FauxRequest ( $fauxReqArr );
+ $fauxReq = new FauxRequest( $fauxReqArr );
// Execute
$module = new ApiMain( $fauxReq );
@@ -102,20 +109,20 @@ class ApiFeedWatchlist extends ApiBase {
$feedItems[] = $this->createFeedItem( $info );
}
- $feedTitle = $wgSitename . ' - ' . wfMsgForContent( 'watchlist' ) . ' [' . $wgContLanguageCode . ']';
- $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullUrl();
+ $feedTitle = $wgSitename . ' - ' . wfMsgForContent( 'watchlist' ) . ' [' . $wgLanguageCode . ']';
+ $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
$feed = new $wgFeedClasses[$params['feedformat']] ( $feedTitle, htmlspecialchars( wfMsgForContent( 'watchlist' ) ), $feedUrl );
- ApiFormatFeedWrapper :: setResult( $this->getResult(), $feed, $feedItems );
+ ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
} catch ( Exception $e ) {
// Error results should not be cached
$this->getMain()->setCacheMaxAge( 0 );
- $feedTitle = $wgSitename . ' - Error - ' . wfMsgForContent( 'watchlist' ) . ' [' . $wgContLanguageCode . ']';
- $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullUrl();
+ $feedTitle = $wgSitename . ' - Error - ' . wfMsgForContent( 'watchlist' ) . ' [' . $wgLanguageCode . ']';
+ $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
$feedFormat = isset( $params['feedformat'] ) ? $params['feedformat'] : 'rss';
$feed = new $wgFeedClasses[$feedFormat] ( $feedTitle, htmlspecialchars( wfMsgForContent( 'watchlist' ) ), $feedUrl );
@@ -128,15 +135,19 @@ class ApiFeedWatchlist extends ApiBase {
}
$errorText = $e->getMessage();
- $feedItems[] = new FeedItem( "Error ($errorCode)", $errorText, "", "", "" );
- ApiFormatFeedWrapper :: setResult( $this->getResult(), $feed, $feedItems );
+ $feedItems[] = new FeedItem( "Error ($errorCode)", $errorText, '', '', '' );
+ ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
}
}
private function createFeedItem( $info ) {
$titleStr = $info['title'];
- $title = Title :: newFromText( $titleStr );
- $titleUrl = $title->getFullUrl();
+ $title = Title::newFromText( $titleStr );
+ if ( $this->linkToDiffs && isset( $info['revid'] ) ) {
+ $titleUrl = $title->getFullURL( array( 'diff' => $info['revid'] ) );
+ } else {
+ $titleUrl = $title->getFullURL();
+ }
$comment = isset( $info['comment'] ) ? $info['comment'] : null;
$timestamp = $info['timestamp'];
$user = $info['user'];
@@ -150,33 +161,35 @@ class ApiFeedWatchlist extends ApiBase {
global $wgFeedClasses;
$feedFormatNames = array_keys( $wgFeedClasses );
return array (
- 'feedformat' => array (
- ApiBase :: PARAM_DFLT => 'rss',
- ApiBase :: PARAM_TYPE => $feedFormatNames
+ 'feedformat' => array(
+ ApiBase::PARAM_DFLT => 'rss',
+ ApiBase::PARAM_TYPE => $feedFormatNames
),
- 'hours' => array (
- ApiBase :: PARAM_DFLT => 24,
- ApiBase :: PARAM_TYPE => 'integer',
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => 72,
+ 'hours' => array(
+ ApiBase::PARAM_DFLT => 24,
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => 72,
),
'allrev' => null,
- 'wlowner' => array (
- ApiBase :: PARAM_TYPE => 'user'
+ 'wlowner' => array(
+ ApiBase::PARAM_TYPE => 'user'
),
- 'wltoken' => array (
- ApiBase :: PARAM_TYPE => 'string'
- )
+ 'wltoken' => array(
+ ApiBase::PARAM_TYPE => 'string'
+ ),
+ 'linktodiffs' => false,
);
}
public function getParamDescription() {
- return array (
+ return array(
'feedformat' => 'The format of the feed',
'hours' => 'List pages modified within this many hours from now',
- 'allrev' => 'Include multiple revisions of the same page within given timeframe.',
- 'wlowner' => "The user whose watchlist you want (must be accompanied by wltoken if it's not you)",
- 'wltoken' => 'Security token that requested user set in their preferences'
+ 'allrev' => 'Include multiple revisions of the same page within given timeframe',
+ 'wlowner' => "The user whose watchlist you want (must be accompanied by {$this->getModulePrefix()}token if it's not you)",
+ 'wltoken' => 'Security token that requested user set in their preferences',
+ 'linktodiffs'=> 'Link to change differences instead of article pages'
);
}
@@ -185,12 +198,13 @@ class ApiFeedWatchlist extends ApiBase {
}
protected function getExamples() {
- return array (
- 'api.php?action=feedwatchlist'
+ return array(
+ 'api.php?action=feedwatchlist',
+ 'api.php?action=feedwatchlist&allrev=allrev&linktodiffs=&hours=6'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFeedWatchlist.php 69357 2010-07-14 22:39:23Z mah $';
+ return __CLASS__ . ': $Id: ApiFeedWatchlist.php 77674 2010-12-03 19:47:22Z catrope $';
}
}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index de211fe9..9d1dfbc1 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 19, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 19, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
abstract class ApiFormatBase extends ApiBase {
private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp, $mCleared;
- private $mBufferResult = false, $mBuffer;
+ private $mBufferResult = false, $mBuffer, $mDisabled = false;
/**
* Constructor
@@ -45,13 +46,14 @@ abstract class ApiFormatBase extends ApiBase {
* @param $format string Format name
*/
public function __construct( $main, $format ) {
- parent :: __construct( $main, $format );
+ parent::__construct( $main, $format );
$this->mIsHtml = ( substr( $format, - 2, 2 ) === 'fm' ); // ends with 'fm'
- if ( $this->mIsHtml )
+ if ( $this->mIsHtml ) {
$this->mFormat = substr( $format, 0, - 2 ); // remove ending 'fm'
- else
+ } else {
$this->mFormat = $format;
+ }
$this->mFormat = strtoupper( $this->mFormat );
$this->mCleared = false;
}
@@ -113,20 +115,36 @@ abstract class ApiFormatBase extends ApiBase {
}
/**
+ * Disable the formatter completely. This causes calls to initPrinter(),
+ * printText() and closePrinter() to be ignored.
+ */
+ public function disable() {
+ $this->mDisabled = true;
+ }
+
+ public function isDisabled() {
+ return $this->mDisabled;
+ }
+
+ /**
* Initialize the printer function and prepare the output headers, etc.
* This method must be the first outputing method during execution.
* A help screen's header is printed for the HTML-based output
* @param $isError bool Whether an error message is printed
*/
function initPrinter( $isError ) {
+ if ( $this->mDisabled ) {
+ return;
+ }
$isHtml = $this->getIsHtml();
$mime = $isHtml ? 'text/html' : $this->getMimeType();
$script = wfScript( 'api' );
// Some printers (ex. Feed) do their own header settings,
// in which case $mime will be set to null
- if ( is_null( $mime ) )
+ if ( is_null( $mime ) ) {
return; // skip any initialization
+ }
header( "Content-Type: $mime; charset=utf-8" );
@@ -170,6 +188,9 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
* Finish printing. Closes HTML tags.
*/
public function closePrinter() {
+ if ( $this->mDisabled ) {
+ return;
+ }
if ( $this->getIsHtml() ) {
?>
@@ -189,6 +210,9 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
* @param $text string
*/
public function printText( $text ) {
+ if ( $this->mDisabled ) {
+ return;
+ }
if ( $this->mBufferResult ) {
$this->mBuffer = $text;
} elseif ( $this->getIsHtml() ) {
@@ -197,8 +221,7 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
// 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 )
- {
+ if ( !$this->mCleared ) {
ob_clean();
$this->mCleared = true;
}
@@ -224,15 +247,15 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
* @param $help bool
*/
public function setHelp( $help = true ) {
- $this->mHelp = true;
+ $this->mHelp = $help;
}
/**
- * Prety-print various elements in HTML format, such as xml tags and
- * URLs. This method also escapes characters like <
- * @param $text string
- * @return string
- */
+ * Pretty-print various elements in HTML format, such as xml tags and
+ * URLs. This method also escapes characters like <
+ * @param $text string
+ * @return string
+ */
protected function formatHTML( $text ) {
global $wgUrlProtocols;
@@ -254,12 +277,15 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
$text = preg_replace( "#\\$[^<>\n]+\\$#", '<b><i>\\0</i></b>', $text );
}
- /* Temporary fix for bad links in help messages. As a special case,
+ /**
+ * Temporary fix for bad links in help messages. As a special case,
* XML-escaped metachars are de-escaped one level in the help message
- * for legibility. Should be removed once we have completed a fully-html
- * version of the help message. */
- if ( $this->mUnescapeAmps )
+ * for legibility. Should be removed once we have completed a fully-HTML
+ * version of the help message.
+ */
+ if ( $this->mUnescapeAmps ) {
$text = preg_replace( '/&amp;(amp|quot|lt|gt);/', '&\1;', $text );
+ }
return $text;
}
@@ -273,7 +299,7 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
}
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiFormatBase.php 62367 2010-02-12 14:09:42Z siebrand $';
+ return __CLASS__ . ': $Id: ApiFormatBase.php 75970 2010-11-04 00:55:30Z reedy $';
}
}
@@ -284,7 +310,7 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
class ApiFormatFeedWrapper extends ApiFormatBase {
public function __construct( $main ) {
- parent :: __construct( $main, 'feed' );
+ parent::__construct( $main, 'feed' );
}
/**
@@ -326,13 +352,14 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
*/
public function execute() {
$data = $this->getResultData();
- if ( isset ( $data['_feed'] ) && isset ( $data['_feeditems'] ) ) {
+ if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
$feed = $data['_feed'];
$items = $data['_feeditems'];
$feed->outHeader();
- foreach ( $items as & $item )
+ foreach ( $items as & $item ) {
$feed->outItem( $item );
+ }
$feed->outFooter();
} else {
// Error has occured, print something useful
@@ -341,6 +368,6 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatBase.php 62367 2010-02-12 14:09:42Z siebrand $';
+ return __CLASS__ . ': $Id: ApiFormatBase.php 75970 2010-11-04 00:55:30Z reedy $';
}
} \ No newline at end of file
diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php
index 26afd329..d4aeb0b8 100644
--- a/includes/api/ApiFormatDbg.php
+++ b/includes/api/ApiFormatDbg.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 22, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Oct 22, 2006
+ *
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,22 +18,25 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiFormatBase.php' );
+ require_once( 'ApiFormatBase.php' );
}
/**
+ * API PHP's var_export() output formatter
* @ingroup API
*/
class ApiFormatDbg extends ApiFormatBase {
public function __construct( $main, $format ) {
- parent :: __construct( $main, $format );
+ parent::__construct( $main, $format );
}
public function getMimeType() {
@@ -49,10 +51,10 @@ class ApiFormatDbg extends ApiFormatBase {
}
public function getDescription() {
- return 'Output data in PHP\'s var_export() format' . parent :: getDescription();
+ return 'Output data in PHP\'s var_export() format' . parent::getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatDbg.php 61444 2010-01-23 22:52:40Z reedy $';
+ return __CLASS__ . ': $Id: ApiFormatDbg.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php
new file mode 100644
index 00000000..6197563d
--- /dev/null
+++ b/includes/api/ApiFormatDump.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * API for MediaWiki 1.8+
+ *
+ * Created on August 8, 2010
+ *
+ * Copyright © 2010 Soxred93
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ // Eclipse helper - will be ignored in production
+ require_once( 'ApiFormatBase.php' );
+}
+
+/**
+ * API PHP's var_dump() output formatter
+ * @ingroup API
+ */
+class ApiFormatDump extends ApiFormatBase {
+
+ public function __construct( $main, $format ) {
+ parent::__construct( $main, $format );
+ }
+
+ public function getMimeType() {
+ // This looks like it should be text/plain, but IE7 is so
+ // brain-damaged it tries to parse text/plain as HTML if it
+ // contains HTML tags. Using MIME text/text works around this bug
+ return 'text/text';
+ }
+
+ public function execute() {
+ ob_start();
+ var_dump( $this->getResultData() );
+ $result = ob_get_contents();
+ ob_end_clean();
+ $this->printText( $result );
+ }
+
+ public function getDescription() {
+ return 'Output data in PHP\'s var_dump() format' . parent::getDescription();
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id$';
+ }
+}
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index 69686bfb..7c02baa0 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 19, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 19, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,16 +18,19 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiFormatBase.php' );
+ require_once( 'ApiFormatBase.php' );
}
/**
+ * API JSON output formatter
* @ingroup API
*/
class ApiFormatJson extends ApiFormatBase {
@@ -36,7 +38,7 @@ class ApiFormatJson extends ApiFormatBase {
private $mIsRaw;
public function __construct( $main, $format ) {
- parent :: __construct( $main, $format );
+ parent::__construct( $main, $format );
$this->mIsRaw = ( $format === 'rawfm' );
}
@@ -59,40 +61,42 @@ class ApiFormatJson extends ApiFormatBase {
}
public function execute() {
- $prefix = $suffix = "";
+ $prefix = $suffix = '';
$params = $this->extractRequestParams();
$callback = $params['callback'];
if ( !is_null( $callback ) ) {
- $prefix = preg_replace( "/[^][.\\'\\\"_A-Za-z0-9]/", "", $callback ) . "(";
- $suffix = ")";
+ $prefix = preg_replace( "/[^][.\\'\\\"_A-Za-z0-9]/", '', $callback ) . '(';
+ $suffix = ')';
}
$this->printText(
$prefix .
- FormatJson::encode( $this->getResultData(), $this->getIsHtml() ) .
- $suffix );
+ FormatJson::encode( $this->getResultData(), $this->getIsHtml() ) .
+ $suffix
+ );
}
public function getAllowedParams() {
- return array (
+ return array(
'callback' => null,
);
}
public function getParamDescription() {
- return array (
+ return array(
'callback' => 'If specified, wraps the output into a given function call. For safety, all user-specific data will be restricted.',
);
}
public function getDescription() {
- if ( $this->mIsRaw )
- return 'Output data with the debuging elements in JSON format' . parent :: getDescription();
- else
- return 'Output data in JSON format' . parent :: getDescription();
+ if ( $this->mIsRaw ) {
+ return 'Output data with the debuging elements in JSON format' . parent::getDescription();
+ } else {
+ return 'Output data in JSON format' . parent::getDescription();
+ }
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatJson.php 62354 2010-02-12 06:44:16Z mah $';
+ return __CLASS__ . ': $Id: ApiFormatJson.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php
index dd03c300..e83941d4 100644
--- a/includes/api/ApiFormatPhp.php
+++ b/includes/api/ApiFormatPhp.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 22, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Oct 22, 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
@@ -19,22 +18,25 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiFormatBase.php' );
+ require_once( 'ApiFormatBase.php' );
}
/**
+ * API Serialized PHP output formatter
* @ingroup API
*/
class ApiFormatPhp extends ApiFormatBase {
public function __construct( $main, $format ) {
- parent :: __construct( $main, $format );
+ parent::__construct( $main, $format );
}
public function getMimeType() {
@@ -46,10 +48,10 @@ class ApiFormatPhp extends ApiFormatBase {
}
public function getDescription() {
- return 'Output data in serialized PHP format' . parent :: getDescription();
+ return 'Output data in serialized PHP format' . parent::getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatPhp.php 60930 2010-01-11 15:55:52Z simetrical $';
+ return __CLASS__ . ': $Id: ApiFormatPhp.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php
index 8bb66aea..98a50652 100644
--- a/includes/api/ApiFormatRaw.php
+++ b/includes/api/ApiFormatRaw.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Feb 2, 2009
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Feb 2, 2009
+ *
+ * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiFormatBase.php' );
+ require_once( 'ApiFormatBase.php' );
}
/**
@@ -40,36 +41,38 @@ class ApiFormatRaw extends ApiFormatBase {
* @param $errorFallback Formatter object to fall back on for errors
*/
public function __construct( $main, $errorFallback ) {
- parent :: __construct( $main, 'raw' );
+ parent::__construct( $main, 'raw' );
$this->mErrorFallback = $errorFallback;
}
public function getMimeType() {
$data = $this->getResultData();
- if ( isset( $data['error'] ) )
+ if ( isset( $data['error'] ) ) {
return $this->mErrorFallback->getMimeType();
+ }
+
+ if ( !isset( $data['mime'] ) ) {
+ ApiBase::dieDebug( __METHOD__, 'No MIME type set for raw formatter' );
+ }
- if ( !isset( $data['mime'] ) )
- ApiBase::dieDebug( __METHOD__, "No MIME type set for raw formatter" );
-
return $data['mime'];
}
public function execute() {
$data = $this->getResultData();
- if ( isset( $data['error'] ) )
- {
+ if ( isset( $data['error'] ) ) {
$this->mErrorFallback->execute();
return;
}
-
- if ( !isset( $data['text'] ) )
- ApiBase::dieDebug( __METHOD__, "No text given for raw formatter" );
+
+ if ( !isset( $data['text'] ) ) {
+ ApiBase::dieDebug( __METHOD__, 'No text given for raw formatter' );
+ }
$this->printText( $data['text'] );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatRaw.php 61437 2010-01-23 22:26:40Z reedy $';
+ return __CLASS__ . ': $Id: ApiFormatRaw.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php
index 1627dde6..bbb268f1 100644
--- a/includes/api/ApiFormatTxt.php
+++ b/includes/api/ApiFormatTxt.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 22, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Oct 22, 2006
+ *
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,22 +18,25 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiFormatBase.php' );
+ require_once( 'ApiFormatBase.php' );
}
/**
+ * API Text output formatter
* @ingroup API
*/
class ApiFormatTxt extends ApiFormatBase {
public function __construct( $main, $format ) {
- parent :: __construct( $main, $format );
+ parent::__construct( $main, $format );
}
public function getMimeType() {
@@ -49,10 +51,10 @@ class ApiFormatTxt extends ApiFormatBase {
}
public function getDescription() {
- return 'Output data in PHP\'s print_r() format' . parent :: getDescription();
+ return 'Output data in PHP\'s print_r() format' . parent::getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatTxt.php 61444 2010-01-23 22:52:40Z reedy $';
+ return __CLASS__ . ': $Id: ApiFormatTxt.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index e95e540b..6c1e3066 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 22, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Oct 22, 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
@@ -19,22 +18,25 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiFormatBase.php' );
+ require_once( 'ApiFormatBase.php' );
}
/**
+ * API WDDX output formatter
* @ingroup API
*/
class ApiFormatWddx extends ApiFormatBase {
public function __construct( $main, $format ) {
- parent :: __construct( $main, $format );
+ parent::__construct( $main, $format );
}
public function getMimeType() {
@@ -53,8 +55,8 @@ class ApiFormatWddx extends ApiFormatBase {
} else {
// Don't do newlines and indentation if we weren't asked
// for pretty output
- $nl = ( $this->getIsHtml() ? "" : "\n" );
- $indstr = " ";
+ $nl = ( $this->getIsHtml() ? '' : "\n" );
+ $indstr = ' ';
$this->printText( "<?xml version=\"1.0\"?>$nl" );
$this->printText( "<wddxPacket version=\"1.0\">$nl" );
$this->printText( "$indstr<header/>$nl" );
@@ -69,11 +71,11 @@ class ApiFormatWddx extends ApiFormatBase {
* Recursively go through the object and output its data in WDDX format.
*/
function slowWddxPrinter( $elemValue, $indent = 0 ) {
- $indstr = ( $this->getIsHtml() ? "" : str_repeat( ' ', $indent ) );
- $indstr2 = ( $this->getIsHtml() ? "" : str_repeat( ' ', $indent + 2 ) );
- $nl = ( $this->getIsHtml() ? "" : "\n" );
+ $indstr = ( $this->getIsHtml() ? '' : str_repeat( ' ', $indent ) );
+ $indstr2 = ( $this->getIsHtml() ? '' : str_repeat( ' ', $indent + 2 ) );
+ $nl = ( $this->getIsHtml() ? '' : "\n" );
switch ( gettype( $elemValue ) ) {
- case 'array' :
+ case 'array':
// Check whether we've got an associative array (<struct>)
// or a regular array (<array>)
$cnt = count( $elemValue );
@@ -81,8 +83,9 @@ class ApiFormatWddx extends ApiFormatBase {
// Regular array
$this->printText( $indstr . Xml::element( 'array', array(
'length' => $cnt ), null ) . $nl );
- foreach ( $elemValue as $subElemValue )
+ foreach ( $elemValue as $subElemValue ) {
$this->slowWddxPrinter( $subElemValue, $indent + 2 );
+ }
$this->printText( "$indstr</array>$nl" );
} else {
// Associative array (<struct>)
@@ -97,23 +100,23 @@ class ApiFormatWddx extends ApiFormatBase {
$this->printText( "$indstr</struct>$nl" );
}
break;
- case 'integer' :
- case 'double' :
+ case 'integer':
+ case 'double':
$this->printText( $indstr . Xml::element( 'number', null, $elemValue ) . $nl );
break;
- case 'string' :
+ case 'string':
$this->printText( $indstr . Xml::element( 'string', null, $elemValue ) . $nl );
break;
- default :
- ApiBase :: dieDebug( __METHOD__, 'Unknown type ' . gettype( $elemValue ) );
+ default:
+ ApiBase::dieDebug( __METHOD__, 'Unknown type ' . gettype( $elemValue ) );
}
}
public function getDescription() {
- return 'Output data in WDDX format' . parent :: getDescription();
+ return 'Output data in WDDX format' . parent::getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatWddx.php 61437 2010-01-23 22:26:40Z reedy $';
+ return __CLASS__ . ': $Id: ApiFormatWddx.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index a3758a49..45ab73ef 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 19, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 19, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,16 +18,19 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiFormatBase.php' );
+ require_once( 'ApiFormatBase.php' );
}
/**
+ * API XML output formatter
* @ingroup API
*/
class ApiFormatXml extends ApiFormatBase {
@@ -38,7 +40,7 @@ class ApiFormatXml extends ApiFormatBase {
private $mXslt = null;
public function __construct( $main, $format ) {
- parent :: __construct( $main, $format );
+ parent::__construct( $main, $format );
}
public function getMimeType() {
@@ -59,45 +61,50 @@ class ApiFormatXml extends ApiFormatBase {
$this->mXslt = $params['xslt'];
$this->printText( '<?xml version="1.0"?>' );
- if ( !is_null( $this->mXslt ) )
+ if ( !is_null( $this->mXslt ) ) {
$this->addXslt();
- $this->printText( self::recXmlPrint( $this->mRootElemName,
+ }
+ $this->printText(
+ self::recXmlPrint( $this->mRootElemName,
$this->getResultData(),
$this->getIsHtml() ? - 2 : null,
- $this->mDoubleQuote ) );
+ $this->mDoubleQuote
+ )
+ );
}
/**
- * 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'].
- * Example: name='root', value = array( '_element'=>'page', 'x', 'y', 'z') creates <root> <page>x</page> <page>y</page> <page>z</page> </root>
- *
- * 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.
- * Example: name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10) creates <root lang='en' id='10'>text</root>
- *
- * If neither key is found, all keys become element names, and values become element content.
- * The method is recursive, so the same rules apply to any sub-arrays.
- */
+ * 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'].
+ * Example: name='root', value = array( '_element'=>'page', 'x', 'y', 'z') creates <root> <page>x</page> <page>y</page> <page>z</page> </root>
+ *
+ * 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.
+ * Example: name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10) creates <root lang='en' id='10'>text</root>
+ *
+ * If neither key is found, all keys become element names, and values become element content.
+ * The method is recursive, so the same rules apply to any sub-arrays.
+ */
public static function recXmlPrint( $elemName, $elemValue, $indent, $doublequote = false ) {
$retval = '';
if ( !is_null( $indent ) ) {
$indent += 2;
- $indstr = "\n" . str_repeat( " ", $indent );
+ $indstr = "\n" . str_repeat( ' ', $indent );
} else {
$indstr = '';
}
$elemName = str_replace( ' ', '_', $elemName );
switch ( gettype( $elemValue ) ) {
- case 'array' :
- if ( isset ( $elemValue['*'] ) ) {
+ case 'array':
+ if ( isset( $elemValue['*'] ) ) {
$subElemContent = $elemValue['*'];
- if ( $doublequote )
+ if ( $doublequote ) {
$subElemContent = Sanitizer::encodeAttribute( $subElemContent );
- unset ( $elemValue['*'] );
-
+ }
+ unset( $elemValue['*'] );
+
// Add xml:space="preserve" to the
// element so XML parsers will leave
// whitespace in the content alone
@@ -106,59 +113,65 @@ class ApiFormatXml extends ApiFormatBase {
$subElemContent = null;
}
- if ( isset ( $elemValue['_element'] ) ) {
+ if ( isset( $elemValue['_element'] ) ) {
$subElemIndName = $elemValue['_element'];
- unset ( $elemValue['_element'] );
+ unset( $elemValue['_element'] );
} else {
$subElemIndName = null;
}
- $indElements = array ();
- $subElements = array ();
+ $indElements = array();
+ $subElements = array();
foreach ( $elemValue as $subElemId => & $subElemValue ) {
- if ( is_string( $subElemValue ) && $doublequote )
+ if ( is_string( $subElemValue ) && $doublequote ) {
$subElemValue = Sanitizer::encodeAttribute( $subElemValue );
-
+ }
+
if ( gettype( $subElemId ) === 'integer' ) {
$indElements[] = $subElemValue;
- unset ( $elemValue[$subElemId] );
+ unset( $elemValue[$subElemId] );
} elseif ( is_array( $subElemValue ) ) {
$subElements[$subElemId] = $subElemValue;
unset ( $elemValue[$subElemId] );
}
}
- if ( is_null( $subElemIndName ) && count( $indElements ) )
- ApiBase :: dieDebug( __METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName()." );
+ 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 ( count( $subElements ) && count( $indElements ) && !is_null( $subElemContent ) ) {
+ ApiBase::dieDebug( __METHOD__, "($elemName, ...) has content and subelements" );
+ }
if ( !is_null( $subElemContent ) ) {
$retval .= $indstr . Xml::element( $elemName, $elemValue, $subElemContent );
} elseif ( !count( $indElements ) && !count( $subElements ) ) {
- $retval .= $indstr . Xml::element( $elemName, $elemValue );
+ $retval .= $indstr . Xml::element( $elemName, $elemValue );
} else {
$retval .= $indstr . Xml::element( $elemName, $elemValue, null );
- foreach ( $subElements as $subElemId => & $subElemValue )
+ foreach ( $subElements as $subElemId => & $subElemValue ) {
$retval .= self::recXmlPrint( $subElemId, $subElemValue, $indent );
+ }
- foreach ( $indElements as $subElemId => & $subElemValue )
+ foreach ( $indElements as &$subElemValue ) {
$retval .= self::recXmlPrint( $subElemIndName, $subElemValue, $indent );
+ }
$retval .= $indstr . Xml::closeElement( $elemName );
}
break;
- case 'object' :
+ case 'object':
// ignore
break;
- default :
+ default:
$retval .= $indstr . Xml::element( $elemName, null, $elemValue );
break;
}
return $retval;
}
+
function addXslt() {
$nt = Title::newFromText( $this->mXslt );
if ( is_null( $nt ) || !$nt->exists() ) {
@@ -175,26 +188,26 @@ class ApiFormatXml extends ApiFormatBase {
}
$this->printText( '<?xml-stylesheet href="' . $nt->escapeLocalURL( 'action=raw' ) . '" type="text/xsl" ?>' );
}
-
+
public function getAllowedParams() {
- return array (
+ return array(
'xmldoublequote' => false,
'xslt' => null,
);
}
public function getParamDescription() {
- return array (
- 'xmldoublequote' => 'If specified, double quotes all attributes and content.',
+ return array(
+ 'xmldoublequote' => 'If specified, double quotes all attributes and content',
'xslt' => 'If specified, adds <xslt> as stylesheet',
);
}
public function getDescription() {
- return 'Output data in XML format' . parent :: getDescription();
+ return 'Output data in XML format' . parent::getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatXml.php 62402 2010-02-13 00:09:05Z reedy $';
+ return __CLASS__ . ': $Id: ApiFormatXml.php 73753 2010-09-25 16:56:03Z reedy $';
}
}
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
index 39381b0f..ccf52746 100644
--- a/includes/api/ApiFormatYaml.php
+++ b/includes/api/ApiFormatYaml.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 19, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 19, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,22 +18,25 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiFormatBase.php' );
+ require_once( 'ApiFormatBase.php' );
}
/**
+ * API YAML output formatter
* @ingroup API
*/
class ApiFormatYaml extends ApiFormatBase {
public function __construct( $main, $format ) {
- parent :: __construct( $main, $format );
+ parent::__construct( $main, $format );
}
public function getMimeType() {
@@ -42,14 +44,14 @@ class ApiFormatYaml extends ApiFormatBase {
}
public function execute() {
- $this->printText( Spyc :: YAMLDump( $this->getResultData() ) );
+ $this->printText( Spyc::YAMLDump( $this->getResultData() ) );
}
public function getDescription() {
- return 'Output data in YAML format' . parent :: getDescription();
+ return 'Output data in YAML format' . parent::getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatYaml.php 60930 2010-01-11 15:55:52Z simetrical $';
+ return __CLASS__ . ': $Id: ApiFormatYaml.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index 1f32e019..eedbde13 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 6, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 6, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
@@ -36,14 +37,71 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiHelp extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
/**
- * Stub module for displaying help when no parameters are given
+ * Module for displaying help
*/
public function execute() {
- $this->dieUsage( '', 'help' );
+ // Get parameters
+ $params = $this->extractRequestParams();
+
+ if ( !isset( $params['modules'] ) && !isset( $params['querymodules'] ) ) {
+ $this->dieUsage( '', 'help' );
+ }
+
+ $this->getMain()->setHelp();
+
+ $result = $this->getResult();
+ $queryObj = new ApiQuery( $this->getMain(), 'query' );
+ $r = array();
+ if ( is_array( $params['modules'] ) ) {
+ $modArr = $this->getMain()->getModules();
+
+ foreach ( $params['modules'] as $m ) {
+ if ( !isset( $modArr[$m] ) ) {
+ $r[] = array( 'name' => $m, 'missing' => '' );
+ continue;
+ }
+ $module = new $modArr[$m]( $this->getMain(), $m );
+
+ $r[] = $this->buildModuleHelp( $module, 'action' );
+ }
+ }
+
+ if ( is_array( $params['querymodules'] ) ) {
+ $qmodArr = $queryObj->getModules();
+
+ foreach ( $params['querymodules'] as $qm ) {
+ if ( !isset( $qmodArr[$qm] ) ) {
+ $r[] = array( 'name' => $qm, 'missing' => '' );
+ continue;
+ }
+ $module = new $qmodArr[$qm]( $this, $qm );
+ $type = $queryObj->getModuleType( $qm );
+
+ if ( $type === null ) {
+ $r[] = array( 'name' => $qm, 'missing' => '' );
+ continue;
+ }
+
+ $r[] = $this->buildModuleHelp( $module, $type );
+ }
+ }
+ $result->setIndexedTagName( $r, 'module' );
+ $result->addValue( null, $this->getModuleName(), $r );
+ }
+
+ private function buildModuleHelp( $module, $type ) {
+ $msg = ApiMain::makeHelpMsgHeader( $module, $type );
+
+ $msg2 = $module->makeHelpMsg();
+ if ( $msg2 !== false ) {
+ $msg .= $msg2;
+ }
+
+ return $msg;
}
public function shouldCheckMaxlag() {
@@ -54,13 +112,44 @@ class ApiHelp extends ApiBase {
return false;
}
+ public function getAllowedParams() {
+ return array(
+ 'modules' => array(
+ ApiBase::PARAM_ISMULTI => true
+ ),
+ 'querymodules' => array(
+ ApiBase::PARAM_ISMULTI => true
+ ),
+ );
+ }
+
+ 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)',
+ );
+ }
+
public function getDescription() {
- return array (
- 'Display this help screen.'
+ return 'Display this help screen. Or the help screen for the specified module';
+ }
+
+ protected function getExamples() {
+ return array(
+ 'Whole help page:',
+ ' api.php?action=help',
+ 'Module (action) help page:',
+ ' api.php?action=help&modules=protect',
+ 'Query (list) modules help page:',
+ ' api.php?action=help&querymodules=categorymembers',
+ 'Query (prop) modules help page:',
+ ' api.php?action=help&querymodules=info',
+ 'Query (meta) modules help page:',
+ ' api.php?action=help&querymodules=siteinfo',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiHelp.php 60930 2010-01-11 15:55:52Z simetrical $';
+ return __CLASS__ . ': $Id: ApiHelp.php 73863 2010-09-28 02:33:43Z brion $';
}
}
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
index d33a472a..1b5153f9 100644
--- a/includes/api/ApiImport.php
+++ b/includes/api/ApiImport.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Feb 4, 2009
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Feb 4, 2009
+ *
+ * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
@@ -36,58 +37,54 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiImport extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
global $wgUser;
- if ( !$wgUser->isAllowed( 'import' ) )
+ if ( !$wgUser->isAllowed( 'import' ) ) {
$this->dieUsageMsg( array( 'cantimport' ) );
+ }
$params = $this->extractRequestParams();
- $source = null;
$isUpload = false;
- if ( isset( $params['interwikisource'] ) )
- {
- if ( !isset( $params['interwikipage'] ) )
+ if ( isset( $params['interwikisource'] ) ) {
+ if ( !isset( $params['interwikipage'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'interwikipage' ) );
+ }
$source = ImportStreamSource::newFromInterwiki(
- $params['interwikisource'],
- $params['interwikipage'],
- $params['fullhistory'],
- $params['templates'] );
- }
- else
- {
+ $params['interwikisource'],
+ $params['interwikipage'],
+ $params['fullhistory'],
+ $params['templates']
+ );
+ } else {
$isUpload = true;
- if ( !$wgUser->isAllowed( 'importupload' ) )
+ if ( !$wgUser->isAllowed( 'importupload' ) ) {
$this->dieUsageMsg( array( 'cantimport-upload' ) );
+ }
$source = ImportStreamSource::newFromUpload( 'xml' );
}
- if ( $source instanceof WikiErrorMsg )
- $this->dieUsageMsg( array_merge(
- array( $source->getMessageKey() ),
- $source->getMessageArgs() ) );
- else if ( WikiError::isError( $source ) )
- // This shouldn't happen
- $this->dieUsageMsg( array( 'import-unknownerror', $source->getMessage() ) );
-
- $importer = new WikiImporter( $source );
- if ( isset( $params['namespace'] ) )
+ if ( !$source->isOK() ) {
+ $this->dieUsageMsg( $source->getErrorsArray() );
+ }
+
+ $importer = new WikiImporter( $source->value );
+ if ( isset( $params['namespace'] ) ) {
$importer->setTargetNamespace( $params['namespace'] );
- $reporter = new ApiImportReporter( $importer, $isUpload,
- $params['interwikisource'],
- $params['summary'] );
-
- $result = $importer->doImport();
- if ( $result instanceof WikiXmlError )
- $this->dieUsageMsg( array( 'import-xml-error',
- $result->mLine,
- $result->mColumn,
- $result->mByte . $result->mContext,
- xml_error_string( $result->mXmlError ) ) );
- else if ( WikiError::isError( $result ) )
- $this->dieUsageMsg( array( 'import-unknownerror', $result->getMessage() ) ); // This shouldn't happen
+ }
+ $reporter = new ApiImportReporter(
+ $importer,
+ $isUpload,
+ $params['interwikisource'],
+ $params['summary']
+ );
+
+ try {
+ $importer->doImport();
+ } catch ( MWException $e ) {
+ $this->dieUsageMsg( array( 'import-unknownerror', $e->getMessage() ) );
+ }
$resultData = $reporter->getData();
$this->getResult()->setIndexedTagName( $resultData, 'page' );
@@ -104,24 +101,24 @@ class ApiImport extends ApiBase {
public function getAllowedParams() {
global $wgImportSources;
- return array (
+ return array(
'token' => null,
'summary' => null,
'xml' => null,
'interwikisource' => array(
- ApiBase :: PARAM_TYPE => $wgImportSources
+ ApiBase::PARAM_TYPE => $wgImportSources
),
'interwikipage' => null,
'fullhistory' => false,
'templates' => false,
'namespace' => array(
- ApiBase :: PARAM_TYPE => 'namespace'
+ ApiBase::PARAM_TYPE => 'namespace'
)
);
}
public function getParamDescription() {
- return array (
+ return array(
'token' => 'Import token obtained through prop=info',
'summary' => 'Import summary',
'xml' => 'Uploaded XML file',
@@ -134,11 +131,9 @@ class ApiImport extends ApiBase {
}
public function getDescription() {
- return array (
- 'Import a page from another wiki, or an XML file'
- );
+ return 'Import a page from another wiki, or an XML file';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'cantimport' ),
@@ -148,7 +143,7 @@ class ApiImport extends ApiBase {
array( 'import-unknownerror', 'result' ),
) );
}
-
+
public function needsToken() {
return true;
}
@@ -160,12 +155,12 @@ class ApiImport extends ApiBase {
protected function getExamples() {
return array(
'Import [[meta:Help:Parserfunctions]] to namespace 100 with full history:',
- ' api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&namespace=100&fullhistory&token=123ABC',
+ ' api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&namespace=100&fullhistory=&token=123ABC',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiImport.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiImport.php 77800 2010-12-05 14:22:49Z ialex $';
}
}
@@ -176,8 +171,7 @@ class ApiImport extends ApiBase {
class ApiImportReporter extends ImportReporter {
private $mResultArr = array();
- function reportPage( $title, $origTitle, $revisionCount, $successCount )
- {
+ function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
// Add a result entry
$r = array();
ApiQueryBase::addTitleInfo( $r, $title );
@@ -185,11 +179,10 @@ class ApiImportReporter extends ImportReporter {
$this->mResultArr[] = $r;
// Piggyback on the parent to do the logging
- parent::reportPage( $title, $origTitle, $revisionCount, $successCount );
+ parent::reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo );
}
- function getData()
- {
+ function getData() {
return $this->mResultArr;
}
-} \ No newline at end of file
+}
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index 442bc44c..0675de7b 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -1,10 +1,9 @@
<?php
-
/**
- * Created on Sep 19, 2006
- *
* API for MediaWiki 1.8+
*
+ * Created on Sep 19, 2006
+ *
* Copyright © 2006-2007 Yuri Astrakhan <Firstname><Lastname>@gmail.com,
* Daniel Cannon (cannon dot danielc at gmail dot com)
*
@@ -20,8 +19,10 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
@@ -68,10 +69,11 @@ class ApiLogin extends ApiBase {
}
$loginForm = new LoginForm( $req );
+
+ global $wgCookiePrefix, $wgUser, $wgPasswordAttemptThrottle;
+
switch ( $authRes = $loginForm->authenticateUserData() ) {
case LoginForm::SUCCESS:
- global $wgUser, $wgCookiePrefix;
-
$wgUser->setOption( 'rememberpassword', 1 );
$wgUser->setCookies();
@@ -87,15 +89,14 @@ class ApiLogin extends ApiBase {
$result['cookieprefix'] = $wgCookiePrefix;
$result['sessionid'] = session_id();
break;
-
+
case LoginForm::NEED_TOKEN:
- global $wgCookiePrefix;
$result['result'] = 'NeedToken';
$result['token'] = $loginForm->getLoginToken();
$result['cookieprefix'] = $wgCookiePrefix;
$result['sessionid'] = session_id();
break;
-
+
case LoginForm::WRONG_TOKEN:
$result['result'] = 'WrongToken';
break;
@@ -131,7 +132,6 @@ class ApiLogin extends ApiBase {
break;
case LoginForm::THROTTLED:
- global $wgPasswordAttemptThrottle;
$result['result'] = 'Throttled';
$result['wait'] = intval( $wgPasswordAttemptThrottle['seconds'] );
break;
@@ -179,7 +179,7 @@ class ApiLogin extends ApiBase {
'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.'
+ 'This is to prevent password guessing by automated password crackers'
);
}
@@ -206,6 +206,6 @@ class ApiLogin extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiLogin.php 64697 2010-04-07 09:05:05Z catrope $';
+ return __CLASS__ . ': $Id: ApiLogin.php 76080 2010-11-05 11:54:35Z catrope $';
}
}
diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php
index 6637ee09..89326915 100644
--- a/includes/api/ApiLogout.php
+++ b/includes/api/ApiLogout.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Jan 4, 2008
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Yuri Astrakhan <Firstname><Lastname>@gmail.com,
+ * Created on Jan 4, 2008
+ *
+ * Copyright © 2008 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
@@ -37,14 +38,14 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiLogout extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
global $wgUser;
$oldName = $wgUser->getName();
$wgUser->logout();
-
+
// Give extensions to do something after user logout
$injected_html = '';
wfRunHooks( 'UserLogoutComplete', array( &$wgUser, &$injected_html, $oldName ) );
@@ -55,17 +56,15 @@ class ApiLogout extends ApiBase {
}
public function getAllowedParams() {
- return array ();
+ return array();
}
public function getParamDescription() {
- return array ();
+ return array();
}
public function getDescription() {
- return array (
- 'This module is used to logout and clear session data'
- );
+ return 'This module is used to logout and clear session data';
}
protected function getExamples() {
@@ -75,6 +74,6 @@ class ApiLogout extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiLogout.php 69578 2010-07-20 02:46:20Z tstarling $';
+ return __CLASS__ . ': $Id: ApiLogout.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index fa6957b6..d5238a51 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 4, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * 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
@@ -19,20 +18,19 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @defgroup API API
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
- * @defgroup API API
- */
-
-/**
* This is the main API class, used for both external and internal processing.
* When executed, it will create the requested formatter object,
* instantiate and execute an object associated with the needed action,
@@ -55,7 +53,7 @@ class ApiMain extends ApiBase {
/**
* List of available modules: action name => module class
*/
- private static $Modules = array (
+ private static $Modules = array(
'login' => 'ApiLogin',
'logout' => 'ApiLogout',
'query' => 'ApiQuery',
@@ -65,6 +63,7 @@ class ApiMain extends ApiBase {
'feedwatchlist' => 'ApiFeedWatchlist',
'help' => 'ApiHelp',
'paraminfo' => 'ApiParamInfo',
+ 'rsd' => 'ApiRsd',
// Write modules
'purge' => 'ApiPurge',
@@ -87,7 +86,7 @@ class ApiMain extends ApiBase {
/**
* List of available formats: format name => format class
*/
- private static $Formats = array (
+ private static $Formats = array(
'json' => 'ApiFormatJson',
'jsonfm' => 'ApiFormatJson',
'php' => 'ApiFormatPhp',
@@ -102,7 +101,9 @@ class ApiMain extends ApiBase {
'txt' => 'ApiFormatTxt',
'txtfm' => 'ApiFormatTxt',
'dbg' => 'ApiFormatDbg',
- 'dbgfm' => 'ApiFormatDbg'
+ 'dbgfm' => 'ApiFormatDbg',
+ 'dump' => 'ApiFormatDump',
+ 'dumpfm' => 'ApiFormatDump',
);
/**
@@ -111,17 +112,17 @@ class ApiMain extends ApiBase {
* 'params' => array ( $someVarToSubst ) ),
* );
*/
- private static $mRights = array( 'writeapi' => array(
- 'msg' => 'Use of the write API',
- '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.',
- 'params' => array ( ApiMain::LIMIT_SML2, ApiMain::LIMIT_BIG2 )
- )
+ private static $mRights = array(
+ 'writeapi' => array(
+ 'msg' => 'Use of the write API',
+ '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.',
+ 'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 )
+ )
);
-
private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames;
private $mResult, $mAction, $mShowVersions, $mEnableWrite, $mRequest;
private $mInternalMode, $mSquidMaxage, $mModule;
@@ -130,20 +131,18 @@ class ApiMain extends ApiBase {
private $mCacheControl = array();
/**
- * Constructs an instance of ApiMain that utilizes the module and format specified by $request.
- *
- * @param $request object - if this is an instance of FauxRequest, errors are thrown and no printing occurs
- * @param $enableWrite bool should be set to true if the api may modify data
- */
+ * Constructs an instance of ApiMain that utilizes the module and format specified by $request.
+ *
+ * @param $request WebRequest - if this is an instance of FauxRequest, errors are thrown and no printing occurs
+ * @param $enableWrite bool should be set to true if the api may modify data
+ */
public function __construct( $request, $enableWrite = false ) {
-
$this->mInternalMode = ( $request instanceof FauxRequest );
// Special handling for the main module: $parent === $this
- parent :: __construct( $this, $this->mInternalMode ? 'main_int' : 'main' );
+ parent::__construct( $this, $this->mInternalMode ? 'main_int' : 'main' );
if ( !$this->mInternalMode ) {
-
// Impose module restrictions.
// If the current user cannot read,
// Remove all modules other than login
@@ -158,17 +157,17 @@ class ApiMain extends ApiBase {
}
global $wgAPIModules; // extension modules
- $this->mModules = $wgAPIModules + self :: $Modules;
+ $this->mModules = $wgAPIModules + self::$Modules;
$this->mModuleNames = array_keys( $this->mModules );
- $this->mFormats = self :: $Formats;
+ $this->mFormats = self::$Formats;
$this->mFormatNames = array_keys( $this->mFormats );
$this->mResult = new ApiResult( $this );
$this->mShowVersions = false;
$this->mEnableWrite = $enableWrite;
- $this->mRequest = & $request;
+ $this->mRequest = &$request;
$this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling()
$this->mCommit = false;
@@ -183,6 +182,7 @@ class ApiMain extends ApiBase {
/**
* Return the request object that contains client's request
+ * @return WebRequest
*/
public function getRequest() {
return $this->mRequest;
@@ -190,6 +190,8 @@ class ApiMain extends ApiBase {
/**
* Get the ApiResult object associated with current request
+ *
+ * @return ApiResult
*/
public function getResult() {
return $this->mResult;
@@ -203,14 +205,12 @@ class ApiMain extends ApiBase {
}
/**
- * Only kept for backwards compatibility
- * @deprecated Use isWriteMode() instead
+ * Get the result formatter object. Only works after setupExecuteAction()
+ *
+ * @return ApiFormatBase
*/
- public function requestWriteMode() {
- if ( !$this->mEnableWrite )
- $this->dieUsageMsg( array( 'writedisabled' ) );
- if ( wfReadOnly() )
- $this->dieUsageMsg( array( 'readonlytext' ) );
+ public function getPrinter() {
+ return $this->mPrinter;
}
/**
@@ -226,31 +226,31 @@ class ApiMain extends ApiBase {
/**
* Set the type of caching headers which will be sent.
*
- * @param $mode One of:
- * - 'public': Cache this object in public caches, if the maxage or smaxage
+ * @param $mode String One of:
+ * - 'public': Cache this object in public caches, if the maxage or smaxage
* parameter is set, or if setCacheMaxAge() was called. If a maximum age is
* not provided by any of these means, the object will be private.
* - 'private': Cache this object only in private client-side caches.
* - 'anon-public-user-private': Make this object cacheable for logged-out
- * users, but private for logged-in users. IMPORTANT: If this is set, it must be
- * set consistently for a given URL, it cannot be set differently depending on
+ * users, but private for logged-in users. IMPORTANT: If this is set, it must be
+ * set consistently for a given URL, it cannot be set differently depending on
* things like the contents of the database, or whether the user is logged in.
*
* If the wiki does not allow anonymous users to read it, the mode set here
- * will be ignored, and private caching headers will always be sent. In other words,
+ * will be ignored, and private caching headers will always be sent. In other words,
* the "public" mode is equivalent to saying that the data sent is as public as a page
* view.
*
- * For user-dependent data, the private mode should generally be used. The
- * anon-public-user-private mode should only be used where there is a particularly
+ * For user-dependent data, the private mode should generally be used. The
+ * anon-public-user-private mode should only be used where there is a particularly
* good performance reason for caching the anonymous response, but where the
- * response to logged-in users may differ, or may contain private data.
+ * response to logged-in users may differ, or may contain private data.
*
* If this function is never called, then the default will be the private mode.
*/
public function setCacheMode( $mode ) {
if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) {
- wfDebug( __METHOD__.": unrecognised cache mode \"$mode\"\n" );
+ wfDebug( __METHOD__ . ": unrecognised cache mode \"$mode\"\n" );
// Ignore for forwards-compatibility
return;
}
@@ -258,18 +258,18 @@ class ApiMain extends ApiBase {
if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
// Private wiki, only private headers
if ( $mode !== 'private' ) {
- wfDebug( __METHOD__.": ignoring request for $mode cache mode, private wiki\n" );
+ wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" );
return;
}
}
- wfDebug( __METHOD__.": setting cache mode $mode\n" );
+ wfDebug( __METHOD__ . ": setting cache mode $mode\n" );
$this->mCacheMode = $mode;
}
-
+
/**
- * @deprecated Private caching is now the default, so there is usually no
- * need to call this function. If there is a need, you can use
+ * @deprecated Private caching is now the default, so there is usually no
+ * need to call this function. If there is a need, you can use
* $this->setCacheMode('private')
*/
public function setCachePrivate() {
@@ -281,13 +281,13 @@ class ApiMain extends ApiBase {
* Boolean values will be formatted as such, by including or omitting
* without an equals sign.
*
- * Cache control values set here will only be used if the cache mode is not
+ * Cache control values set here will only be used if the cache mode is not
* private, see setCacheMode().
*/
public function setCacheControl( $directives ) {
$this->mCacheControl = $directives + $this->mCacheControl;
}
-
+
/**
* Make sure Vary: Cookie and friends are set. Use this when the output of a request
* may be cached for anons but may not be cached for logged-in users.
@@ -306,8 +306,9 @@ class ApiMain extends ApiBase {
* Create an instance of an output formatter by its name
*/
public function createPrinterByName( $format ) {
- if ( !isset( $this->mFormats[$format] ) )
+ if ( !isset( $this->mFormats[$format] ) ) {
$this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
+ }
return new $this->mFormats[$format] ( $this, $format );
}
@@ -316,10 +317,11 @@ class ApiMain extends ApiBase {
*/
public function execute() {
$this->profileIn();
- if ( $this->mInternalMode )
+ if ( $this->mInternalMode ) {
$this->executeAction();
- else
+ } else {
$this->executeActionWithErrorHandling();
+ }
$this->profileOut();
}
@@ -329,7 +331,6 @@ class ApiMain extends ApiBase {
* have been accumulated, and replace it with an error message and a help screen.
*/
protected function executeActionWithErrorHandling() {
-
// In case an error occurs during data output,
// clear the output buffer and print just the error information
ob_start();
@@ -354,10 +355,11 @@ class ApiMain extends ApiBase {
$this->setCacheMode( 'private' );
$headerStr = 'MediaWiki-API-Error: ' . $errCode;
- if ( $e->getCode() === 0 )
+ if ( $e->getCode() === 0 ) {
header( $headerStr );
- else
+ } else {
header( $headerStr, true, $e->getCode() );
+ }
// Reset and print just the error message
ob_clean();
@@ -367,11 +369,11 @@ class ApiMain extends ApiBase {
$this->printResult( true );
}
- // Send cache headers after any code which might generate an error, to
+ // Send cache headers after any code which might generate an error, to
// avoid sending public cache headers for errors.
$this->sendCacheHeaders();
- if ( $this->mPrinter->getIsHtml() ) {
+ if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) {
echo wfReportTime();
}
@@ -401,12 +403,6 @@ class ApiMain extends ApiBase {
header( 'Cache-Control: private' );
return;
} // else no XVO and anonymous, send public headers below
- } else /* if public */ {
- // Give a debugging message if the user object is unstubbed on a public request
- global $wgUser;
- if ( !( $wgUser instanceof StubUser ) ) {
- wfDebug( __METHOD__." \$wgUser is unstubbed on a public request!\n" );
- }
}
// If nobody called setCacheMaxAge(), use the (s)maxage parameters
@@ -446,26 +442,28 @@ class ApiMain extends ApiBase {
$separator = ', ';
}
}
-
+
header( "Cache-Control: $ccHeader" );
}
/**
* Replace the result data with the information about an exception.
* Returns the error code
+ * @param $e Exception
*/
protected function substituteResultWithError( $e ) {
-
// Printer may not be initialized if the extractRequestParams() fails for the main module
if ( !isset ( $this->mPrinter ) ) {
// The printer has not been created yet. Try to manually get formatter value.
$value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT );
- if ( !in_array( $value, $this->mFormatNames ) )
+ if ( !in_array( $value, $this->mFormatNames ) ) {
$value = self::API_DEFAULT_FORMAT;
+ }
$this->mPrinter = $this->createPrinterByName( $value );
- if ( $this->mPrinter->getNeedsRawData() )
+ if ( $this->mPrinter->getNeedsRawData() ) {
$this->getResult()->setRawMode();
+ }
}
if ( $e instanceof UsageException ) {
@@ -475,8 +473,9 @@ class ApiMain extends ApiBase {
$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() );
+ if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) {
+ ApiResult::setContent( $errMessage, $this->makeHelpMsg() );
+ }
} else {
global $wgShowSQLErrors, $wgShowExceptionDetails;
@@ -484,37 +483,45 @@ class ApiMain extends ApiBase {
// Something is seriously wrong
//
if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
- $info = "Database query error";
+ $info = 'Database query error';
} else {
$info = "Exception Caught: {$e->getMessage()}";
}
- $errMessage = array (
+ $errMessage = array(
'code' => 'internal_api_error_' . get_class( $e ),
'info' => $info,
);
- ApiResult :: setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : "" );
+ ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' );
}
$this->getResult()->reset();
$this->getResult()->disableSizeCheck();
// Re-add the id
$requestid = $this->getParameter( 'requestid' );
- if ( !is_null( $requestid ) )
+ if ( !is_null( $requestid ) ) {
$this->getResult()->addValue( null, 'requestid', $requestid );
+ }
+ // servedby is especially useful when debugging errors
+ $this->getResult()->addValue( null, 'servedby', wfHostName() );
$this->getResult()->addValue( null, 'error', $errMessage );
return $errMessage['code'];
}
/**
- * Execute the actual module, without any error handling
+ * Set up for the execution.
*/
- protected function executeAction() {
+ protected function setupExecuteAction() {
// First add the id to the top element
$requestid = $this->getParameter( 'requestid' );
- if ( !is_null( $requestid ) )
+ if ( !is_null( $requestid ) ) {
$this->getResult()->addValue( null, 'requestid', $requestid );
+ }
+ $servedby = $this->getParameter( 'servedby' );
+ if ( $servedby ) {
+ $this->getResult()->addValue( null, 'servedby', wfHostName() );
+ }
$params = $this->extractRequestParams();
@@ -522,19 +529,26 @@ class ApiMain extends ApiBase {
$this->mAction = $params['action'];
if ( !is_string( $this->mAction ) ) {
- $this->dieUsage( "The API requires a valid action parameter", 'unknown_action' );
+ $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' );
}
-
+
+ return $params;
+ }
+
+ /**
+ * Set up the module for response
+ * @return ApiBase The module that will handle this action
+ */
+ protected function setupModule() {
// Instantiate the module requested by the user
$module = new $this->mModules[$this->mAction] ( $this, $this->mAction );
$this->mModule = $module;
$moduleParams = $module->extractRequestParams();
-
+
// Die if token required, but not provided (unless there is a gettoken parameter)
$salt = $module->getTokenSalt();
- if ( $salt !== false && !isset( $moduleParams['gettoken'] ) )
- {
+ if ( $salt !== false && !isset( $moduleParams['gettoken'] ) ) {
if ( !isset( $moduleParams['token'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'token' ) );
} else {
@@ -544,7 +558,16 @@ class ApiMain extends ApiBase {
}
}
}
+ return $module;
+ }
+ /**
+ * Check the max lag if necessary
+ * @param $module ApiBase object: Api module being used
+ * @param $params Array an array containing the request parameters.
+ * @return boolean True on success, false should exit immediately
+ */
+ protected function checkMaxLag( $module, $params ) {
if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
// Check for maxlag
global $wgShowHostnames;
@@ -558,36 +581,75 @@ class ApiMain extends ApiBase {
} else {
$this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
}
- return;
+ return false;
}
}
+ return true;
+ }
+
- global $wgUser, $wgGroupPermissions;
- if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) && !$wgUser->isAllowed( 'read' ) )
+ /**
+ * Check for sufficient permissions to execute
+ * @param $module ApiBase An Api module
+ */
+ protected function checkExecutePermissions( $module ) {
+ global $wgUser;
+ if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) &&
+ !$wgUser->isAllowed( 'read' ) )
+ {
$this->dieUsageMsg( array( 'readrequired' ) );
+ }
if ( $module->isWriteMode() ) {
- if ( !$this->mEnableWrite )
+ if ( !$this->mEnableWrite ) {
$this->dieUsageMsg( array( 'writedisabled' ) );
- if ( !$wgUser->isAllowed( 'writeapi' ) )
+ }
+ if ( !$wgUser->isAllowed( 'writeapi' ) ) {
$this->dieUsageMsg( array( 'writerequired' ) );
- if ( wfReadOnly() )
+ }
+ if ( wfReadOnly() ) {
$this->dieReadOnly();
+ }
}
+ }
- if ( !$this->mInternalMode ) {
- // Ignore mustBePosted() for internal calls
- if ( $module->mustBePosted() && !$this->mRequest->wasPosted() )
- $this->dieUsageMsg( array ( 'mustbeposted', $this->mAction ) );
-
- // See if custom printer is used
- $this->mPrinter = $module->getCustomPrinter();
- if ( is_null( $this->mPrinter ) ) {
- // Create an appropriate printer
- $this->mPrinter = $this->createPrinterByName( $params['format'] );
- }
+ /**
+ * Check POST for external response and setup result printer
+ * @param $module ApiBase An Api module
+ * @param $params Array an array with the request parameters
+ */
+ protected function setupExternalResponse( $module, $params ) {
+ // Ignore mustBePosted() for internal calls
+ if ( $module->mustBePosted() && !$this->mRequest->wasPosted() ) {
+ $this->dieUsageMsg( array( 'mustbeposted', $this->mAction ) );
+ }
- if ( $this->mPrinter->getNeedsRawData() )
- $this->getResult()->setRawMode();
+ // See if custom printer is used
+ $this->mPrinter = $module->getCustomPrinter();
+ if ( is_null( $this->mPrinter ) ) {
+ // Create an appropriate printer
+ $this->mPrinter = $this->createPrinterByName( $params['format'] );
+ }
+
+ if ( $this->mPrinter->getNeedsRawData() ) {
+ $this->getResult()->setRawMode();
+ }
+ }
+
+ /**
+ * Execute the actual module, without any error handling
+ */
+ protected function executeAction() {
+ $params = $this->setupExecuteAction();
+ $module = $this->setupModule();
+
+ $this->checkExecutePermissions( $module );
+
+ if ( !$this->checkMaxLag( $module, $params ) ) {
+ return;
+ }
+
+ if ( !$this->mInternalMode ) {
+ $this->setupExternalResponse( $module, $params );
}
// Execute
@@ -610,10 +672,12 @@ class ApiMain extends ApiBase {
$printer = $this->mPrinter;
$printer->profileIn();
- /* If the help message is requested in the default (xmlfm) format,
+ /**
+ * 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. */
- $printer->setUnescapeAmps ( ( $this->mAction == 'help' || $isError )
+ * not break.
+ */
+ $printer->setUnescapeAmps( ( $this->mAction == 'help' || $isError )
&& $printer->getFormat() == 'XML' && $printer->getIsHtml() );
$printer->initPrinter( $isError );
@@ -631,28 +695,29 @@ class ApiMain extends ApiBase {
* See ApiBase for description.
*/
public function getAllowedParams() {
- return array (
- 'format' => array (
- ApiBase :: PARAM_DFLT => ApiMain :: API_DEFAULT_FORMAT,
- ApiBase :: PARAM_TYPE => $this->mFormatNames
+ return array(
+ 'format' => array(
+ ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
+ ApiBase::PARAM_TYPE => $this->mFormatNames
),
- 'action' => array (
- ApiBase :: PARAM_DFLT => 'help',
- ApiBase :: PARAM_TYPE => $this->mModuleNames
+ 'action' => array(
+ ApiBase::PARAM_DFLT => 'help',
+ ApiBase::PARAM_TYPE => $this->mModuleNames
),
'version' => false,
- 'maxlag' => array (
- ApiBase :: PARAM_TYPE => 'integer'
+ 'maxlag' => array(
+ ApiBase::PARAM_TYPE => 'integer'
),
- 'smaxage' => array (
- ApiBase :: PARAM_TYPE => 'integer',
- ApiBase :: PARAM_DFLT => 0
+ 'smaxage' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_DFLT => 0
),
- 'maxage' => array (
- ApiBase :: PARAM_TYPE => 'integer',
- ApiBase :: PARAM_DFLT => 0
+ 'maxage' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_DFLT => 0
),
'requestid' => null,
+ 'servedby' => false,
);
}
@@ -660,14 +725,15 @@ class ApiMain extends ApiBase {
* See ApiBase for description.
*/
public function getParamDescription() {
- return array (
+ return array(
'format' => 'The format of the output',
- 'action' => 'What action you would like to perform',
+ 'action' => 'What action you would like to perform. See below for module help',
'version' => 'When showing help, include version for each module',
'maxlag' => 'Maximum lag',
'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',
'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',
);
}
@@ -675,25 +741,26 @@ class ApiMain extends ApiBase {
* See ApiBase for description.
*/
public function getDescription() {
- return array (
+ return array(
'',
'',
- '******************************************************************',
- '** **',
- '** This is an auto-generated MediaWiki API documentation page **',
- '** **',
- '** Documentation and Examples: **',
- '** http://www.mediawiki.org/wiki/API **',
- '** **',
- '******************************************************************',
+ '******************************************************************************************',
+ '** **',
+ '** This is an auto-generated MediaWiki API documentation page **',
+ '** **',
+ '** Documentation and Examples: **',
+ '** http://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.',
+ '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',
'',
- 'Documentation: http://www.mediawiki.org/wiki/API',
- 'Mailing list: http://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
- 'Bugs & Requests: http://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
+ 'Documentation: http://www.mediawiki.org/wiki/API',
+ 'Mailing list: http://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
+ 'Api Announcements: http://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce',
+ 'Bugs & Requests: http://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
'',
'',
'',
@@ -702,14 +769,14 @@ class ApiMain extends ApiBase {
);
}
- public function getPossibleErrors() {
+ public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'readonlytext' ),
array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ),
array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ),
array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ),
array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ),
- ) );
+ ) );
}
/**
@@ -728,74 +795,83 @@ class ApiMain extends ApiBase {
'or file a bug report at http://bugzilla.wikimedia.org/'
);
}
+ /**
+ * Sets whether the pretty-printer should format *bold* and $italics$
+ */
+ public function setHelp( $help = true ) {
+ $this->mPrinter->setHelp( $help );
+ }
/**
* Override the parent to generate help messages for all available modules.
*/
public function makeHelpMsg() {
global $wgMemc, $wgAPICacheHelp, $wgAPICacheHelpTimeout;
- $this->mPrinter->setHelp();
+ $this->setHelp();
// Get help text from cache if present
$key = wfMemcKey( 'apihelp', $this->getModuleName(),
SpecialVersion::getVersion( 'nodb' ) .
$this->getMain()->getShowVersions() );
if ( $wgAPICacheHelp ) {
$cached = $wgMemc->get( $key );
- if ( $cached )
+ if ( $cached ) {
return $cached;
+ }
}
$retval = $this->reallyMakeHelpMsg();
- if ( $wgAPICacheHelp )
+ if ( $wgAPICacheHelp ) {
$wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout );
+ }
return $retval;
}
public function reallyMakeHelpMsg() {
-
- $this->mPrinter->setHelp();
+ $this->setHelp();
// Use parent to make default message for the main module
- $msg = parent :: makeHelpMsg();
+ $msg = parent::makeHelpMsg();
$astriks = str_repeat( '*** ', 10 );
$msg .= "\n\n$astriks Modules $astriks\n\n";
- foreach ( $this->mModules as $moduleName => $unused ) {
+ foreach ( array_keys( $this->mModules ) as $moduleName ) {
$module = new $this->mModules[$moduleName] ( $this, $moduleName );
$msg .= self::makeHelpMsgHeader( $module, 'action' );
$msg2 = $module->makeHelpMsg();
- if ( $msg2 !== false )
+ if ( $msg2 !== false ) {
$msg .= $msg2;
+ }
$msg .= "\n";
}
$msg .= "\n$astriks Permissions $astriks\n\n";
- foreach ( self :: $mRights as $right => $rightMsg ) {
+ foreach ( self::$mRights as $right => $rightMsg ) {
$groups = User::getGroupsWithPermission( $right );
$msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) .
- "\nGranted to:\n " . str_replace( "*", "all", implode( ", ", $groups ) ) . "\n";
+ "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
}
$msg .= "\n$astriks Formats $astriks\n\n";
- foreach ( $this->mFormats as $formatName => $unused ) {
+ foreach ( array_keys( $this->mFormats ) as $formatName ) {
$module = $this->createPrinterByName( $formatName );
$msg .= self::makeHelpMsgHeader( $module, 'format' );
$msg2 = $module->makeHelpMsg();
- if ( $msg2 !== false )
+ if ( $msg2 !== false ) {
$msg .= $msg2;
+ }
$msg .= "\n";
}
$msg .= "\n*** Credits: ***\n " . implode( "\n ", $this->getCredits() ) . "\n";
-
return $msg;
}
public static function makeHelpMsgHeader( $module, $paramName ) {
$modulePrefix = $module->getModulePrefix();
- if ( strval( $modulePrefix ) !== '' )
+ if ( strval( $modulePrefix ) !== '' ) {
$modulePrefix = "($modulePrefix) ";
+ }
return "* $paramName={$module->getModuleName()} $modulePrefix*";
}
@@ -809,7 +885,7 @@ class ApiMain extends ApiBase {
* OBSOLETE, use canApiHighLimits() instead
*/
public function isBot() {
- if ( !isset ( $this->mIsBot ) ) {
+ if ( !isset( $this->mIsBot ) ) {
global $wgUser;
$this->mIsBot = $wgUser->isAllowed( 'bot' );
}
@@ -822,7 +898,7 @@ class ApiMain extends ApiBase {
* OBSOLETE, use canApiHighLimits() instead
*/
public function isSysop() {
- if ( !isset ( $this->mIsSysop ) ) {
+ if ( !isset( $this->mIsSysop ) ) {
global $wgUser;
$this->mIsSysop = in_array( 'sysop', $wgUser->getGroups() );
}
@@ -858,10 +934,10 @@ class ApiMain extends ApiBase {
public function getVersion() {
$vers = array ();
$vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
- $vers[] = __CLASS__ . ': $Id: ApiMain.php 70066 2010-07-28 05:52:32Z tstarling $';
- $vers[] = ApiBase :: getBaseVersion();
- $vers[] = ApiFormatBase :: getBaseVersion();
- $vers[] = ApiQueryBase :: getBaseVersion();
+ $vers[] = __CLASS__ . ': $Id: ApiMain.php 76196 2010-11-06 16:11:19Z reedy $';
+ $vers[] = ApiBase::getBaseVersion();
+ $vers[] = ApiFormatBase::getBaseVersion();
+ $vers[] = ApiQueryBase::getBaseVersion();
return $vers;
}
@@ -870,7 +946,6 @@ class ApiMain extends ApiBase {
* classes who wish to add their own modules to their lexicon or override the
* behavior of inherent ones.
*
- * @access protected
* @param $mdlName String The identifier for this module.
* @param $mdlClass String The class where this module is implemented.
*/
@@ -882,7 +957,6 @@ class ApiMain extends ApiBase {
* Add or overwrite an output format for this ApiMain. Intended for use by extending
* classes who wish to add to or modify current formatters.
*
- * @access protected
* @param $fmtName The identifier for this format.
* @param $fmtClass The class implementing this format.
*/
@@ -910,22 +984,26 @@ class UsageException extends Exception {
private $mExtraData;
public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
- parent :: __construct( $message, $code );
+ parent::__construct( $message, $code );
$this->mCodestr = $codestr;
$this->mExtraData = $extradata;
}
+
public function getCodeString() {
return $this->mCodestr;
}
+
public function getMessageArray() {
- $result = array (
- 'code' => $this->mCodestr,
- 'info' => $this->getMessage()
+ $result = array(
+ 'code' => $this->mCodestr,
+ 'info' => $this->getMessage()
);
- if ( is_array( $this->mExtraData ) )
+ if ( is_array( $this->mExtraData ) ) {
$result = array_merge( $result, $this->mExtraData );
+ }
return $result;
}
+
public function __toString() {
return "{$this->getCodeString()}: {$this->getMessage()}";
}
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index c234f084..a93188bf 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -1,10 +1,10 @@
<?php
-
-/*
- * Created on Oct 31, 2007
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Oct 31, 2007
+ *
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,55 +18,57 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
-
/**
+ * API Module to move pages
* @ingroup API
*/
class ApiMove extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
global $wgUser;
$params = $this->extractRequestParams();
- if ( is_null( $params['reason'] ) )
+ if ( is_null( $params['reason'] ) ) {
$params['reason'] = '';
+ }
$this->requireOnlyOneParameter( $params, 'from', 'fromid' );
- if ( !isset( $params['to'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'to' ) );
- if ( isset( $params['from'] ) )
- {
+ if ( isset( $params['from'] ) ) {
$fromTitle = Title::newFromText( $params['from'] );
- if ( !$fromTitle )
+ if ( !$fromTitle ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['from'] ) );
- }
- else if ( isset( $params['fromid'] ) )
- {
+ }
+ } elseif ( isset( $params['fromid'] ) ) {
$fromTitle = Title::newFromID( $params['fromid'] );
- if ( !$fromTitle )
+ if ( !$fromTitle ) {
$this->dieUsageMsg( array( 'nosuchpageid', $params['fromid'] ) );
+ }
}
- if ( !$fromTitle->exists() )
+ if ( !$fromTitle->exists() ) {
$this->dieUsageMsg( array( 'notanarticle' ) );
+ }
$fromTalk = $fromTitle->getTalkPage();
$toTitle = Title::newFromText( $params['to'] );
- if ( !$toTitle )
+ if ( !$toTitle ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['to'] ) );
+ }
$toTalk = $toTitle->getTalkPage();
if ( $toTitle->getNamespace() == NS_FILE
@@ -81,27 +83,24 @@ class ApiMove extends ApiBase {
}
// Move the page
- $hookErr = null;
$retval = $fromTitle->moveTo( $toTitle, true, $params['reason'], !$params['noredirect'] );
- if ( $retval !== true )
+ if ( $retval !== true ) {
$this->dieUsageMsg( reset( $retval ) );
+ }
$r = array( 'from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason'] );
- if ( !$params['noredirect'] || !$wgUser->isAllowed( 'suppressredirect' ) )
+ if ( !$params['noredirect'] || !$wgUser->isAllowed( 'suppressredirect' ) ) {
$r['redirectcreated'] = '';
+ }
// Move the talk page
- if ( $params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage() )
- {
+ if ( $params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage() ) {
$retval = $fromTalk->moveTo( $toTalk, true, $params['reason'], !$params['noredirect'] );
- if ( $retval === true )
- {
+ if ( $retval === true ) {
$r['talkfrom'] = $fromTalk->getPrefixedText();
$r['talkto'] = $toTalk->getPrefixedText();
- }
- // We're not gonna dieUsage() on failure, since we already changed something
- else
- {
+ } 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'];
@@ -109,51 +108,49 @@ class ApiMove extends ApiBase {
}
// Move subpages
- if ( $params['movesubpages'] )
- {
+ if ( $params['movesubpages'] ) {
$r['subpages'] = $this->moveSubpages( $fromTitle, $toTitle,
$params['reason'], $params['noredirect'] );
$this->getResult()->setIndexedTagName( $r['subpages'], 'subpage' );
- if ( $params['movetalk'] )
- {
+ if ( $params['movetalk'] ) {
$r['subpages-talk'] = $this->moveSubpages( $fromTalk, $toTalk,
$params['reason'], $params['noredirect'] );
$this->getResult()->setIndexedTagName( $r['subpages-talk'], 'subpage' );
}
}
- // Watch pages
- if ( $params['watch'] || $wgUser->getOption( 'watchmoves' ) )
- {
- $wgUser->addWatch( $fromTitle );
- $wgUser->addWatch( $toTitle );
- }
- else if ( $params['unwatch'] )
- {
- $wgUser->removeWatch( $fromTitle );
- $wgUser->removeWatch( $toTitle );
+ $watch = "preferences";
+ if ( isset( $params['watchlist'] ) ) {
+ $watch = $params['watchlist'];
+ } elseif ( $params['watch'] ) {
+ $watch = 'watch';
+ } elseif ( $params['unwatch'] ) {
+ $watch = 'unwatch';
}
+
+ // Watch pages
+ $this->setWatch( $watch, $fromTitle, 'watchmoves' );
+ $this->setWatch( $watch, $toTitle, 'watchmoves' );
+
$this->getResult()->addValue( null, $this->getModuleName(), $r );
}
- public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect )
- {
+ public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect ) {
$retval = array();
$success = $fromTitle->moveSubpages( $toTitle, true, $reason, !$noredirect );
- if ( isset( $success[0] ) )
+ if ( isset( $success[0] ) ) {
return array( 'error' => $this->parseMsg( $success ) );
- else
- {
+ } else {
// At least some pages could be moved
// Report each of them separately
- foreach ( $success as $oldTitle => $newTitle )
- {
+ foreach ( $success as $oldTitle => $newTitle ) {
$r = array( 'from' => $oldTitle );
- if ( is_array( $newTitle ) )
+ if ( is_array( $newTitle ) ) {
$r['error'] = $this->parseMsg( reset( $newTitle ) );
- else
+ } else {
// Success
$r['to'] = $newTitle;
+ }
$retval[] = $r;
}
}
@@ -169,48 +166,65 @@ class ApiMove extends ApiBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'from' => null,
'fromid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
- 'to' => null,
+ 'to' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'token' => null,
'reason' => null,
'movetalk' => false,
'movesubpages' => false,
'noredirect' => false,
- 'watch' => false,
- 'unwatch' => false,
+ 'watch' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'unwatch' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'watchlist' => array(
+ ApiBase::PARAM_DFLT => 'preferences',
+ ApiBase::PARAM_TYPE => array(
+ 'watch',
+ 'unwatch',
+ 'preferences',
+ 'nochange'
+ ),
+ ),
'ignorewarnings' => false
);
}
public function getParamDescription() {
- return array (
- 'from' => 'Title of the page you want to move. Cannot be used together with fromid.',
- 'fromid' => 'Page ID of the page you want to move. Cannot be used together with from.',
- 'to' => 'Title you want to rename the page to.',
+ $p = $this->getModulePrefix();
+ return array(
+ 'from' => "Title of the page you want to move. Cannot be used together with {$p}fromid",
+ 'fromid' => "Page ID of the page you want to move. Cannot be used together with {$p}from",
+ 'to' => 'Title you want to rename the page to',
'token' => 'A move token previously retrieved through prop=info',
- 'reason' => 'Reason for the move (optional).',
- 'movetalk' => 'Move the talk page, if it exists.',
+ 'reason' => 'Reason for the move (optional)',
+ '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 array(
- 'Move a page.'
- );
+ return 'Move a page';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'to' ),
array( 'invalidtitle', 'from' ),
array( 'nosuchpageid', 'fromid' ),
array( 'notanarticle' ),
@@ -228,12 +242,12 @@ class ApiMove extends ApiBase {
}
protected function getExamples() {
- return array (
- 'api.php?action=move&from=Exampel&to=Example&token=123ABC&reason=Misspelled%20title&movetalk&noredirect'
+ return array(
+ 'api.php?action=move&from=Exampel&to=Example&token=123ABC&reason=Misspelled%20title&movetalk=&noredirect='
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiMove.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiMove.php 77192 2010-11-23 22:05:27Z btongminh $';
}
}
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index e145d80c..885766d2 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 13, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Oct 13, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -34,7 +35,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiOpenSearch extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function getCustomPrinter() {
@@ -50,44 +51,63 @@ class ApiOpenSearch extends ApiBase {
$suggest = $params['suggest'];
// MWSuggest or similar hit
- if ( $suggest && !$wgEnableOpenSearchSuggest )
- $srchres = array();
- else {
- // Open search results may be stored for a very long
- // time
+ if ( $suggest && !$wgEnableOpenSearchSuggest ) {
+ $searches = array();
+ } else {
+ // Open search results may be stored for a very long time
$this->getMain()->setCacheMaxAge( $wgSearchSuggestCacheExpiry );
$this->getMain()->setCacheMode( 'public' );
- $srchres = PrefixSearch::titleSearch( $search, $limit,
+ $searches = PrefixSearch::titleSearch( $search, $limit,
$namespaces );
+
+ // if the content language has variants, try to retrieve fallback results
+ $fallbackLimit = $limit - count( $searches );
+ if ( $fallbackLimit > 0 ) {
+ global $wgContLang;
+
+ $fallbackSearches = $wgContLang->autoConvertToAllVariants( $search );
+ $fallbackSearches = array_diff( array_unique( $fallbackSearches ), array( $search ) );
+
+ foreach ( $fallbackSearches as $fbs ) {
+ $fallbackSearchResult = PrefixSearch::titleSearch( $fbs, $fallbackLimit,
+ $namespaces );
+ $searches = array_merge( $searches, $fallbackSearchResult );
+ $fallbackLimit -= count( $fallbackSearchResult );
+
+ if ( $fallbackLimit == 0 ) {
+ break;
+ }
+ }
+ }
}
// Set top level elements
$result = $this->getResult();
$result->addValue( null, 0, $search );
- $result->addValue( null, 1, $srchres );
+ $result->addValue( null, 1, $searches );
}
public function getAllowedParams() {
- return array (
+ return array(
'search' => null,
'limit' => array(
- ApiBase :: PARAM_DFLT => 10,
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => 100,
- ApiBase :: PARAM_MAX2 => 100
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => 100,
+ ApiBase::PARAM_MAX2 => 100
),
'namespace' => array(
- ApiBase :: PARAM_DFLT => NS_MAIN,
- ApiBase :: PARAM_TYPE => 'namespace',
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_DFLT => NS_MAIN,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_ISMULTI => true
),
'suggest' => false,
);
}
public function getParamDescription() {
- return array (
+ return array(
'search' => 'Search string',
'limit' => 'Maximum amount of results to return',
'namespace' => 'Namespaces to search',
@@ -100,12 +120,12 @@ class ApiOpenSearch extends ApiBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=opensearch&search=Te'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiOpenSearch.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiOpenSearch.php 79720 2011-01-06 14:48:34Z catrope $';
}
}
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index 361f1d8b..1cb12c07 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 24, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 24, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -45,9 +46,10 @@ class ApiPageSet extends ApiQueryBase {
private $mAllPages; // [ns][dbkey] => page_id or negative when missing
private $mTitles, $mGoodTitles, $mMissingTitles, $mInvalidTitles;
- private $mMissingPageIDs, $mRedirectTitles;
+ private $mMissingPageIDs, $mRedirectTitles, $mSpecialTitles;
private $mNormalizedTitles, $mInterwikiTitles;
private $mResolveRedirects, $mPendingRedirectIDs;
+ private $mConvertTitles, $mConvertedTitles;
private $mGoodRevIDs, $mMissingRevIDs;
private $mFakePageId;
@@ -58,25 +60,30 @@ class ApiPageSet extends ApiQueryBase {
* @param $query ApiQuery
* @param $resolveRedirects bool Whether redirects should be resolved
*/
- public function __construct( $query, $resolveRedirects = false ) {
- parent :: __construct( $query, 'query' );
+ public function __construct( $query, $resolveRedirects = false, $convertTitles = false ) {
+ parent::__construct( $query, 'query' );
- $this->mAllPages = array ();
+ $this->mAllPages = array();
$this->mTitles = array();
- $this->mGoodTitles = array ();
- $this->mMissingTitles = array ();
- $this->mInvalidTitles = array ();
- $this->mMissingPageIDs = array ();
- $this->mRedirectTitles = array ();
- $this->mNormalizedTitles = array ();
- $this->mInterwikiTitles = array ();
+ $this->mGoodTitles = array();
+ $this->mMissingTitles = array();
+ $this->mInvalidTitles = array();
+ $this->mMissingPageIDs = array();
+ $this->mRedirectTitles = array();
+ $this->mNormalizedTitles = array();
+ $this->mInterwikiTitles = array();
$this->mGoodRevIDs = array();
$this->mMissingRevIDs = array();
+ $this->mSpecialTitles = array();
- $this->mRequestedPageFields = array ();
+ $this->mRequestedPageFields = array();
$this->mResolveRedirects = $resolveRedirects;
- if ( $resolveRedirects )
+ if ( $resolveRedirects ) {
$this->mPendingRedirectIDs = array();
+ }
+
+ $this->mConvertTitles = $convertTitles;
+ $this->mConvertedTitles = array();
$this->mFakePageId = - 1;
}
@@ -117,14 +124,15 @@ class ApiPageSet extends ApiQueryBase {
public function getPageTableFields() {
// Ensure we get minimum required fields
// DON'T change this order
- $pageFlds = array (
+ $pageFlds = array(
'page_namespace' => null,
'page_title' => null,
'page_id' => null,
);
- if ( $this->mResolveRedirects )
+ if ( $this->mResolveRedirects ) {
$pageFlds['page_is_redirect'] = null;
+ }
// only store non-default fields
$this->mRequestedPageFields = array_diff_key( $this->mRequestedPageFields, $pageFlds );
@@ -220,6 +228,15 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Get a list of title conversions - maps a title to its converted
+ * version.
+ * @return array raw_prefixed_title (string) => prefixed_title (string)
+ */
+ public function getConvertedTitles() {
+ return $this->mConvertedTitles;
+ }
+
+ /**
* Get a list of interwiki titles - maps a title to its interwiki
* prefix.
* @return array raw_prefixed_title (string) => interwiki_prefix (string)
@@ -245,6 +262,14 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Get the list of titles with negative namespace
+ * @return array Title
+ */
+ public function getSpecialTitles() {
+ return $this->mSpecialTitles;
+ }
+
+ /**
* Returns the number of revisions (requested with revids= parameter)\
* @return int
*/
@@ -261,34 +286,38 @@ class ApiPageSet extends ApiQueryBase {
// Only one of the titles/pageids/revids is allowed at the same time
$dataSource = null;
- if ( isset ( $params['titles'] ) )
+ if ( isset( $params['titles'] ) ) {
$dataSource = 'titles';
- if ( isset ( $params['pageids'] ) ) {
- if ( isset ( $dataSource ) )
+ }
+ if ( isset( $params['pageids'] ) ) {
+ if ( isset( $dataSource ) ) {
$this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
+ }
$dataSource = 'pageids';
}
- if ( isset ( $params['revids'] ) ) {
- if ( isset ( $dataSource ) )
+ if ( isset( $params['revids'] ) ) {
+ if ( isset( $dataSource ) ) {
$this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
+ }
$dataSource = 'revids';
}
switch ( $dataSource ) {
- case 'titles' :
+ case 'titles':
$this->initFromTitles( $params['titles'] );
break;
- case 'pageids' :
+ case 'pageids':
$this->initFromPageIds( $params['pageids'] );
break;
- case 'revids' :
- if ( $this->mResolveRedirects )
+ case 'revids':
+ if ( $this->mResolveRedirects ) {
$this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' .
'Any redirects the revids= point to have not been resolved.' );
+ }
$this->mResolveRedirects = false;
$this->initFromRevIDs( $params['revids'] );
break;
- default :
+ default:
// Do nothing - some queries do not need any of the data sources.
break;
}
@@ -318,7 +347,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Populate this PageSet from a rowset returned from the database
* @param $db Database object
- * @param $queryResult Query result object
+ * @param $queryResult ResultWrapper Query result object
*/
public function populateFromQueryResult( $db, $queryResult ) {
$this->profileIn();
@@ -341,9 +370,8 @@ class ApiPageSet extends ApiQueryBase {
* @param $row Result row
*/
public function processDbRow( $row ) {
-
// Store Title object in various data structures
- $title = Title :: makeTitle( $row->page_namespace, $row->page_title );
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
$pageId = intval( $row->page_id );
$this->mAllPages[$row->page_namespace][$row->page_title] = $pageId;
@@ -355,8 +383,9 @@ class ApiPageSet extends ApiQueryBase {
$this->mGoodTitles[$pageId] = $title;
}
- foreach ( $this->mRequestedPageFields as $fieldName => & $fieldValues )
+ foreach ( $this->mRequestedPageFields as $fieldName => &$fieldValues ) {
$fieldValues[$pageId] = $row-> $fieldName;
+ }
}
/**
@@ -385,11 +414,11 @@ class ApiPageSet extends ApiQueryBase {
* @param $titles array of Title objects or strings
*/
private function initFromTitles( $titles ) {
-
// Get validated and normalized title objects
$linkBatch = $this->processTitlesArray( $titles );
- if ( $linkBatch->isEmpty() )
+ if ( $linkBatch->isEmpty() ) {
return;
+ }
$db = $this->getDB();
$set = $linkBatch->constructSet( 'page', $db );
@@ -401,7 +430,7 @@ class ApiPageSet extends ApiQueryBase {
$this->profileDBOut();
// Hack: get the ns:titles stored in array(ns => array(titles)) format
- $this->initFromQueryResult( $db, $res, $linkBatch->data, true ); // process Titles
+ $this->initFromQueryResult( $db, $res, $linkBatch->data, true ); // process Titles
// Resolve any found redirects
$this->resolvePendingRedirects();
@@ -412,11 +441,12 @@ class ApiPageSet extends ApiQueryBase {
* @param $pageids array of page IDs
*/
private function initFromPageIds( $pageids ) {
- if ( !count( $pageids ) )
+ if ( !count( $pageids ) ) {
return;
+ }
$pageids = array_map( 'intval', $pageids ); // paranoia
- $set = array (
+ $set = array(
'page_id' => $pageids
);
$db = $this->getDB();
@@ -438,7 +468,7 @@ class ApiPageSet extends ApiQueryBase {
* Iterate through the result of the query on 'page' table,
* and for each row create and store title object and save any extra fields requested.
* @param $db Database
- * @param $res DB Query result
+ * @param $res ResultWrapper DB Query result
* @param $remaining array of either pageID or ns/title elements (optional).
* If given, any missing items will go to $mMissingPageIDs and $mMissingTitles
* @param $processTitles bool Must be provided together with $remaining.
@@ -446,47 +476,46 @@ class ApiPageSet extends ApiQueryBase {
* If false, treat it as an array of [pageIDs]
*/
private function initFromQueryResult( $db, $res, &$remaining = null, $processTitles = null ) {
- if ( !is_null( $remaining ) && is_null( $processTitles ) )
- ApiBase :: dieDebug( __METHOD__, 'Missing $processTitles parameter when $remaining is provided' );
-
- while ( $row = $db->fetchObject( $res ) ) {
+ if ( !is_null( $remaining ) && is_null( $processTitles ) ) {
+ ApiBase::dieDebug( __METHOD__, 'Missing $processTitles parameter when $remaining is provided' );
+ }
+ foreach ( $res as $row ) {
$pageId = intval( $row->page_id );
// Remove found page from the list of remaining items
if ( isset( $remaining ) ) {
- if ( $processTitles )
- unset ( $remaining[$row->page_namespace][$row->page_title] );
- else
- unset ( $remaining[$pageId] );
+ if ( $processTitles ) {
+ unset( $remaining[$row->page_namespace][$row->page_title] );
+ } else {
+ unset( $remaining[$pageId] );
+ }
}
// Store any extra fields requested by modules
$this->processDbRow( $row );
}
- $db->freeResult( $res );
if ( isset( $remaining ) ) {
// Any items left in the $remaining list are added as missing
if ( $processTitles ) {
// The remaining titles in $remaining are non-existent pages
foreach ( $remaining as $ns => $dbkeys ) {
- foreach ( $dbkeys as $dbkey => $unused ) {
- $title = Title :: makeTitle( $ns, $dbkey );
+ foreach ( array_keys( $dbkeys ) as $dbkey ) {
+ $title = Title::makeTitle( $ns, $dbkey );
$this->mAllPages[$ns][$dbkey] = $this->mFakePageId;
$this->mMissingTitles[$this->mFakePageId] = $title;
$this->mFakePageId--;
$this->mTitles[] = $title;
}
}
- }
- else
- {
+ } else {
// The remaining pageids do not exist
- if ( !$this->mMissingPageIDs )
+ if ( !$this->mMissingPageIDs ) {
$this->mMissingPageIDs = array_keys( $remaining );
- else
+ } else {
$this->mMissingPageIDs = array_merge( $this->mMissingPageIDs, array_keys( $remaining ) );
+ }
}
}
}
@@ -497,9 +526,9 @@ class ApiPageSet extends ApiQueryBase {
* @param $revids array of revision IDs
*/
private function initFromRevIDs( $revids ) {
-
- if ( !count( $revids ) )
+ if ( !count( $revids ) ) {
return;
+ }
$revids = array_map( 'intval', $revids ); // paranoia
$db = $this->getDB();
@@ -513,14 +542,13 @@ class ApiPageSet extends ApiQueryBase {
// Get pageIDs data from the `page` table
$this->profileDBIn();
$res = $db->select( $tables, $fields, $where, __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$revid = intval( $row->rev_id );
$pageid = intval( $row->rev_page );
$this->mGoodRevIDs[$revid] = $pageid;
$pageids[$pageid] = '';
unset( $remaining[$revid] );
}
- $db->freeResult( $res );
$this->profileDBOut();
$this->mMissingRevIDs = array_keys( $remaining );
@@ -535,7 +563,6 @@ class ApiPageSet extends ApiQueryBase {
* have been resolved.
*/
private function resolvePendingRedirects() {
-
if ( $this->mResolveRedirects ) {
$db = $this->getDB();
$pageFlds = $this->getPageTableFields();
@@ -543,17 +570,18 @@ class ApiPageSet extends ApiQueryBase {
// Repeat until all redirects have been resolved
// The infinite loop is prevented by keeping all known pages in $this->mAllPages
while ( $this->mPendingRedirectIDs ) {
-
// Resolve redirects by querying the pagelinks table, and repeat the process
// Create a new linkBatch object for the next pass
$linkBatch = $this->getRedirectTargets();
- if ( $linkBatch->isEmpty() )
+ if ( $linkBatch->isEmpty() ) {
break;
+ }
$set = $linkBatch->constructSet( 'page', $db );
- if ( $set === false )
+ if ( $set === false ) {
break;
+ }
// Get pageIDs data from the `page` table
$this->profileDBIn();
@@ -578,7 +606,9 @@ class ApiPageSet extends ApiQueryBase {
$db = $this->getDB();
$this->profileDBIn();
- $res = $db->select( 'redirect', array(
+ $res = $db->select(
+ 'redirect',
+ array(
'rd_from',
'rd_namespace',
'rd_title'
@@ -587,28 +617,27 @@ class ApiPageSet extends ApiQueryBase {
);
$this->profileDBOut();
- while ( $row = $db->fetchObject( $res ) )
- {
+ foreach ( $res as $row ) {
$rdfrom = intval( $row->rd_from );
$from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
$to = Title::makeTitle( $row->rd_namespace, $row->rd_title )->getPrefixedText();
unset( $this->mPendingRedirectIDs[$rdfrom] );
- if ( !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) )
+ if ( !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
$lb->add( $row->rd_namespace, $row->rd_title );
+ }
$this->mRedirectTitles[$from] = $to;
}
- $db->freeResult( $res );
- if ( $this->mPendingRedirectIDs )
- {
+
+ if ( $this->mPendingRedirectIDs ) {
// We found pages that aren't in the redirect table
// Add them
- foreach ( $this->mPendingRedirectIDs as $id => $title )
- {
+ foreach ( $this->mPendingRedirectIDs as $id => $title ) {
$article = new Article( $title );
$rt = $article->insertRedirect();
- if ( !$rt )
+ if ( !$rt ) {
// What the hell. Let's just ignore this
continue;
+ }
$lb->addObj( $rt );
$this->mRedirectTitles[$title->getPrefixedText()] = $rt->getPrefixedText();
unset( $this->mPendingRedirectIDs[$id] );
@@ -627,31 +656,45 @@ class ApiPageSet extends ApiQueryBase {
* @return LinkBatch
*/
private function processTitlesArray( $titles ) {
-
$linkBatch = new LinkBatch();
foreach ( $titles as $title ) {
-
- $titleObj = is_string( $title ) ? Title :: newFromText( $title ) : $title;
- if ( !$titleObj )
- {
+ $titleObj = is_string( $title ) ? Title::newFromText( $title ) : $title;
+ if ( !$titleObj ) {
// Handle invalid titles gracefully
$this->mAllpages[0][$title] = $this->mFakePageId;
$this->mInvalidTitles[$this->mFakePageId] = $title;
$this->mFakePageId--;
continue; // There's nothing else we can do
}
+ $unconvertedTitle = $titleObj->getPrefixedText();
+ $titleWasConverted = false;
$iw = $titleObj->getInterwiki();
if ( strval( $iw ) !== '' ) {
// This title is an interwiki link.
$this->mInterwikiTitles[$titleObj->getPrefixedText()] = $iw;
} else {
+ // Variants checking
+ global $wgContLang;
+ if ( $this->mConvertTitles &&
+ count( $wgContLang->getVariants() ) > 1 &&
+ !$titleObj->exists() ) {
+ // Language::findVariantLink will modify titleObj into
+ // the canonical variant if possible
+ $wgContLang->findVariantLink( $title, $titleObj );
+ $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
+ }
+
- // Validation
- if ( $titleObj->getNamespace() < 0 )
- $this->setWarning( "No support for special pages has been implemented" );
- else
+ if ( $titleObj->getNamespace() < 0 ) {
+ // Handle Special and Media pages
+ $titleObj = $titleObj->fixSpecialName();
+ $this->mSpecialTitles[$this->mFakePageId] = $titleObj;
+ $this->mFakePageId--;
+ } else {
+ // Regular page
$linkBatch->addObj( $titleObj );
+ }
}
// Make sure we remember the original title that was
@@ -659,7 +702,9 @@ class ApiPageSet extends ApiQueryBase {
// titles with the originally requested when e.g. the
// namespace is localized or the capitalization is
// different
- if ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) {
+ if ( $titleWasConverted ) {
+ $this->mConvertedTitles[$title] = $titleObj->getPrefixedText();
+ } elseif ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) {
$this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
}
}
@@ -668,23 +713,23 @@ class ApiPageSet extends ApiQueryBase {
}
protected function getAllowedParams() {
- return array (
- 'titles' => array (
- ApiBase :: PARAM_ISMULTI => true
+ return array(
+ 'titles' => array(
+ ApiBase::PARAM_ISMULTI => true
),
- 'pageids' => array (
- ApiBase :: PARAM_TYPE => 'integer',
- ApiBase :: PARAM_ISMULTI => true
+ 'pageids' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_ISMULTI => true
),
- 'revids' => array (
- ApiBase :: PARAM_TYPE => 'integer',
- ApiBase :: PARAM_ISMULTI => true
+ 'revids' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_ISMULTI => true
)
);
}
protected function getParamDescription() {
- return array (
+ 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'
@@ -699,6 +744,6 @@ class ApiPageSet extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPageSet.php 62410 2010-02-13 01:21:52Z reedy $';
+ return __CLASS__ . ': $Id: ApiPageSet.php 76196 2010-11-06 16:11:19Z reedy $';
}
}
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index 8fe2cad2..a2c0bd11 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Dec 01, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Dec 01, 2007
+ *
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -34,7 +35,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiParamInfo extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
@@ -43,14 +44,11 @@ class ApiParamInfo extends ApiBase {
$result = $this->getResult();
$queryObj = new ApiQuery( $this->getMain(), 'query' );
$r = array();
- if ( is_array( $params['modules'] ) )
- {
+ if ( is_array( $params['modules'] ) ) {
$modArr = $this->getMain()->getModules();
$r['modules'] = array();
- foreach ( $params['modules'] as $m )
- {
- if ( !isset( $modArr[$m] ) )
- {
+ foreach ( $params['modules'] as $m ) {
+ if ( !isset( $modArr[$m] ) ) {
$r['modules'][] = array( 'name' => $m, 'missing' => '' );
continue;
}
@@ -61,14 +59,11 @@ class ApiParamInfo extends ApiBase {
}
$result->setIndexedTagName( $r['modules'], 'module' );
}
- if ( is_array( $params['querymodules'] ) )
- {
+ if ( is_array( $params['querymodules'] ) ) {
$qmodArr = $queryObj->getModules();
$r['querymodules'] = array();
- foreach ( $params['querymodules'] as $qm )
- {
- if ( !isset( $qmodArr[$qm] ) )
- {
+ foreach ( $params['querymodules'] as $qm ) {
+ if ( !isset( $qmodArr[$qm] ) ) {
$r['querymodules'][] = array( 'name' => $qm, 'missing' => '' );
continue;
}
@@ -79,60 +74,64 @@ class ApiParamInfo extends ApiBase {
}
$result->setIndexedTagName( $r['querymodules'], 'module' );
}
- if ( $params['mainmodule'] )
+ if ( $params['mainmodule'] ) {
$r['mainmodule'] = $this->getClassInfo( $this->getMain() );
- if ( $params['pagesetmodule'] )
- {
+ }
+ if ( $params['pagesetmodule'] ) {
$pageSet = new ApiPageSet( $queryObj );
$r['pagesetmodule'] = $this->getClassInfo( $pageSet );
}
$result->addValue( null, $this->getModuleName(), $r );
}
- function getClassInfo( $obj )
- {
+ function getClassInfo( $obj ) {
$result = $this->getResult();
$retval['classname'] = get_class( $obj );
$retval['description'] = implode( "\n", (array)$obj->getDescription() );
+ $retval['examples'] = implode( "\n", (array)$obj->getExamples() );
$retval['version'] = implode( "\n", (array)$obj->getVersion() );
$retval['prefix'] = $obj->getModulePrefix();
- if ( $obj->isReadMode() )
+ if ( $obj->isReadMode() ) {
$retval['readrights'] = '';
- if ( $obj->isWriteMode() )
+ }
+ if ( $obj->isWriteMode() ) {
$retval['writerights'] = '';
- if ( $obj->mustBePosted() )
+ }
+ if ( $obj->mustBePosted() ) {
$retval['mustbeposted'] = '';
- if ( $obj instanceof ApiQueryGeneratorBase )
+ }
+ if ( $obj instanceof ApiQueryGeneratorBase ) {
$retval['generator'] = '';
+ }
$allowedParams = $obj->getFinalParams();
- if ( !is_array( $allowedParams ) )
+ if ( !is_array( $allowedParams ) ) {
return $retval;
-
+ }
+
$retval['parameters'] = array();
$paramDesc = $obj->getFinalParamDescription();
- foreach ( $allowedParams as $n => $p )
- {
+ foreach ( $allowedParams as $n => $p ) {
$a = array( 'name' => $n );
- if ( isset( $paramDesc[$n] ) )
+ if ( isset( $paramDesc[$n] ) ) {
$a['description'] = implode( "\n", (array)$paramDesc[$n] );
- if ( isset( $p[ApiBase::PARAM_DEPRECATED] ) && $p[ApiBase::PARAM_DEPRECATED] )
+ }
+ if ( isset( $p[ApiBase::PARAM_DEPRECATED] ) && $p[ApiBase::PARAM_DEPRECATED] ) {
$a['deprecated'] = '';
- if ( !is_array( $p ) )
- {
- if ( is_bool( $p ) )
- {
+ }
+ if ( isset( $p[ApiBase::PARAM_REQUIRED] ) && $p[ApiBase::PARAM_REQUIRED] ) {
+ $a['required'] = '';
+ }
+
+ if ( !is_array( $p ) ) {
+ if ( is_bool( $p ) ) {
$a['type'] = 'bool';
$a['default'] = ( $p ? 'true' : 'false' );
- }
- else if ( is_string( $p ) || is_null( $p ) )
- {
+ } elseif ( is_string( $p ) || is_null( $p ) ) {
$a['type'] = 'string';
$a['default'] = strval( $p );
- }
- else if ( is_int( $p ) )
- {
+ } elseif ( is_int( $p ) ) {
$a['type'] = 'integer';
$a['default'] = intval( $p );
}
@@ -140,42 +139,45 @@ class ApiParamInfo extends ApiBase {
continue;
}
- if ( isset( $p[ApiBase::PARAM_DFLT] ) )
+ if ( isset( $p[ApiBase::PARAM_DFLT] ) ) {
$a['default'] = $p[ApiBase::PARAM_DFLT];
- if ( isset( $p[ApiBase::PARAM_ISMULTI] ) )
- if ( $p[ApiBase::PARAM_ISMULTI] )
- {
- $a['multi'] = '';
- $a['limit'] = $this->getMain()->canApiHighLimits() ?
- ApiBase::LIMIT_SML2 :
- ApiBase::LIMIT_SML1;
- }
+ }
+ if ( isset( $p[ApiBase::PARAM_ISMULTI] ) && $p[ApiBase::PARAM_ISMULTI] ) {
+ $a['multi'] = '';
+ $a['limit'] = $this->getMain()->canApiHighLimits() ?
+ ApiBase::LIMIT_SML2 :
+ ApiBase::LIMIT_SML1;
+ }
- if ( isset( $p[ApiBase::PARAM_ALLOW_DUPLICATES] ) )
- if ( $p[ApiBase::PARAM_ALLOW_DUPLICATES] )
- $a['allowsduplicates'] = '';
+ if ( isset( $p[ApiBase::PARAM_ALLOW_DUPLICATES] ) && $p[ApiBase::PARAM_ALLOW_DUPLICATES] ) {
+ $a['allowsduplicates'] = '';
+ }
- if ( isset( $p[ApiBase::PARAM_TYPE] ) )
- {
+ if ( isset( $p[ApiBase::PARAM_TYPE] ) ) {
$a['type'] = $p[ApiBase::PARAM_TYPE];
- if ( is_array( $a['type'] ) )
+ if ( is_array( $a['type'] ) ) {
+ $a['type'] = array_values( $a['type'] ); // to prevent sparse arrays from being serialized to JSON as objects
$result->setIndexedTagName( $a['type'], 't' );
+ }
}
- if ( isset( $p[ApiBase::PARAM_MAX] ) )
+ if ( isset( $p[ApiBase::PARAM_MAX] ) ) {
$a['max'] = $p[ApiBase::PARAM_MAX];
- if ( isset( $p[ApiBase::PARAM_MAX2] ) )
+ }
+ if ( isset( $p[ApiBase::PARAM_MAX2] ) ) {
$a['highmax'] = $p[ApiBase::PARAM_MAX2];
- if ( isset( $p[ApiBase::PARAM_MIN] ) )
+ }
+ if ( isset( $p[ApiBase::PARAM_MIN] ) ) {
$a['min'] = $p[ApiBase::PARAM_MIN];
+ }
$retval['parameters'][] = $a;
}
$result->setIndexedTagName( $retval['parameters'], 'param' );
-
+
// Errors
$retval['errors'] = $this->parseErrors( $obj->getPossibleErrors() );
-
+
$result->setIndexedTagName( $retval['errors'], 'error' );
-
+
return $retval;
}
@@ -184,12 +186,12 @@ class ApiParamInfo extends ApiBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'modules' => array(
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true
),
'querymodules' => array(
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true
),
'mainmodule' => false,
'pagesetmodule' => false,
@@ -197,7 +199,7 @@ class ApiParamInfo extends ApiBase {
}
public function getParamDescription() {
- return array (
+ 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',
@@ -206,16 +208,16 @@ class ApiParamInfo extends ApiBase {
}
public function getDescription() {
- return 'Obtain information about certain API parameters';
+ return 'Obtain information about certain API parameters and errors';
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=paraminfo&modules=parse&querymodules=allpages|siteinfo'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiParamInfo.php 62336 2010-02-11 22:22:20Z reedy $';
+ return __CLASS__ . ': $Id: ApiParamInfo.php 87170 2011-04-30 16:57:22Z catrope $';
}
}
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index db389bdb..2d12c233 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Dec 01, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Dec 01, 2007
+ *
+ * Copyright © 2007 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -33,8 +34,10 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiParse extends ApiBase {
+ private $section, $text, $pstText = null;
+
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
@@ -46,141 +49,238 @@ class ApiParse extends ApiBase {
$text = $params['text'];
$title = $params['title'];
$page = $params['page'];
+ $pageid = $params['pageid'];
$oldid = $params['oldid'];
- if ( !is_null( $page ) && ( !is_null( $text ) || $title != "API" ) )
- $this->dieUsage( "The page parameter cannot be used together with the text and title parameters", 'params' );
+
+ if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
+ $this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
+ }
$prop = array_flip( $params['prop'] );
- $revid = false;
+
+ if ( isset( $params['section'] ) ) {
+ $this->section = $params['section'];
+ } else {
+ $this->section = false;
+ }
// The parser needs $wgTitle to be set, apparently the
// $title parameter in Parser::parse isn't enough *sigh*
- global $wgParser, $wgUser, $wgTitle, $wgEnableParserCache;
+ global $wgParser, $wgUser, $wgTitle, $wgLang;
+
+ // Currently unnecessary, code to act as a safeguard against any change in current behaviour of uselang breaks
+ $oldLang = null;
+ if ( isset( $params['uselang'] ) && $params['uselang'] != $wgLang->getCode() ) {
+ $oldLang = $wgLang; // Backup wgLang
+ $wgLang = Language::factory( $params['uselang'] );
+ }
+
$popts = new ParserOptions();
$popts->setTidy( true );
- $popts->enableLimitReport();
+ $popts->enableLimitReport( !$params['disablepp'] );
+
$redirValues = null;
- if ( !is_null( $oldid ) || !is_null( $page ) )
- {
- if ( !is_null( $oldid ) )
- {
+
+ if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
+
+ if ( !is_null( $oldid ) ) {
// Don't use the parser cache
$rev = Revision::newFromID( $oldid );
- if ( !$rev )
+ if ( !$rev ) {
$this->dieUsage( "There is no revision ID $oldid", 'missingrev' );
- if ( !$rev->userCan( Revision::DELETED_TEXT ) )
+ }
+ if ( !$rev->userCan( Revision::DELETED_TEXT ) ) {
$this->dieUsage( "You don't have permission to view deleted revisions", 'permissiondenied' );
+ }
- $text = $rev->getText( Revision::FOR_THIS_USER );
$titleObj = $rev->getTitle();
+
$wgTitle = $titleObj;
- $p_result = $wgParser->parse( $text, $titleObj, $popts );
- }
- else
- {
- if ( $params['redirects'] )
- {
- $req = new FauxRequest( array(
- 'action' => 'query',
- 'redirects' => '',
- 'titles' => $page
- ) );
- $main = new ApiMain( $req );
- $main->execute();
- $data = $main->getResultData();
- $redirValues = @$data['query']['redirects'];
- $to = $page;
- foreach ( (array)$redirValues as $r )
- $to = $r['to'];
+
+ //If for some reason the "oldid" is actually the current revision, it may be cached
+ if ( $titleObj->getLatestRevID() === $oldid ) {
+ $articleObj = new Article( $titleObj, 0 );
+
+ $p_result = $this->getParsedSectionOrText( $articleObj, $titleObj, $popts, $pageid,
+ isset( $prop['wikitext'] ) ) ;
+
+ } else { // This is an old revision, so get the text differently
+ $this->text = $rev->getText( Revision::FOR_THIS_USER );
+
+ $wgTitle = $titleObj;
+
+ if ( $this->section !== false ) {
+ $this->text = $this->getSectionText( $this->text, 'r' . $rev->getId() );
+ }
+
+ $p_result = $wgParser->parse( $this->text, $titleObj, $popts );
}
- else
- $to = $page;
- $titleObj = Title::newFromText( $to );
- if ( !$titleObj )
- $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
-
- $articleObj = new Article( $titleObj );
- if ( isset( $prop['revid'] ) )
+
+ } else { // Not $oldid
+
+ if ( !is_null ( $pageid ) ) {
+ $titleObj = Title::newFromID( $pageid );
+
+ if ( !$titleObj ) {
+ $this->dieUsageMsg( array( 'nosuchpageid', $pageid ) );
+ }
+ } else { // $page
+
+ if ( $params['redirects'] ) {
+ $req = new FauxRequest( array(
+ 'action' => 'query',
+ 'redirects' => '',
+ 'titles' => $page
+ ) );
+ $main = new ApiMain( $req );
+ $main->execute();
+ $data = $main->getResultData();
+ $redirValues = @$data['query']['redirects'];
+ $to = $page;
+ foreach ( (array)$redirValues as $r ) {
+ $to = $r['to'];
+ }
+ } else {
+ $to = $page;
+ }
+ $titleObj = Title::newFromText( $to );
+ if ( !$titleObj || !$titleObj->exists() ) {
+ $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
+ }
+ }
+ $wgTitle = $titleObj;
+
+ $articleObj = new Article( $titleObj, 0 );
+ if ( isset( $prop['revid'] ) ) {
$oldid = $articleObj->getRevIdFetched();
- // Try the parser cache first
- $p_result = false;
- $pcache = ParserCache::singleton();
- if ( $wgEnableParserCache )
- $p_result = $pcache->get( $articleObj, $wgUser );
- if ( !$p_result )
- {
- $p_result = $wgParser->parse( $articleObj->getContent(), $titleObj, $popts );
-
- if ( $wgEnableParserCache )
- $pcache->save( $p_result, $articleObj, $popts );
}
+
+ $p_result = $this->getParsedSectionOrText( $articleObj, $titleObj, $popts, $pageid,
+ isset( $prop['wikitext'] ) ) ;
}
- }
- else
- {
+
+ } else { // Not $oldid, $pageid, $page. Hence based on $text
+
+ $this->text = $text;
$titleObj = Title::newFromText( $title );
- if ( !$titleObj )
- $titleObj = Title::newFromText( "API" );
+ if ( !$titleObj ) {
+ $titleObj = Title::newFromText( 'API' );
+ }
$wgTitle = $titleObj;
- if ( $params['pst'] || $params['onlypst'] )
- $text = $wgParser->preSaveTransform( $text, $titleObj, $wgUser, $popts );
- if ( $params['onlypst'] )
- {
+
+ if ( $this->section !== false ) {
+ $this->text = $this->getSectionText( $this->text, $titleObj->getText() );
+ }
+
+ if ( $params['pst'] || $params['onlypst'] ) {
+ $this->pstText = $wgParser->preSaveTransform( $this->text, $titleObj, $wgUser, $popts );
+ }
+ if ( $params['onlypst'] ) {
// Build a result and bail out
$result_array['text'] = array();
- $this->getResult()->setContent( $result_array['text'], $text );
+ $this->getResult()->setContent( $result_array['text'], $this->pstText );
+ if ( isset( $prop['wikitext'] ) ) {
+ $result_array['wikitext'] = array();
+ $this->getResult()->setContent( $result_array['wikitext'], $this->text );
+ }
$this->getResult()->addValue( null, $this->getModuleName(), $result_array );
return;
}
- $p_result = $wgParser->parse( $text, $titleObj, $popts );
+ $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
}
// Return result
$result = $this->getResult();
$result_array = array();
- if ( $params['redirects'] && !is_null( $redirValues ) )
+ if ( $params['redirects'] && !is_null( $redirValues ) ) {
$result_array['redirects'] = $redirValues;
-
+ }
+
if ( isset( $prop['text'] ) ) {
$result_array['text'] = array();
$result->setContent( $result_array['text'], $p_result->getText() );
}
-
+
if ( !is_null( $params['summary'] ) ) {
$result_array['parsedsummary'] = array();
$result->setContent( $result_array['parsedsummary'], $wgUser->getSkin()->formatComment( $params['summary'], $titleObj ) );
}
-
- if ( isset( $prop['langlinks'] ) )
+
+ if ( isset( $prop['langlinks'] ) ) {
$result_array['langlinks'] = $this->formatLangLinks( $p_result->getLanguageLinks() );
- if ( isset( $prop['categories'] ) )
+ }
+ if ( isset( $prop['languageshtml'] ) ) {
+ $languagesHtml = $this->languagesHtml( $p_result->getLanguageLinks() );
+ $result_array['languageshtml'] = array();
+ $result->setContent( $result_array['languageshtml'], $languagesHtml );
+ }
+ if ( isset( $prop['categories'] ) ) {
$result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
- if ( isset( $prop['links'] ) )
+ }
+ if ( isset( $prop['categorieshtml'] ) ) {
+ $categoriesHtml = $this->categoriesHtml( $p_result->getCategories() );
+ $result_array['categorieshtml'] = array();
+ $result->setContent( $result_array['categorieshtml'], $categoriesHtml );
+ }
+ if ( isset( $prop['links'] ) ) {
$result_array['links'] = $this->formatLinks( $p_result->getLinks() );
- if ( isset( $prop['templates'] ) )
+ }
+ if ( isset( $prop['templates'] ) ) {
$result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
- if ( isset( $prop['images'] ) )
+ }
+ if ( isset( $prop['images'] ) ) {
$result_array['images'] = array_keys( $p_result->getImages() );
- if ( isset( $prop['externallinks'] ) )
+ }
+ if ( isset( $prop['externallinks'] ) ) {
$result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
- if ( isset( $prop['sections'] ) )
+ }
+ if ( isset( $prop['sections'] ) ) {
$result_array['sections'] = $p_result->getSections();
- if ( isset( $prop['displaytitle'] ) )
+ }
+
+ if ( isset( $prop['displaytitle'] ) ) {
$result_array['displaytitle'] = $p_result->getDisplayTitle() ?
$p_result->getDisplayTitle() :
$titleObj->getPrefixedText();
-
- if ( isset( $prop['headitems'] ) )
- $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
-
- if ( isset( $prop['headhtml'] ) ) {
+ }
+
+ if ( isset( $prop['headitems'] ) || isset( $prop['headhtml'] ) ) {
$out = new OutputPage;
$out->addParserOutputNoText( $p_result );
+ $userSkin = $wgUser->getSkin();
+ }
+
+ if ( isset( $prop['headitems'] ) ) {
+ $headItems = $this->formatHeadItems( $p_result->getHeadItems() );
+
+ $userSkin->setupUserCss( $out );
+ $css = $this->formatCss( $out->buildCssLinksArray() );
+
+ $scripts = array( $out->getHeadScripts( $userSkin ) );
+
+ $result_array['headitems'] = array_merge( $headItems, $css, $scripts );
+ }
+
+ if ( isset( $prop['headhtml'] ) ) {
$result_array['headhtml'] = array();
- $result->setContent( $result_array['headhtml'], $out->headElement( $wgUser->getSkin() ) );
+ $result->setContent( $result_array['headhtml'], $out->headElement( $userSkin ) );
+ }
+
+ if ( isset( $prop['iwlinks'] ) ) {
+ $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
}
- if ( !is_null( $oldid ) )
+ if ( isset( $prop['wikitext'] ) ) {
+ $result_array['wikitext'] = array();
+ $result->setContent( $result_array['wikitext'], $this->text );
+ if ( !is_null( $this->pstText ) ) {
+ $result_array['psttext'] = array();
+ $result->setContent( $result_array['psttext'], $this->pstText );
+ }
+ }
+
+ if ( !is_null( $oldid ) ) {
$result_array['revid'] = intval( $oldid );
+ }
$result_mapping = array(
'redirects' => 'r',
@@ -190,11 +290,54 @@ class ApiParse extends ApiBase {
'templates' => 'tl',
'images' => 'img',
'externallinks' => 'el',
+ 'iwlinks' => 'iw',
'sections' => 's',
- 'headitems' => 'hi'
+ 'headitems' => 'hi',
);
$this->setIndexedTagNames( $result_array, $result_mapping );
$result->addValue( null, $this->getModuleName(), $result_array );
+
+ if ( !is_null( $oldLang ) ) {
+ $wgLang = $oldLang; // Reset $wgLang to $oldLang
+ }
+ }
+
+ /**
+ * @param $articleObj Article
+ * @param $titleObj Title
+ * @param $popts ParserOptions
+ * @param $pageId Int
+ * @param $getWikitext Bool
+ * @return ParserOutput
+ */
+ private function getParsedSectionOrText( $articleObj, $titleObj, $popts, $pageId = null, $getWikitext = false ) {
+ if ( $this->section !== false ) {
+ global $wgParser;
+
+ $this->text = $this->getSectionText( $articleObj->getRawText(), !is_null ( $pageId )
+ ? 'page id ' . $pageId : $titleObj->getText() );
+
+ return $wgParser->parse( $this->text, $titleObj, $popts );
+ } else {
+ // Try the parser cache first
+ $pout = $articleObj->getParserOutput();
+ if ( $getWikitext ) {
+ $rev = Revision::newFromTitle( $titleObj );
+ if ( $rev ) {
+ $this->text = $rev->getText();
+ }
+ }
+ return $pout;
+ }
+ }
+
+ private function getSectionText( $text, $what ) {
+ global $wgParser;
+ $text = $wgParser->getSection( $text, $this->section, false );
+ if ( $text === false ) {
+ $this->dieUsage( "There is no section {$this->section} in " . $what, 'nosuchsection' );
+ }
+ return $text;
}
private function formatLangLinks( $links ) {
@@ -202,7 +345,12 @@ class ApiParse extends ApiBase {
foreach ( $links as $link ) {
$entry = array();
$bits = explode( ':', $link, 2 );
+ $title = Title::newFromText( $link );
+
$entry['lang'] = $bits[0];
+ if ( $title ) {
+ $entry['url'] = $title->getFullURL();
+ }
$this->getResult()->setContent( $entry, $bits[1] );
$result[] = $entry;
}
@@ -220,6 +368,20 @@ class ApiParse extends ApiBase {
return $result;
}
+ private function categoriesHtml( $categories ) {
+ global $wgOut, $wgUser;
+ $wgOut->addCategoryLinks( $categories );
+ $sk = $wgUser->getSkin();
+ return $sk->getCategories();
+ }
+
+ private function languagesHtml( $languages ) {
+ global $wgOut, $wgUser;
+ $wgOut->setLanguageLinks( $languages );
+ $sk = $wgUser->getSkin();
+ return $sk->otherLanguages();
+ }
+
private function formatLinks( $links ) {
$result = array();
foreach ( $links as $ns => $nslinks ) {
@@ -227,8 +389,28 @@ class ApiParse extends ApiBase {
$entry = array();
$entry['ns'] = $ns;
$this->getResult()->setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() );
- if ( $id != 0 )
+ if ( $id != 0 ) {
$entry['exists'] = '';
+ }
+ $result[] = $entry;
+ }
+ }
+ return $result;
+ }
+
+ private function formatIWLinks( $iw ) {
+ $result = array();
+ foreach ( $iw as $prefix => $titles ) {
+ foreach ( array_keys( $titles ) as $title ) {
+ $entry = array();
+ $entry['prefix'] = $prefix;
+
+ $title = Title::newFromText( "{$prefix}:{$title}" );
+ if ( $title ) {
+ $entry['url'] = $title->getFullURL();
+ }
+
+ $this->getResult()->setContent( $entry, $title->getFullText() );
$result[] = $entry;
}
}
@@ -246,30 +428,45 @@ class ApiParse extends ApiBase {
return $result;
}
+ private function formatCss( $css ) {
+ $result = array();
+ foreach ( $css as $file => $link ) {
+ $entry = array();
+ $entry['file'] = $file;
+ $this->getResult()->setContent( $entry, $link );
+ $result[] = $entry;
+ }
+ return $result;
+ }
+
private function setIndexedTagNames( &$array, $mapping ) {
foreach ( $mapping as $key => $name ) {
- if ( isset( $array[$key] ) )
+ if ( isset( $array[$key] ) ) {
$this->getResult()->setIndexedTagName( $array[$key], $name );
+ }
}
}
public function getAllowedParams() {
- return array (
+ return array(
'title' => array(
- ApiBase :: PARAM_DFLT => 'API',
+ ApiBase::PARAM_DFLT => 'API',
),
'text' => null,
'summary' => null,
'page' => null,
+ 'pageid' => null,
'redirects' => false,
'oldid' => null,
'prop' => array(
- ApiBase :: PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle',
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle',
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'text',
'langlinks',
+ 'languageshtml',
'categories',
+ 'categorieshtml',
'links',
'templates',
'images',
@@ -278,54 +475,84 @@ class ApiParse extends ApiBase {
'revid',
'displaytitle',
'headitems',
- 'headhtml'
+ 'headhtml',
+ 'iwlinks',
+ 'wikitext',
)
),
'pst' => false,
'onlypst' => false,
+ 'uselang' => null,
+ 'section' => null,
+ 'disablepp' => false,
);
}
public function getParamDescription() {
- return array (
+ $p = $this->getModulePrefix();
+ return array(
'text' => 'Wikitext to parse',
'summary' => 'Summary to parse',
- 'redirects' => 'If the page parameter is set to a redirect, resolve it',
+ 'redirects' => "If the {$p}page parameter is set to a redirect, resolve it",
'title' => 'Title of page the text belongs to',
- 'page' => 'Parse the content of this page. Cannot be used together with text and title',
- 'oldid' => 'Parse the content of this revision. Overrides page',
- 'prop' => array( 'Which pieces of information to get.',
- 'NOTE: Section tree is only generated if there are more than 4 sections, or if the __TOC__ keyword is present'
+ '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 langlinks the parsed wikitext',
+ ' categories - Gives the categories of the parsed wikitext',
+ ' categorieshtml - Gives the html version of the categories',
+ ' languageshtml - Gives the html version of the languagelinks',
+ ' 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',
+ ' iwlinks - Gives interwiki links in the parsed wikitext',
+ ' wikitext - Gives the original wikitext that was parsed',
),
- 'pst' => array( 'Do a pre-save transform on the input before parsing it.',
- 'Ignored if page or oldid is used.'
+ 'pst' => array(
+ 'Do a pre-save transform on the input before parsing it',
+ 'Ignored if page, pageid or oldid is used'
),
- 'onlypst' => array( 'Do a PST on the input, but don\'t parse it.',
- 'Returns PSTed wikitext. Ignored if page or oldid is used.'
+ '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. Ignored if page, pageid or oldid is used'
),
+ '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',
);
}
public function getDescription() {
return 'This module parses wikitext and returns parser output';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'params', 'info' => 'The page parameter cannot be used together with the text and title parameters' ),
array( 'code' => 'missingrev', 'info' => 'There is no revision ID oldid' ),
array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revisions' ),
array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ),
+ array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
+ array( 'nosuchpageid' ),
) );
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=parse&text={{Project:Sandbox}}'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiParse.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiParse.php 89672 2011-06-07 18:45:20Z catrope $';
}
}
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index 79916117..08835743 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 2, 2008
- *
+/**
* API for MediaWiki 1.14+
*
- * Copyright (C) 2008 Soxred93 soxred93@gmail.com,
+ * Created on Sep 2, 2008
+ *
+ * Copyright © 2008 Soxred93 soxred93@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
@@ -19,8 +18,10 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
@@ -34,7 +35,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiPatrol extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
/**
@@ -42,62 +43,63 @@ class ApiPatrol extends ApiBase {
*/
public function execute() {
$params = $this->extractRequestParams();
-
- if ( !isset( $params['rcid'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'rcid' ) );
$rc = RecentChange::newFromID( $params['rcid'] );
- if ( !$rc instanceof RecentChange )
+ if ( !$rc instanceof RecentChange ) {
$this->dieUsageMsg( array( 'nosuchrcid', $params['rcid'] ) );
+ }
$retval = RecentChange::markPatrolled( $params['rcid'] );
-
- if ( $retval )
+
+ if ( $retval ) {
$this->dieUsageMsg( reset( $retval ) );
-
+ }
+
$result = array( 'rcid' => intval( $rc->getAttribute( 'rc_id' ) ) );
ApiQueryBase::addTitleInfo( $result, $rc->getTitle() );
$this->getResult()->addValue( null, $this->getModuleName(), $result );
}
+ public function mustBePosted() {
+ return true;
+ }
+
public function isWriteMode() {
return true;
}
public function getAllowedParams() {
- return array (
+ return array(
'token' => null,
'rcid' => array(
- ApiBase :: PARAM_TYPE => 'integer'
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_REQUIRED => true
),
);
}
public function getParamDescription() {
- return array (
+ return array(
'token' => 'Patrol token obtained from list=recentchanges',
'rcid' => 'Recentchanges ID to patrol',
);
}
public function getDescription() {
- return array (
- 'Patrol a page or revision. '
- );
+ return 'Patrol a page or revision';
}
-
- public function getPossibleErrors() {
+
+ public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'rcid' ),
array( 'nosuchrcid', 'rcid' ),
- ) );
+ ) );
}
-
+
public function needsToken() {
return true;
}
public function getTokenSalt() {
- return '';
+ return 'patrol';
}
protected function getExamples() {
@@ -107,6 +109,6 @@ class ApiPatrol extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPatrol.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiPatrol.php 78437 2010-12-15 14:14:16Z catrope $';
}
-} \ No newline at end of file
+}
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index 0b1ae4c8..3a1d18e0 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -1,10 +1,10 @@
<?php
-
-/*
- * Created on Sep 1, 2007
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Sep 1, 2007
+ *
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -33,68 +35,68 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiProtect extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
- global $wgUser, $wgRestrictionTypes, $wgRestrictionLevels;
+ global $wgUser, $wgRestrictionLevels;
$params = $this->extractRequestParams();
- $titleObj = null;
- if ( !isset( $params['title'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'title' ) );
- if ( empty( $params['protections'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'protections' ) );
-
$titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj )
+ if ( !$titleObj ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
$errors = $titleObj->getUserPermissionsErrors( 'protect', $wgUser );
- if ( $errors )
+ if ( $errors ) {
// We don't care about multiple errors, just report one of them
$this->dieUsageMsg( reset( $errors ) );
+ }
$expiry = (array)$params['expiry'];
- if ( count( $expiry ) != count( $params['protections'] ) )
- {
- if ( count( $expiry ) == 1 )
+ if ( count( $expiry ) != count( $params['protections'] ) ) {
+ if ( count( $expiry ) == 1 ) {
$expiry = array_fill( 0, count( $params['protections'] ), $expiry[0] );
- else
+ } else {
$this->dieUsageMsg( array( 'toofewexpiries', count( $expiry ), count( $params['protections'] ) ) );
+ }
}
-
+
$restrictionTypes = $titleObj->getRestrictionTypes();
-
+
$protections = array();
$expiryarray = array();
$resultProtections = array();
- foreach ( $params['protections'] as $i => $prot )
- {
+ foreach ( $params['protections'] as $i => $prot ) {
$p = explode( '=', $prot );
$protections[$p[0]] = ( $p[1] == 'all' ? '' : $p[1] );
- if ( $titleObj->exists() && $p[0] == 'create' )
+ if ( $titleObj->exists() && $p[0] == 'create' ) {
$this->dieUsageMsg( array( 'create-titleexists' ) );
- if ( !$titleObj->exists() && $p[0] != 'create' )
+ }
+ if ( !$titleObj->exists() && $p[0] != 'create' ) {
$this->dieUsageMsg( array( 'missingtitle-createonly' ) );
+ }
- if ( !in_array( $p[0], $restrictionTypes ) && $p[0] != 'create' )
+ if ( !in_array( $p[0], $restrictionTypes ) && $p[0] != 'create' ) {
$this->dieUsageMsg( array( 'protect-invalidaction', $p[0] ) );
- if ( !in_array( $p[1], $wgRestrictionLevels ) && $p[1] != 'all' )
+ }
+ if ( !in_array( $p[1], $wgRestrictionLevels ) && $p[1] != 'all' ) {
$this->dieUsageMsg( array( 'protect-invalidlevel', $p[1] ) );
+ }
- if ( in_array( $expiry[$i], array( 'infinite', 'indefinite', 'never' ) ) )
+ if ( in_array( $expiry[$i], array( 'infinite', 'indefinite', 'never' ) ) ) {
$expiryarray[$p[0]] = Block::infinity();
- else
- {
+ } else {
$exp = strtotime( $expiry[$i] );
- if ( $exp < 0 || $exp == false )
+ if ( $exp < 0 || !$exp ) {
$this->dieUsageMsg( array( 'invalidexpiry', $expiry[$i] ) );
+ }
$exp = wfTimestamp( TS_MW, $exp );
- if ( $exp < wfTimestampNow() )
+ if ( $exp < wfTimestampNow() ) {
$this->dieUsageMsg( array( 'pastexpiry', $expiry[$i] ) );
+ }
$expiryarray[$p[0]] = $exp;
}
$resultProtections[] = array( $p[0] => $protections[$p[0]],
@@ -105,19 +107,27 @@ class ApiProtect extends ApiBase {
$cascade = $params['cascade'];
$articleObj = new Article( $titleObj );
- if ( $params['watch'] )
- $articleObj->doWatch();
- if ( $titleObj->exists() )
+
+ $watch = $params['watch'] ? 'watch' : $params['watchlist'];
+ $this->setWatch( $watch, $titleObj );
+
+ if ( $titleObj->exists() ) {
$ok = $articleObj->updateRestrictions( $protections, $params['reason'], $cascade, $expiryarray );
- else
+ } else {
$ok = $titleObj->updateTitleProtection( $protections['create'], $params['reason'], $expiryarray['create'] );
- if ( !$ok )
+ }
+ if ( !$ok ) {
// This is very weird. Maybe the article was deleted or the user was blocked/desysopped in the meantime?
// Just throw an unknown error in this case, as it's very likely to be a race condition
$this->dieUsageMsg( array() );
- $res = array( 'title' => $titleObj->getPrefixedText(), 'reason' => $params['reason'] );
- if ( $cascade )
+ }
+ $res = array(
+ 'title' => $titleObj->getPrefixedText(),
+ 'reason' => $params['reason']
+ );
+ if ( $cascade ) {
$res['cascade'] = '';
+ }
$res['protections'] = $resultProtections;
$this->getResult()->setIndexedTagName( $res['protections'], 'protection' );
$this->getResult()->addValue( null, $this->getModuleName(), $res );
@@ -132,26 +142,42 @@ class ApiProtect extends ApiBase {
}
public function getAllowedParams() {
- return array (
- 'title' => null,
+ return array(
+ 'title' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'token' => null,
'protections' => array(
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_REQUIRED => true,
),
'expiry' => array(
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_ALLOW_DUPLICATES => true,
- ApiBase :: PARAM_DFLT => 'infinite',
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ALLOW_DUPLICATES => true,
+ ApiBase::PARAM_DFLT => 'infinite',
),
'reason' => '',
'cascade' => false,
- 'watch' => false,
+ 'watch' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'watchlist' => array(
+ ApiBase::PARAM_DFLT => 'preferences',
+ ApiBase::PARAM_TYPE => array(
+ 'watch',
+ 'unwatch',
+ 'preferences',
+ 'nochange'
+ ),
+ ),
);
}
public function getParamDescription() {
- return array (
- 'title' => 'Title of the page you want to (un)protect.',
+ return array(
+ 'title' => 'Title of the page you want to (un)protect',
'token' => 'A protect token previously retrieved through prop=info',
'protections' => 'Pipe-separated 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.',
@@ -160,19 +186,16 @@ class ApiProtect extends ApiBase {
'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 array(
- 'Change the protection level of a page.'
- );
+ return 'Change the protection level of a page';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'title' ),
- array( 'missingparam', 'protections' ),
array( 'invalidtitle', 'title' ),
array( 'toofewexpiries', 'noofexpiries', 'noofprotections' ),
array( 'create-titleexists' ),
@@ -183,7 +206,7 @@ class ApiProtect extends ApiBase {
array( 'pastexpiry', 'expiry' ),
) );
}
-
+
public function needsToken() {
return true;
}
@@ -193,13 +216,13 @@ class ApiProtect extends ApiBase {
}
protected function getExamples() {
- return array (
- 'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=sysop|move=sysop&cascade&expiry=20070901163000|never',
+ return array(
+ 'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=sysop|move=sysop&cascade=&expiry=20070901163000|never',
'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=all|move=all&reason=Lifting%20restrictions'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiProtect.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiProtect.php 77192 2010-11-23 22:05:27Z btongminh $';
}
}
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index 76d45404..a17abf16 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -1,11 +1,11 @@
<?php
-/*
- * Created on Sep 2, 2008
- *
+/**
* API for MediaWiki 1.14+
*
- * Copyright (C) 2008 Chad Horohoe
+ * Created on Sep 2, 2008
+ *
+ * Copyright © 2008 Chad Horohoe
*
* This 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,12 +19,14 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
@@ -34,7 +36,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiPurge extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
/**
@@ -43,29 +45,27 @@ class ApiPurge extends ApiBase {
public function execute() {
global $wgUser;
$params = $this->extractRequestParams();
- if ( !$wgUser->isAllowed( 'purge' ) )
- $this->dieUsageMsg( array( 'cantpurge' ) );
- if ( !isset( $params['titles'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'titles' ) );
+ if ( !$wgUser->isAllowed( 'purge' ) && !$this->getMain()->isInternalMode() &&
+ !$this->getMain()->getRequest()->wasPosted() ) {
+ $this->dieUsageMsg( array( 'mustbeposted', $this->getModuleName() ) );
+ }
$result = array();
foreach ( $params['titles'] as $t ) {
$r = array();
$title = Title::newFromText( $t );
- if ( !$title instanceof Title )
- {
+ if ( !$title instanceof Title ) {
$r['title'] = $t;
$r['invalid'] = '';
$result[] = $r;
continue;
}
ApiQueryBase::addTitleInfo( $r, $title );
- if ( !$title->exists() )
- {
+ if ( !$title->exists() ) {
$r['missing'] = '';
$result[] = $r;
continue;
}
- $article = Mediawiki::articleFromTitle( $title );
+ $article = MediaWiki::articleFromTitle( $title );
$article->doPurge(); // Directly purge and skip the UI part of purge().
$r['purged'] = '';
$result[] = $r;
@@ -74,40 +74,35 @@ class ApiPurge extends ApiBase {
$this->getResult()->addValue( null, $this->getModuleName(), $result );
}
- public function mustBePosted() {
- global $wgUser;
- return $wgUser->isAnon();
- }
-
public function isWriteMode() {
return true;
}
public function getAllowedParams() {
- return array (
+ return array(
'titles' => array(
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_REQUIRED => true
)
);
}
public function getParamDescription() {
- return array (
+ return array(
'titles' => 'A list of titles',
);
}
public function getDescription() {
- return array (
- 'Purge the cache for the given titles.'
+ return array( 'Purge the cache for the given titles.',
+ 'This module requires a POST request if the user is not logged in.'
);
}
-
- public function getPossibleErrors() {
+
+ public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'cantpurge' ),
- array( 'missingparam', 'titles' ),
- ) );
+ ) );
}
protected function getExamples() {
@@ -117,6 +112,6 @@ class ApiPurge extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPurge.php 69578 2010-07-20 02:46:20Z tstarling $';
+ return __CLASS__ . ': $Id: ApiPurge.php 74944 2010-10-18 09:19:20Z catrope $';
}
}
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index 8d3ef616..f88aa850 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 7, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 7, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
@@ -43,23 +44,26 @@ class ApiQuery extends ApiBase {
private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames;
private $mPageSet;
- private $params, $redirect;
+ private $params, $redirects, $convertTitles;
- private $mQueryPropModules = array (
+ private $mQueryPropModules = array(
'info' => 'ApiQueryInfo',
'revisions' => 'ApiQueryRevisions',
'links' => 'ApiQueryLinks',
+ 'iwlinks' => 'ApiQueryIWLinks',
'langlinks' => 'ApiQueryLangLinks',
'images' => 'ApiQueryImages',
'imageinfo' => 'ApiQueryImageInfo',
+ 'stashimageinfo' => 'ApiQueryStashImageInfo',
'templates' => 'ApiQueryLinks',
'categories' => 'ApiQueryCategories',
'extlinks' => 'ApiQueryExternalLinks',
'categoryinfo' => 'ApiQueryCategoryInfo',
'duplicatefiles' => 'ApiQueryDuplicateFiles',
+ 'pageprops' => 'ApiQueryPageProps',
);
- private $mQueryListModules = array (
+ private $mQueryListModules = array(
'allimages' => 'ApiQueryAllimages',
'allpages' => 'ApiQueryAllpages',
'alllinks' => 'ApiQueryAllLinks',
@@ -70,7 +74,9 @@ class ApiQuery extends ApiBase {
'categorymembers' => 'ApiQueryCategoryMembers',
'deletedrevs' => 'ApiQueryDeletedrevs',
'embeddedin' => 'ApiQueryBacklinks',
+ 'filearchive' => 'ApiQueryFilearchive',
'imageusage' => 'ApiQueryBacklinks',
+ 'iwbacklinks' => 'ApiQueryIWBacklinks',
'logevents' => 'ApiQueryLogEvents',
'recentchanges' => 'ApiQueryRecentChanges',
'search' => 'ApiQuerySearch',
@@ -84,7 +90,7 @@ class ApiQuery extends ApiBase {
'protectedtitles' => 'ApiQueryProtectedTitles',
);
- private $mQueryMetaModules = array (
+ private $mQueryMetaModules = array(
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
'allmessages' => 'ApiQueryAllmessages',
@@ -94,13 +100,13 @@ class ApiQuery extends ApiBase {
private $mNamedDB = array();
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
// Allow custom modules to be added in LocalSettings.php
global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules;
- self :: appendUserModules( $this->mQueryPropModules, $wgAPIPropModules );
- self :: appendUserModules( $this->mQueryListModules, $wgAPIListModules );
- self :: appendUserModules( $this->mQueryMetaModules, $wgAPIMetaModules );
+ self::appendUserModules( $this->mQueryPropModules, $wgAPIPropModules );
+ self::appendUserModules( $this->mQueryListModules, $wgAPIListModules );
+ self::appendUserModules( $this->mQueryMetaModules, $wgAPIMetaModules );
$this->mPropModuleNames = array_keys( $this->mQueryPropModules );
$this->mListModuleNames = array_keys( $this->mQueryListModules );
@@ -129,7 +135,7 @@ class ApiQuery extends ApiBase {
* @return Database
*/
public function getDB() {
- if ( !isset ( $this->mSlaveDB ) ) {
+ if ( !isset( $this->mSlaveDB ) ) {
$this->profileDBIn();
$this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
$this->profileDBOut();
@@ -172,14 +178,37 @@ class ApiQuery extends ApiBase {
return array_merge( $this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules );
}
+ /**
+ * Get whether the specified module is a prop, list or a meta query module
+ * @param $moduleName string Name of the module to find type for
+ * @return mixed string or null
+ */
+ function getModuleType( $moduleName ) {
+ if ( array_key_exists ( $moduleName, $this->mQueryPropModules ) ) {
+ return 'prop';
+ }
+
+ if ( array_key_exists ( $moduleName, $this->mQueryListModules ) ) {
+ return 'list';
+ }
+
+ if ( array_key_exists ( $moduleName, $this->mQueryMetaModules ) ) {
+ return 'meta';
+ }
+
+ return null;
+ }
+
public function getCustomPrinter() {
// If &exportnowrap is set, use the raw formatter
if ( $this->getParameter( 'export' ) &&
$this->getParameter( 'exportnowrap' ) )
+ {
return new ApiFormatRaw( $this->getMain(),
$this->getMain()->createPrinterByName( 'xml' ) );
- else
+ } else {
return null;
+ }
}
/**
@@ -193,26 +222,26 @@ class ApiQuery extends ApiBase {
* #5 Execute all requested modules
*/
public function execute() {
-
$this->params = $this->extractRequestParams();
$this->redirects = $this->params['redirects'];
+ $this->convertTitles = $this->params['converttitles'];
// Create PageSet
- $this->mPageSet = new ApiPageSet( $this, $this->redirects );
+ $this->mPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
// Instantiate requested modules
- $modules = array ();
- $this->InstantiateModules( $modules, 'prop', $this->mQueryPropModules );
- $this->InstantiateModules( $modules, 'list', $this->mQueryListModules );
- $this->InstantiateModules( $modules, 'meta', $this->mQueryMetaModules );
+ $modules = array();
+ $this->instantiateModules( $modules, 'prop', $this->mQueryPropModules );
+ $this->instantiateModules( $modules, 'list', $this->mQueryListModules );
+ $this->instantiateModules( $modules, 'meta', $this->mQueryMetaModules );
$cacheMode = 'public';
// If given, execute generator to substitute user supplied data with generated data.
- if ( isset ( $this->params['generator'] ) ) {
+ if ( isset( $this->params['generator'] ) ) {
$generator = $this->newGenerator( $this->params['generator'] );
$params = $generator->extractRequestParams();
- $cacheMode = $this->mergeCacheMode( $cacheMode,
+ $cacheMode = $this->mergeCacheMode( $cacheMode,
$generator->getCacheMode( $params ) );
$this->executeGeneratorModule( $generator, $modules );
} else {
@@ -227,7 +256,7 @@ class ApiQuery extends ApiBase {
// Execute all requested modules.
foreach ( $modules as $module ) {
$params = $module->extractRequestParams();
- $cacheMode = $this->mergeCacheMode(
+ $cacheMode = $this->mergeCacheMode(
$cacheMode, $module->getCacheMode( $params ) );
$module->profileIn();
$module->execute();
@@ -241,7 +270,7 @@ class ApiQuery extends ApiBase {
/**
* Update a cache mode string, applying the cache mode of a new module to it.
- * The cache mode may increase in the level of privacy, but public modules
+ * The cache mode may increase in the level of privacy, but public modules
* added to private data do not decrease the level of privacy.
*/
protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
@@ -273,15 +302,17 @@ class ApiQuery extends ApiBase {
/**
* Create instances of all modules requested by the client
- * @param $modules array to append instatiated modules to
+ * @param $modules Array to append instantiated modules to
* @param $param string Parameter name to read modules from
- * @param $moduleList array(modulename => classname)
+ * @param $moduleList Array array(modulename => classname)
*/
- private function InstantiateModules( &$modules, $param, $moduleList ) {
+ private function instantiateModules( &$modules, $param, $moduleList ) {
$list = @$this->params[$param];
- if ( !is_null ( $list ) )
- foreach ( $list as $moduleName )
+ if ( !is_null ( $list ) ) {
+ foreach ( $list as $moduleName ) {
$modules[] = new $moduleList[$moduleName] ( $this, $moduleName );
+ }
+ }
}
/**
@@ -290,7 +321,6 @@ class ApiQuery extends ApiBase {
* and missing or invalid title/pageids/revids.
*/
private function outputGeneralPageInfo() {
-
$pageSet = $this->getPageSet();
$result = $this->getResult();
@@ -299,9 +329,9 @@ class ApiQuery extends ApiBase {
// and the maximum result size must be even higher than that.
// Title normalizations
- $normValues = array ();
+ $normValues = array();
foreach ( $pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
- $normValues[] = array (
+ $normValues[] = array(
'from' => $rawTitleStr,
'to' => $titleStr
);
@@ -312,10 +342,24 @@ class ApiQuery extends ApiBase {
$result->addValue( 'query', 'normalized', $normValues );
}
+ // Title conversions
+ $convValues = array();
+ foreach ( $pageSet->getConvertedTitles() as $rawTitleStr => $titleStr ) {
+ $convValues[] = array(
+ 'from' => $rawTitleStr,
+ 'to' => $titleStr
+ );
+ }
+
+ if ( count( $convValues ) ) {
+ $result->setIndexedTagName( $convValues, 'c' );
+ $result->addValue( 'query', 'converted', $convValues );
+ }
+
// Interwiki titles
- $intrwValues = array ();
+ $intrwValues = array();
foreach ( $pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
- $intrwValues[] = array (
+ $intrwValues[] = array(
'title' => $rawTitleStr,
'iw' => $interwikiStr
);
@@ -327,9 +371,9 @@ class ApiQuery extends ApiBase {
}
// Show redirect information
- $redirValues = array ();
+ $redirValues = array();
foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo ) {
- $redirValues[] = array (
+ $redirValues[] = array(
'from' => strval( $titleStrFrom ),
'to' => $titleStrTo
);
@@ -340,14 +384,12 @@ class ApiQuery extends ApiBase {
$result->addValue( 'query', 'redirects', $redirValues );
}
- //
// Missing revision elements
- //
$missingRevIDs = $pageSet->getMissingRevisionIDs();
if ( count( $missingRevIDs ) ) {
- $revids = array ();
+ $revids = array();
foreach ( $missingRevIDs as $revid ) {
- $revids[$revid] = array (
+ $revids[$revid] = array(
'revid' => $revid
);
}
@@ -355,39 +397,51 @@ class ApiQuery extends ApiBase {
$result->addValue( 'query', 'badrevids', $revids );
}
- //
// Page elements
- //
- $pages = array ();
+ $pages = array();
// Report any missing titles
foreach ( $pageSet->getMissingTitles() as $fakeId => $title ) {
$vals = array();
- ApiQueryBase :: addTitleInfo( $vals, $title );
+ ApiQueryBase::addTitleInfo( $vals, $title );
$vals['missing'] = '';
$pages[$fakeId] = $vals;
}
// Report any invalid titles
- foreach ( $pageSet->getInvalidTitles() as $fakeId => $title )
+ foreach ( $pageSet->getInvalidTitles() as $fakeId => $title ) {
$pages[$fakeId] = array( 'title' => $title, 'invalid' => '' );
+ }
// Report any missing page ids
foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
- $pages[$pageid] = array (
+ $pages[$pageid] = array(
'pageid' => $pageid,
'missing' => ''
);
}
+ // Report special pages
+ foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
+ $vals = array();
+ ApiQueryBase::addTitleInfo( $vals, $title );
+ $vals['special'] = '';
+ if ( $title->getNamespace() == NS_SPECIAL &&
+ !SpecialPage::exists( $title->getDbKey() ) ) {
+ $vals['missing'] = '';
+ } elseif ( $title->getNamespace() == NS_MEDIA &&
+ !wfFindFile( $title ) ) {
+ $vals['missing'] = '';
+ }
+ $pages[$fakeId] = $vals;
+ }
// Output general page information for found titles
foreach ( $pageSet->getGoodTitles() as $pageid => $title ) {
$vals = array();
$vals['pageid'] = $pageid;
- ApiQueryBase :: addTitleInfo( $vals, $title );
+ ApiQueryBase::addTitleInfo( $vals, $title );
$pages[$pageid] = $vals;
}
if ( count( $pages ) ) {
-
if ( $this->params['indexpageids'] ) {
$pageIDs = array_keys( $pages );
// json treats all map keys as strings - converting to match
@@ -400,57 +454,76 @@ class ApiQuery extends ApiBase {
$result->addValue( 'query', 'pages', $pages );
}
if ( $this->params['export'] ) {
- $exporter = new WikiExporter( $this->getDB() );
- // WikiExporter writes to stdout, so catch its
- // output with an ob
- ob_start();
- $exporter->openStream();
- foreach ( @$pageSet->getGoodTitles() as $title )
- if ( $title->userCanRead() )
- $exporter->pageByTitle( $title );
- $exporter->closeStream();
- $exportxml = ob_get_contents();
- ob_end_clean();
-
- // Don't check the size of exported stuff
- // It's not continuable, so it would cause more
- // problems than it'd solve
- $result->disableSizeCheck();
- if ( $this->params['exportnowrap'] ) {
- $result->reset();
- // Raw formatter will handle this
- $result->addValue( null, 'text', $exportxml );
- $result->addValue( null, 'mime', 'text/xml' );
- } else {
- $r = array();
- ApiResult::setContent( $r, $exportxml );
- $result->addValue( 'query', 'export', $r );
+ $this->doExport( $pageSet, $result );
+ }
+ }
+
+ /**
+ * @param $pageSet ApiPageSet Pages to be exported
+ * @param $result ApiResult Result to output to
+ */
+ private function doExport( $pageSet, $result ) {
+ $exportTitles = array();
+ $titles = $pageSet->getGoodTitles();
+ if( count( $titles ) ) {
+ foreach ( $titles as $title ) {
+ if ( $title->userCanRead() ) {
+ $exportTitles[] = $title;
+ }
}
- $result->enableSizeCheck();
}
+ // only export when there are titles
+ if ( !count( $exportTitles ) ) {
+ return;
+ }
+
+ $exporter = new WikiExporter( $this->getDB() );
+ // WikiExporter writes to stdout, so catch its
+ // output with an ob
+ ob_start();
+ $exporter->openStream();
+ foreach ( $exportTitles as $title ) {
+ $exporter->pageByTitle( $title );
+ }
+ $exporter->closeStream();
+ $exportxml = ob_get_contents();
+ ob_end_clean();
+
+ // Don't check the size of exported stuff
+ // It's not continuable, so it would cause more
+ // problems than it'd solve
+ $result->disableSizeCheck();
+ if ( $this->params['exportnowrap'] ) {
+ $result->reset();
+ // Raw formatter will handle this
+ $result->addValue( null, 'text', $exportxml );
+ $result->addValue( null, 'mime', 'text/xml' );
+ } else {
+ $r = array();
+ ApiResult::setContent( $r, $exportxml );
+ $result->addValue( 'query', 'export', $r );
+ }
+ $result->enableSizeCheck();
}
/**
* Create a generator object of the given type and return it
+ * @param $generatorName string Module name
+ * @return ApiQueryGeneratorBase
*/
public function newGenerator( $generatorName ) {
-
// Find class that implements requested generator
- if ( isset ( $this->mQueryListModules[$generatorName] ) ) {
+ if ( isset( $this->mQueryListModules[$generatorName] ) ) {
$className = $this->mQueryListModules[$generatorName];
- } elseif ( isset ( $this->mQueryPropModules[$generatorName] ) ) {
+ } elseif ( isset( $this->mQueryPropModules[$generatorName] ) ) {
$className = $this->mQueryPropModules[$generatorName];
} else {
- ApiBase :: dieDebug( __METHOD__, "Unknown generator=$generatorName" );
+ ApiBase::dieDebug( __METHOD__, "Unknown generator=$generatorName" );
}
-
- // Generator results
- $resultPageSet = new ApiPageSet( $this, $this->redirects );
-
- // Create and execute the generator
$generator = new $className ( $this, $generatorName );
- if ( !$generator instanceof ApiQueryGeneratorBase )
- $this->dieUsage( "Module $generatorName cannot be used as a generator", "badgenerator" );
+ if ( !$generator instanceof ApiQueryGeneratorBase ) {
+ $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
+ }
$generator->setGeneratorMode();
return $generator;
}
@@ -458,7 +531,7 @@ class ApiQuery extends ApiBase {
/**
* For generator mode, execute generator, and use its output as new
* ApiPageSet
- * @param $generatorName string Module name
+ * @param $generator ApiQueryGeneratorBase Generator Module
* @param $modules array of module objects
*/
protected function executeGeneratorModule( $generator, $modules ) {
@@ -484,23 +557,24 @@ class ApiQuery extends ApiBase {
}
public function getAllowedParams() {
- return array (
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => $this->mPropModuleNames
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => $this->mPropModuleNames
),
- 'list' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => $this->mListModuleNames
+ 'list' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => $this->mListModuleNames
),
- 'meta' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => $this->mMetaModuleNames
+ 'meta' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => $this->mMetaModuleNames
),
- 'generator' => array (
- ApiBase :: PARAM_TYPE => $this->mAllowedGenerators
+ 'generator' => array(
+ ApiBase::PARAM_TYPE => $this->mAllowedGenerators
),
'redirects' => false,
+ 'converttitles' => false,
'indexpageids' => false,
'export' => false,
'exportnowrap' => false,
@@ -512,49 +586,48 @@ class ApiQuery extends ApiBase {
* @return string
*/
public function makeHelpMsg() {
-
$msg = '';
// Make sure the internal object is empty
// (just in case a sub-module decides to optimize during instantiation)
$this->mPageSet = null;
- $this->mAllowedGenerators = array(); // Will be repopulated
+ $this->mAllowedGenerators = array(); // Will be repopulated
- $astriks = str_repeat( '--- ', 8 );
- $astriks2 = str_repeat( '*** ', 10 );
- $msg .= "\n$astriks Query: Prop $astriks\n\n";
+ $querySeparator = str_repeat( '--- ', 8 );
+ $moduleSeparator = str_repeat( '*** ', 10 );
+ $msg .= "\n$querySeparator Query: Prop $querySeparator\n\n";
$msg .= $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' );
- $msg .= "\n$astriks Query: List $astriks\n\n";
+ $msg .= "\n$querySeparator Query: List $querySeparator\n\n";
$msg .= $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' );
- $msg .= "\n$astriks Query: Meta $astriks\n\n";
+ $msg .= "\n$querySeparator Query: Meta $querySeparator\n\n";
$msg .= $this->makeHelpMsgHelper( $this->mQueryMetaModules, 'meta' );
- $msg .= "\n\n$astriks2 Modules: continuation $astriks2\n\n";
+ $msg .= "\n\n$moduleSeparator Modules: continuation $moduleSeparator\n\n";
// Perform the base call last because the $this->mAllowedGenerators
// will be updated inside makeHelpMsgHelper()
// Use parent to make default message for the query module
- $msg = parent :: makeHelpMsg() . $msg;
+ $msg = parent::makeHelpMsg() . $msg;
return $msg;
}
/**
* For all modules in $moduleList, generate help messages and join them together
- * @param $moduleList array(modulename => classname)
+ * @param $moduleList Array array(modulename => classname)
* @param $paramName string Parameter name
* @return string
*/
private function makeHelpMsgHelper( $moduleList, $paramName ) {
-
- $moduleDescriptions = array ();
+ $moduleDescriptions = array();
foreach ( $moduleList as $moduleName => $moduleClass ) {
$module = new $moduleClass ( $this, $moduleName, null );
$msg = ApiMain::makeHelpMsgHeader( $module, $paramName );
$msg2 = $module->makeHelpMsg();
- if ( $msg2 !== false )
+ if ( $msg2 !== false ) {
$msg .= $msg2;
+ }
if ( $module instanceof ApiQueryGeneratorBase ) {
$this->mAllowedGenerators[] = $moduleName;
$msg .= "Generator:\n This module may be used as a generator\n";
@@ -571,7 +644,7 @@ class ApiQuery extends ApiBase {
*/
public function makeHelpMsgParameters() {
$psModule = new ApiPageSet( $this );
- return $psModule->makeHelpMsgParameters() . parent :: makeHelpMsgParameters();
+ return $psModule->makeHelpMsgParameters() . parent::makeHelpMsgParameters();
}
public function shouldCheckMaxlag() {
@@ -579,27 +652,29 @@ class ApiQuery extends ApiBase {
}
public function getParamDescription() {
- return array (
- 'prop' => 'Which properties to get for the titles/revisions/pageids',
- 'list' => 'Which lists to get',
- 'meta' => 'Which meta data to get about the site',
+ return 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',
'generator' => array( 'Use the output of a list as the input for other prop/list/meta items',
- 'NOTE: generator parameter names must be prefixed with a \'g\', see examples.' ),
+ 'NOTE: generator parameter names must be prefixed with a \'g\', see examples' ),
'redirects' => 'Automatically resolve redirects',
- 'indexpageids' => 'Include an additional pageids section listing all returned page IDs.',
+ '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 kk, ku, gan, tg, sr, zh' ),
+ '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',
);
}
public function getDescription() {
- return array (
+ 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.'
+ 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites'
);
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ),
@@ -607,7 +682,7 @@ class ApiQuery extends ApiBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment',
'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions',
);
@@ -615,8 +690,8 @@ class ApiQuery extends ApiBase {
public function getVersion() {
$psModule = new ApiPageSet( $this );
- $vers = array ();
- $vers[] = __CLASS__ . ': $Id: ApiQuery.php 69932 2010-07-26 08:03:21Z tstarling $';
+ $vers = array();
+ $vers[] = __CLASS__ . ': $Id: ApiQuery.php 80897 2011-01-24 18:57:42Z catrope $';
$vers[] = $psModule->getVersion();
return $vers;
}
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index 8f24fc7c..c1473252 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on December 12, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on December 12, 2007
+ *
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -37,7 +38,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryAllCategories extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'ac' );
+ parent::__construct( $query, $moduleName, 'ac' );
}
public function execute() {
@@ -53,7 +54,6 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
private function run( $resultPageSet = null ) {
-
$db = $this->getDB();
$params = $this->extractRequestParams();
@@ -62,17 +62,19 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $this->addWhereRange( 'cat_title', $dir, $from, null );
- if ( isset ( $params['prefix'] ) )
+ $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $this->addWhereRange( 'cat_title', $dir, $from, $to );
+
+ if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'cat_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ }
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$this->addOption( 'ORDER BY', 'cat_title' . ( $params['dir'] == 'descending' ? ' DESC' : '' ) );
$prop = array_flip( $params['prop'] );
$this->addFieldsIf( array( 'cat_pages', 'cat_subcats', 'cat_files' ), isset( $prop['size'] ) );
- if ( isset( $prop['hidden'] ) )
- {
+ if ( isset( $prop['hidden'] ) ) {
$this->addTables( array( 'page', 'page_props' ) );
$this->addJoinConds( array(
'page' => array( 'LEFT JOIN', array(
@@ -88,10 +90,10 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$res = $this->select( __METHOD__ );
$pages = array();
- $categories = array();
+
$result = $this->getResult();
$count = 0;
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $params['limit'] ) {
// We've reached the one extra which shows that there are additional cats to be had. Stop here...
// TODO: Security issue - if the user has no right to view next title, it will still be shown
@@ -101,9 +103,9 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
// Normalize titles
$titleObj = Title::makeTitle( NS_CATEGORY, $row->cat_title );
- if ( !is_null( $resultPageSet ) )
+ if ( !is_null( $resultPageSet ) ) {
$pages[] = $titleObj->getPrefixedText();
- else {
+ } else {
$item = array();
$result->setContent( $item, $titleObj->getText() );
if ( isset( $prop['size'] ) ) {
@@ -112,17 +114,16 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$item['files'] = intval( $row->cat_files );
$item['subcats'] = intval( $row->cat_subcats );
}
- if ( isset( $prop['hidden'] ) && $row->cat_hidden )
+ if ( isset( $prop['hidden'] ) && $row->cat_hidden ) {
$item['hidden'] = '';
+ }
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $item );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->cat_title ) );
break;
}
}
}
- $db->freeResult( $res );
if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'c' );
@@ -132,38 +133,44 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'from' => null,
+ 'to' => null,
'prefix' => null,
'dir' => array(
- ApiBase :: PARAM_DFLT => 'ascending',
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
'ascending',
'descending'
),
),
- '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
+ '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
),
- 'prop' => array (
- ApiBase :: PARAM_TYPE => array( 'size', 'hidden' ),
- ApiBase :: PARAM_DFLT => '',
- ApiBase :: PARAM_ISMULTI => true
+ 'prop' => array(
+ ApiBase::PARAM_TYPE => array( 'size', 'hidden' ),
+ ApiBase::PARAM_DFLT => '',
+ ApiBase::PARAM_ISMULTI => true
),
);
}
public function getParamDescription() {
- return array (
- 'from' => 'The category to start enumerating from.',
- 'prefix' => 'Search for all category titles that begin with this value.',
- 'dir' => 'Direction to sort in.',
- 'limit' => 'How many categories to return.',
- 'prop' => 'Which properties to get',
+ return array(
+ 'from' => 'The category to start enumerating from',
+ 'to' => 'The category to stop enumerating at',
+ 'prefix' => 'Search for all category titles that begin with this value',
+ 'dir' => 'Direction to sort in',
+ '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__',
+ ),
);
}
@@ -172,13 +179,13 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&list=allcategories&acprop=size',
'api.php?action=query&generator=allcategories&gacprefix=List&prop=info',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllCategories.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryAllCategories.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index 6b6fc2c0..78784845 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on July 7, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on July 7, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryAllLinks extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'al' );
+ parent::__construct( $query, $moduleName, 'al' );
}
public function execute() {
@@ -52,7 +53,6 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
private function run( $resultPageSet = null ) {
-
$db = $this->getDB();
$params = $this->extractRequestParams();
@@ -61,36 +61,46 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$fld_title = isset( $prop['title'] );
if ( $params['unique'] ) {
- if ( !is_null( $resultPageSet ) )
+ if ( !is_null( $resultPageSet ) ) {
$this->dieUsage( $this->getModuleName() . ' cannot be used as a generator in unique links mode', 'params' );
- if ( $fld_ids )
+ }
+ if ( $fld_ids ) {
$this->dieUsage( $this->getModuleName() . ' cannot return corresponding page ids in unique links mode', 'params' );
+ }
$this->addOption( 'DISTINCT' );
}
$this->addTables( 'pagelinks' );
$this->addWhereFld( 'pl_namespace', $params['namespace'] );
-
- if ( !is_null( $params['from'] ) && !is_null( $params['continue'] ) )
+
+ if ( !is_null( $params['from'] ) && !is_null( $params['continue'] ) ) {
$this->dieUsage( 'alcontinue and alfrom cannot be used together', 'params' );
- if ( !is_null( $params['continue'] ) )
- {
+ }
+ if ( !is_null( $params['continue'] ) ) {
$arr = explode( '|', $params['continue'] );
- if ( count( $arr ) != 2 )
- $this->dieUsage( "Invalid continue parameter", 'badcontinue' );
+ if ( count( $arr ) != 2 ) {
+ $this->dieUsage( 'Invalid continue parameter', 'badcontinue' );
+ }
$from = $this->getDB()->strencode( $this->titleToKey( $arr[0] ) );
$id = intval( $arr[1] );
- $this->addWhere( "pl_title > '$from' OR " .
- "(pl_title = '$from' AND " .
- "pl_from > $id)" );
+ $this->addWhere(
+ "pl_title > '$from' OR " .
+ "(pl_title = '$from' AND " .
+ "pl_from > $id)"
+ );
}
- if ( !is_null( $params['from'] ) )
+ if ( !is_null( $params['from'] ) ) {
$this->addWhere( 'pl_title>=' . $db->addQuotes( $this->titlePartToKey( $params['from'] ) ) );
- if ( isset ( $params['prefix'] ) )
+ }
+ if ( !is_null( $params['to'] ) ) {
+ $this->addWhere( 'pl_title<=' . $db->addQuotes( $this->titlePartToKey( $params['to'] ) ) );
+ }
+ if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'pl_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ }
- $this->addFields( array (
+ $this->addFields( array(
'pl_title',
) );
$this->addFieldsIf( 'pl_from', !$params['unique'] );
@@ -98,49 +108,51 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$this->addOption( 'USE INDEX', 'pl_namespace' );
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
- if ( $params['unique'] )
+ if ( $params['unique'] ) {
$this->addOption( 'ORDER BY', 'pl_title' );
- else
+ } else {
$this->addOption( 'ORDER BY', 'pl_title, pl_from' );
+ }
$res = $this->select( __METHOD__ );
- $pageids = array ();
+ $pageids = array();
$count = 0;
$result = $this->getResult();
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
// TODO: Security issue - if the user has no right to view next title, it will still be shown
- if ( $params['unique'] )
+ if ( $params['unique'] ) {
$this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->pl_title ) );
- else
+ } else {
$this->setContinueEnumParameter( 'continue', $this->keyToTitle( $row->pl_title ) . "|" . $row->pl_from );
+ }
break;
}
if ( is_null( $resultPageSet ) ) {
$vals = array();
- if ( $fld_ids )
+ if ( $fld_ids ) {
$vals['fromid'] = intval( $row->pl_from );
+ }
if ( $fld_title ) {
- $title = Title :: makeTitle( $params['namespace'], $row->pl_title );
+ $title = Title::makeTitle( $params['namespace'], $row->pl_title );
ApiQueryBase::addTitleInfo( $vals, $title );
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
- if ( !$fit )
- {
- if ( $params['unique'] )
+ if ( !$fit ) {
+ if ( $params['unique'] ) {
$this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->pl_title ) );
- else
+ } else {
$this->setContinueEnumParameter( 'continue', $this->keyToTitle( $row->pl_title ) . "|" . $row->pl_from );
+ }
break;
}
} else {
$pageids[] = $row->pl_from;
}
}
- $db->freeResult( $res );
if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'l' );
@@ -150,65 +162,73 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'continue' => null,
'from' => null,
+ 'to' => null,
'prefix' => null,
'unique' => false,
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_DFLT => 'title',
- ApiBase :: PARAM_TYPE => array (
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'title',
+ ApiBase::PARAM_TYPE => array(
'ids',
'title'
)
),
- 'namespace' => array (
- ApiBase :: PARAM_DFLT => 0,
- ApiBase :: PARAM_TYPE => 'namespace'
+ 'namespace' => array(
+ ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_TYPE => 'namespace'
),
- 'limit' => array (
- ApiBase :: PARAM_DFLT => 10,
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
- ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ '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
)
);
}
public function getParamDescription() {
- return array (
- 'from' => 'The page title to start enumerating from.',
- 'prefix' => 'Search for all page titles that begin with this value.',
- 'unique' => 'Only show unique links. Cannot be used with generator or prop=ids',
- 'prop' => 'What pieces of information to include',
- 'namespace' => 'The namespace to enumerate.',
- 'limit' => 'How many total links to return.',
- 'continue' => 'When more results are available, use this to continue.',
+ $p = $this->getModulePrefix();
+ return array(
+ 'from' => 'The page title to start enumerating from',
+ 'to' => 'The page title to stop enumerating at',
+ 'prefix' => 'Search for all page titles that begin with this value',
+ 'unique' => "Only show unique links. Cannot be used with generator or {$p}prop=ids",
+ 'prop' => array(
+ 'What pieces of information to include',
+ " ids - Adds pageid of where the link is from (Cannot be used with {$p}unique)",
+ ' title - Adds the title of the link',
+ ),
+ 'namespace' => 'The namespace to enumerate',
+ 'limit' => 'How many total links to return',
+ 'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
return 'Enumerate all links that point to a given namespace';
}
-
+
public function getPossibleErrors() {
+ $m = $this->getModuleName();
return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => $this->getModuleName() . ' cannot be used as a generator in unique links mode' ),
- array( 'code' => 'params', 'info' => $this->getModuleName() . ' cannot return corresponding page ids in unique links mode' ),
+ array( 'code' => 'params', 'info' => "{$m} cannot be used as a generator in unique links mode" ),
+ array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique links mode" ),
array( 'code' => 'params', 'info' => 'alcontinue and alfrom cannot be used together' ),
array( 'code' => 'badcontinue', 'info' => 'Invalid continue parameter' ),
) );
}
protected function getExamples() {
- return array (
- 'api.php?action=query&list=alllinks&alunique&alfrom=B',
+ return array(
+ 'api.php?action=query&list=alllinks&alunique=&alfrom=B',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllLinks.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryAllLinks.php 77192 2010-11-23 22:05:27Z btongminh $';
}
}
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index 611fc98c..77f507fc 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on July 7, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on July 7, 2007
+ *
+ * Copyright © 2007 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -34,9 +35,8 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* @ingroup API
*/
class ApiQueryAllUsers extends ApiQueryBase {
-
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'au' );
+ parent::__construct( $query, $moduleName, 'au' );
}
public function execute() {
@@ -51,18 +51,23 @@ class ApiQueryAllUsers extends ApiQueryBase {
$fld_groups = isset( $prop['groups'] );
$fld_registration = isset( $prop['registration'] );
} else {
- $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration = false;
+ $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration = $fld_rights = false;
}
$limit = $params['limit'];
$this->addTables( 'user', 'u1' );
$useIndex = true;
- if ( !is_null( $params['from'] ) )
+ if ( !is_null( $params['from'] ) ) {
$this->addWhere( 'u1.user_name >= ' . $db->addQuotes( $this->keyToTitle( $params['from'] ) ) );
+ }
+ if ( !is_null( $params['to'] ) ) {
+ $this->addWhere( 'u1.user_name <= ' . $db->addQuotes( $this->keyToTitle( $params['to'] ) ) );
+ }
- if ( !is_null( $params['prefix'] ) )
+ if ( !is_null( $params['prefix'] ) ) {
$this->addWhere( 'u1.user_name' . $db->buildLike( $this->keyToTitle( $params['prefix'] ), $db->anyString() ) );
+ }
if ( !is_null( $params['group'] ) ) {
$useIndex = false;
@@ -73,8 +78,9 @@ class ApiQueryAllUsers extends ApiQueryBase {
'ug1.ug_group' => $params['group'] ) ) ) );
}
- if ( $params['witheditsonly'] )
+ if ( $params['witheditsonly'] ) {
$this->addWhere( 'u1.user_editcount > 0' );
+ }
if ( $fld_groups ) {
// Show the groups the given users belong to
@@ -89,19 +95,14 @@ class ApiQueryAllUsers extends ApiQueryBase {
} else {
$sqlLimit = $limit + 1;
}
- if ( $fld_blockinfo ) {
- $this->addTables( 'ipblocks' );
- $this->addTables( 'user', 'u2' );
- $u2 = $this->getAliasedName( 'user', 'u2' );
- $this->addJoinConds( array(
- 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=u1.user_id' ),
- $u2 => array( 'LEFT JOIN', 'ipb_by=u2.user_id' ) ) );
- $this->addFields( array( 'ipb_reason', 'u2.user_name AS blocker_name' ) );
- }
+ $this->showHiddenUsersAddBlockInfo( $fld_blockinfo );
$this->addOption( 'LIMIT', $sqlLimit );
- $this->addFields( 'u1.user_name' );
+ $this->addFields( array(
+ 'u1.user_name',
+ 'u1.user_id'
+ ) );
$this->addFieldsIf( 'u1.user_editcount', $fld_editcount );
$this->addFieldsIf( 'u1.user_registration', $fld_registration );
@@ -113,7 +114,6 @@ class ApiQueryAllUsers extends ApiQueryBase {
$res = $this->select( __METHOD__ );
- $data = array ();
$count = 0;
$lastUserData = false;
$lastUser = false;
@@ -125,30 +125,25 @@ class ApiQueryAllUsers extends ApiQueryBase {
// 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.
- //
- while ( true ) {
- $row = $db->fetchObject( $res );
+ foreach ( $res as $row ) {
$count++;
- if ( !$row || $lastUser !== $row->user_name ) {
+ if ( $lastUser !== $row->user_name ) {
// Save the last pass's user data
- if ( is_array( $lastUserData ) )
- {
+ if ( is_array( $lastUserData ) ) {
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $lastUserData );
- if ( !$fit )
- {
+
+ $lastUserData = null;
+
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'from',
$this->keyToTitle( $lastUserData['name'] ) );
break;
}
}
- // No more rows left
- if ( !$row )
- break;
-
if ( $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->user_name ) );
@@ -157,23 +152,31 @@ class ApiQueryAllUsers extends ApiQueryBase {
// Record new user's data
$lastUser = $row->user_name;
- $lastUserData = array( 'name' => $lastUser );
- if ( $fld_blockinfo ) {
+ $lastUserData = array(
+ 'name' => $lastUser,
+ 'userid' => $row->user_id,
+ );
+ if ( $fld_blockinfo && !is_null( $row->blocker_name ) ) {
$lastUserData['blockedby'] = $row->blocker_name;
$lastUserData['blockreason'] = $row->ipb_reason;
}
- if ( $fld_editcount )
+ if ( $row->ipb_deleted ) {
+ $lastUserData['hidden'] = '';
+ }
+ if ( $fld_editcount ) {
$lastUserData['editcount'] = intval( $row->user_editcount );
- if ( $fld_registration )
+ }
+ if ( $fld_registration ) {
$lastUserData['registration'] = $row->user_registration ?
wfTimestamp( TS_ISO_8601, $row->user_registration ) : '';
+ }
}
if ( $sqlLimit == $count ) {
// BUG! database contains group name that User::getAllGroups() does not return
// TODO: should handle this more gracefully
- ApiBase :: dieDebug( __METHOD__,
+ ApiBase::dieDebug( __METHOD__,
'MediaWiki configuration error: the database contains more user groups than known to User::getAllGroups() function' );
}
@@ -185,57 +188,63 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
if ( is_array( $lastUserData ) ) {
- $fit = $result->addValue( array( 'query', $this->getModuleName() ),
- null, $lastUserData );
- if ( !$fit ) {
- $this->setContinueEnumParameter( 'from',
- $this->keyToTitle( $lastUserData['name'] ) );
- }
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ),
+ null, $lastUserData );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'from',
+ $this->keyToTitle( $lastUserData['name'] ) );
+ }
}
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'u' );
}
public function getCacheMode( $params ) {
- return 'public';
+ return 'anon-public-user-private';
}
public function getAllowedParams() {
- return array (
+ return array(
'from' => null,
+ 'to' => null,
'prefix' => null,
'group' => array(
- ApiBase :: PARAM_TYPE => User::getAllGroups()
+ ApiBase::PARAM_TYPE => User::getAllGroups()
),
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'blockinfo',
'groups',
'editcount',
'registration'
)
),
- '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
+ '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
),
'witheditsonly' => false,
);
}
public function getParamDescription() {
- return array (
- 'from' => 'The user name to start enumerating from.',
- 'prefix' => 'Search for all page titles that begin with this value.',
+ 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',
'group' => 'Limit users to a given group name',
'prop' => array(
'What pieces of information to include.',
- '`groups` property uses more server resources and may return fewer results than the limit.' ),
- 'limit' => 'How many total user names to return.',
+ ' blockinfo - Adds the information about a current block on the user',
+ ' groups - Lists groups that the user is in',
+ ' editcount - Adds the edit count of the user',
+ ' registration - Adds the timestamp of when the user registered',
+ '`groups` property uses more server resources and may return fewer results than the limit' ),
+ 'limit' => 'How many total user names to return',
'witheditsonly' => 'Only list users who have made edits',
);
}
@@ -245,12 +254,12 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&list=allusers&aufrom=Y',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllUsers.php 79562 2011-01-04 06:15:54Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryAllUsers.php 85354 2011-04-04 18:25:31Z demon $';
}
}
diff --git a/includes/api/ApiQueryAllimages.php b/includes/api/ApiQueryAllimages.php
index 0a745516..a7825519 100644
--- a/includes/api/ApiQueryAllimages.php
+++ b/includes/api/ApiQueryAllimages.php
@@ -1,11 +1,11 @@
<?php
-/*
- * Created on Mar 16, 2008
- *
+/**
* API for MediaWiki 1.12+
*
- * Copyright (C) 2008 Vasiliev Victor vasilvv@gmail.com,
+ * Created on Mar 16, 2008
+ *
+ * Copyright © 2008 Vasiliev Victor vasilvv@gmail.com,
* based on ApiQueryAllpages.php
*
* This program is free software; you can redistribute it and/or modify
@@ -20,13 +20,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -37,14 +39,14 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryAllimages extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'ai' );
+ parent::__construct( $query, $moduleName, 'ai' );
$this->mRepo = RepoGroup::singleton()->getLocalRepo();
}
-
+
/**
* Overide parent method to make sure to make sure the repo's DB is used
* which may not necesarilly be the same as the local DB.
- *
+ *
* TODO: allow querying non-local repos.
*/
protected function getDB() {
@@ -60,16 +62,18 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
}
public function executeGenerator( $resultPageSet ) {
- if ( $resultPageSet->isResolvingRedirects() )
+ if ( $resultPageSet->isResolvingRedirects() ) {
$this->dieUsage( 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator', 'params' );
+ }
$this->run( $resultPageSet );
}
private function run( $resultPageSet = null ) {
$repo = $this->mRepo;
- if ( !$repo instanceof LocalRepo )
+ if ( !$repo instanceof LocalRepo ) {
$this->dieUsage( 'Local file repository does not support querying all images', 'unsupportedrepo' );
+ }
$db = $this->getDB();
@@ -78,15 +82,17 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
// Image filters
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $this->addWhereRange( 'img_name', $dir, $from, null );
- if ( isset ( $params['prefix'] ) )
+ $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $this->addWhereRange( 'img_name', $dir, $from, $to );
+
+ if ( isset( $params['prefix'] ) )
$this->addWhere( 'img_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
- if ( isset ( $params['minsize'] ) ) {
+ if ( isset( $params['minsize'] ) ) {
$this->addWhere( 'img_size>=' . intval( $params['minsize'] ) );
}
- if ( isset ( $params['maxsize'] ) ) {
+ if ( isset( $params['maxsize'] ) ) {
$this->addWhere( 'img_size<=' . intval( $params['maxsize'] ) );
}
@@ -115,7 +121,7 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
$titles = array();
$count = 0;
$result = $this->getResult();
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
// TODO: Security issue - if the user has no right to view next title, it will still be shown
@@ -136,7 +142,6 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
$titles[] = Title::makeTitle( NS_IMAGE, $row->img_name );
}
}
- $db->freeResult( $res );
if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'img' );
@@ -148,55 +153,71 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
public function getAllowedParams() {
return array (
'from' => null,
+ 'to' => null,
'prefix' => null,
- 'minsize' => array (
- ApiBase :: PARAM_TYPE => 'integer',
+ 'minsize' => array(
+ ApiBase::PARAM_TYPE => 'integer',
),
- 'maxsize' => array (
- ApiBase :: PARAM_TYPE => 'integer',
+ 'maxsize' => array(
+ ApiBase::PARAM_TYPE => 'integer',
),
- '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
+ '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 (
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
'ascending',
'descending'
)
),
'sha1' => null,
'sha1base36' => null,
- 'prop' => array (
- ApiBase :: PARAM_TYPE => ApiQueryImageInfo::getPropertyNames(),
- ApiBase :: PARAM_DFLT => 'timestamp|url',
- ApiBase :: PARAM_ISMULTI => true
+ 'prop' => array(
+ ApiBase::PARAM_TYPE => ApiQueryImageInfo::getPropertyNames(),
+ ApiBase::PARAM_DFLT => 'timestamp|url',
+ ApiBase::PARAM_ISMULTI => true
)
);
}
public function getParamDescription() {
- return array (
- 'from' => 'The image title to start enumerating from.',
- 'prefix' => 'Search for all image titles that begin with this value.',
+ return array(
+ 'from' => 'The image title to start enumerating from',
+ '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',
'minsize' => 'Limit to images with at least this many bytes',
'maxsize' => 'Limit to images with at most this many bytes',
- 'limit' => 'How many total images to return.',
- 'sha1' => 'SHA1 hash of image',
+ 'limit' => 'How many images in total to return',
+ 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36",
'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)',
- 'prop' => 'Which properties to get',
+ 'prop' => array(
+ 'Which properties to get',
+ ' timestamp - Adds the timestamp when the image was upload',
+ ' user - Adds the username of the last uploader',
+ ' userid - Adds the user id of the last uploader',
+ ' comment - Adds the comment of the last upload',
+ ' url - Adds the URL of the image and its description page',
+ ' size - Adds the size of the image in bytes and its height and width',
+ ' dimensions - Alias of size',
+ ' sha1 - Adds the sha1 of the image',
+ ' mime - Adds the MIME of the image',
+ ' thumbmime - Adds the MIME of the tumbnail for the image',
+ ' archivename - Adds the file name of the archive version for non-latest versions',
+ ' bitdepth - Adds the bit depth of the version',
+ ),
);
}
public function getDescription() {
return 'Enumerate all images sequentially';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'params', 'info' => 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator' ),
@@ -205,7 +226,7 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
+ return array(
'Simple Use',
' Show a list of images starting at the letter "B"',
' api.php?action=query&list=allimages&aifrom=B',
@@ -216,6 +237,6 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllimages.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryAllimages.php 71838 2010-08-28 01:18:18Z reedy $';
}
}
diff --git a/includes/api/ApiQueryAllmessages.php b/includes/api/ApiQueryAllmessages.php
index 7dd9d874..81ff255a 100644
--- a/includes/api/ApiQueryAllmessages.php
+++ b/includes/api/ApiQueryAllmessages.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Dec 1, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Dec 1, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -36,22 +37,23 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryAllmessages extends ApiQueryBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'am' );
+ parent::__construct( $query, $moduleName, 'am' );
}
public function execute() {
$params = $this->extractRequestParams();
- if ( !is_null( $params['lang'] ) )
- {
- global $wgLang;
+ global $wgLang;
+
+ $oldLang = null;
+ if ( !is_null( $params['lang'] ) ) {
+ $oldLang = $wgLang; // Keep $wgLang for restore later
$wgLang = Language::factory( $params['lang'] );
}
-
+
$prop = array_flip( (array)$params['prop'] );
// Determine which messages should we print
- $messages_target = array();
if ( in_array( '*', $params['messages'] ) ) {
$message_names = array_keys( Language::getMessagesFor( 'en' ) );
sort( $message_names );
@@ -64,7 +66,8 @@ class ApiQueryAllmessages extends ApiQueryBase {
if ( isset( $params['filter'] ) ) {
$messages_filtered = array();
foreach ( $messages_target as $message ) {
- if ( strpos( $message, $params['filter'] ) !== false ) { // !== is used because filter can be at the beginnig of the string
+ // !== is used because filter can be at the beginning of the string
+ if ( strpos( $message, $params['filter'] ) !== false ) {
$messages_filtered[] = $message;
}
}
@@ -72,13 +75,18 @@ class ApiQueryAllmessages extends ApiQueryBase {
}
// Get all requested messages and print the result
- $messages = array();
$skip = !is_null( $params['from'] );
+ $useto = !is_null( $params['to'] );
$result = $this->getResult();
foreach ( $messages_target as $message ) {
// Skip all messages up to $params['from']
- if ( $skip && $message === $params['from'] )
+ if ( $skip && $message === $params['from'] ) {
$skip = false;
+ }
+
+ if( $useto && $message > $params['to'] ) {
+ break;
+ }
if ( !$skip ) {
$a = array( 'name' => $message );
@@ -89,35 +97,40 @@ class ApiQueryAllmessages extends ApiQueryBase {
// Check if the parser is enabled:
if ( $params['enableparser'] ) {
$msg = wfMsgExt( $message, array( 'parsemag' ), $args );
- } else if ( $args ) {
+ } elseif ( $args ) {
$msgString = wfMsgGetKey( $message, true, false, false );
$msg = wfMsgReplaceArgs( $msgString, $args );
} else {
$msg = wfMsgGetKey( $message, true, false, false );
}
- if ( wfEmptyMsg( $message, $msg ) )
+ if ( wfEmptyMsg( $message, $msg ) ) {
$a['missing'] = '';
- else {
+ } else {
ApiResult::setContent( $a, $msg );
if ( isset( $prop['default'] ) ) {
$default = wfMsgGetKey( $message, false, false, false );
if ( $default !== $msg ) {
- if ( wfEmptyMsg( $message, $default ) )
+ if ( wfEmptyMsg( $message, $default ) ) {
$a['defaultmissing'] = '';
- else
+ } else {
$a['default'] = $default;
+ }
}
}
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $a );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'from', $name );
+ $this->setContinueEnumParameter( 'from', $message );
break;
}
}
}
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'message' );
+
+ if ( !is_null( $oldLang ) ) {
+ $wgLang = $oldLang; // Restore $oldLang
+ }
}
public function getCacheMode( $params ) {
@@ -134,29 +147,30 @@ class ApiQueryAllmessages extends ApiQueryBase {
}
public function getAllowedParams() {
- return array (
- 'messages' => array (
- ApiBase :: PARAM_DFLT => '*',
- ApiBase :: PARAM_ISMULTI => true,
+ return array(
+ 'messages' => array(
+ ApiBase::PARAM_DFLT => '*',
+ ApiBase::PARAM_ISMULTI => true,
),
'prop' => array(
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'default'
)
),
'enableparser' => false,
'args' => array(
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true
),
'filter' => array(),
'lang' => null,
'from' => null,
+ 'to' => null,
);
}
public function getParamDescription() {
- return array (
+ return array(
'messages' => 'Which messages to output. "*" means all messages',
'prop' => 'Which properties to get',
'enableparser' => array( 'Set to enable parser, will preprocess the wikitext of message',
@@ -165,21 +179,22 @@ class ApiQueryAllmessages extends ApiQueryBase {
'filter' => 'Return only messages that contain this string',
'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.';
+ return 'Return messages from this site';
}
protected function getExamples() {
return array(
'api.php?action=query&meta=allmessages&amfilter=ipb-',
'api.php?action=query&meta=allmessages&ammessages=august|mainpage&amlang=de',
- );
+ );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllmessages.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryAllmessages.php 73756 2010-09-25 17:08:23Z reedy $';
}
}
diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php
index 37f22ee2..21f72916 100644
--- a/includes/api/ApiQueryAllpages.php
+++ b/includes/api/ApiQueryAllpages.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 25, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 25, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryAllpages extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'ap' );
+ parent::__construct( $query, $moduleName, 'ap' );
}
public function execute() {
@@ -48,8 +49,9 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
}
public function executeGenerator( $resultPageSet ) {
- if ( $resultPageSet->isResolvingRedirects() )
+ if ( $resultPageSet->isResolvingRedirects() ) {
$this->dieUsage( 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator', 'params' );
+ }
$this->run( $resultPageSet );
}
@@ -61,22 +63,25 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
// Page filters
$this->addTables( 'page' );
-
- if ( $params['filterredir'] == 'redirects' )
+
+ if ( $params['filterredir'] == 'redirects' ) {
$this->addWhereFld( 'page_is_redirect', 1 );
- else if ( $params['filterredir'] == 'nonredirects' )
+ } elseif ( $params['filterredir'] == 'nonredirects' ) {
$this->addWhereFld( 'page_is_redirect', 0 );
+ }
$this->addWhereFld( 'page_namespace', $params['namespace'] );
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $this->addWhereRange( 'page_title', $dir, $from, null );
+ $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $this->addWhereRange( 'page_title', $dir, $from, $to );
- if ( isset ( $params['prefix'] ) )
+ if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'page_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ }
if ( is_null( $resultPageSet ) ) {
- $selectFields = array (
+ $selectFields = array(
'page_namespace',
'page_title',
'page_id'
@@ -87,40 +92,42 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$this->addFields( $selectFields );
$forceNameTitleIndex = true;
- if ( isset ( $params['minsize'] ) ) {
+ if ( isset( $params['minsize'] ) ) {
$this->addWhere( 'page_len>=' . intval( $params['minsize'] ) );
$forceNameTitleIndex = false;
}
- if ( isset ( $params['maxsize'] ) ) {
+ if ( isset( $params['maxsize'] ) ) {
$this->addWhere( 'page_len<=' . intval( $params['maxsize'] ) );
$forceNameTitleIndex = false;
}
// Page protection filtering
- if ( !empty ( $params['prtype'] ) ) {
+ if ( !empty( $params['prtype'] ) ) {
$this->addTables( 'page_restrictions' );
$this->addWhere( 'page_id=pr_page' );
$this->addWhere( 'pr_expiry>' . $db->addQuotes( $db->timestamp() ) );
$this->addWhereFld( 'pr_type', $params['prtype'] );
- if ( isset ( $params['prlevel'] ) ) {
+ if ( isset( $params['prlevel'] ) ) {
// Remove the empty string and '*' from the prlevel array
$prlevel = array_diff( $params['prlevel'], array( '', '*' ) );
-
- if ( !empty( $prlevel ) )
+
+ if ( !empty( $prlevel ) ) {
$this->addWhereFld( 'pr_level', $prlevel );
+ }
}
- if ( $params['prfiltercascade'] == 'cascading' )
+ if ( $params['prfiltercascade'] == 'cascading' ) {
$this->addWhereFld( 'pr_cascade', 1 );
- else if ( $params['prfiltercascade'] == 'noncascading' )
+ } elseif ( $params['prfiltercascade'] == 'noncascading' ) {
$this->addWhereFld( 'pr_cascade', 0 );
+ }
$this->addOption( 'DISTINCT' );
$forceNameTitleIndex = false;
- } else if ( isset ( $params['prlevel'] ) ) {
+ } elseif ( isset( $params['prlevel'] ) ) {
$this->dieUsage( 'prlevel may not be used without prtype', 'params' );
}
@@ -129,7 +136,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$this->addJoinConds( array( 'langlinks' => array( 'LEFT JOIN', 'page_id=ll_from' ) ) );
$this->addWhere( 'll_from IS NULL' );
$forceNameTitleIndex = false;
- } else if ( $params['filterlanglinks'] == 'withlanglinks' ) {
+ } elseif ( $params['filterlanglinks'] == 'withlanglinks' ) {
$this->addTables( 'langlinks' );
$this->addWhere( 'page_id=ll_from' );
$this->addOption( 'STRAIGHT_JOIN' );
@@ -139,8 +146,9 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$forceNameTitleIndex = false;
}
- if ( $forceNameTitleIndex )
+ if ( $forceNameTitleIndex ) {
$this->addOption( 'USE INDEX', 'name_title' );
+ }
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
@@ -148,7 +156,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
// TODO: Security issue - if the user has no right to view next title, it will still be shown
@@ -157,14 +165,14 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $title = Title :: makeTitle( $row->page_namespace, $row->page_title );
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
$vals = array(
'pageid' => intval( $row->page_id ),
'ns' => intval( $title->getNamespace() ),
- 'title' => $title->getPrefixedText() );
+ 'title' => $title->getPrefixedText()
+ );
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->page_title ) );
break;
}
@@ -172,7 +180,6 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$resultPageSet->processDbRow( $row );
}
}
- $db->freeResult( $res );
if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' );
@@ -180,82 +187,85 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- global $wgRestrictionTypes, $wgRestrictionLevels;
+ global $wgRestrictionLevels;
- return array (
+ return array(
'from' => null,
+ 'to' => null,
'prefix' => null,
- 'namespace' => array (
- ApiBase :: PARAM_DFLT => 0,
- ApiBase :: PARAM_TYPE => 'namespace',
+ 'namespace' => array(
+ ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_TYPE => 'namespace',
),
- 'filterredir' => array (
- ApiBase :: PARAM_DFLT => 'all',
- ApiBase :: PARAM_TYPE => array (
+ 'filterredir' => array(
+ ApiBase::PARAM_DFLT => 'all',
+ ApiBase::PARAM_TYPE => array(
'all',
'redirects',
'nonredirects'
)
),
- 'minsize' => array (
- ApiBase :: PARAM_TYPE => 'integer',
+ 'minsize' => array(
+ ApiBase::PARAM_TYPE => 'integer',
),
- 'maxsize' => array (
- ApiBase :: PARAM_TYPE => 'integer',
+ 'maxsize' => array(
+ ApiBase::PARAM_TYPE => 'integer',
),
- 'prtype' => array (
- ApiBase :: PARAM_TYPE => $wgRestrictionTypes,
- ApiBase :: PARAM_ISMULTI => true
+ 'prtype' => array(
+ ApiBase::PARAM_TYPE => Title::getFilteredRestrictionTypes( true ),
+ ApiBase::PARAM_ISMULTI => true
),
- 'prlevel' => array (
- ApiBase :: PARAM_TYPE => $wgRestrictionLevels,
- ApiBase :: PARAM_ISMULTI => true
+ 'prlevel' => array(
+ ApiBase::PARAM_TYPE => $wgRestrictionLevels,
+ ApiBase::PARAM_ISMULTI => true
),
- 'prfiltercascade' => array (
- ApiBase :: PARAM_DFLT => 'all',
- ApiBase :: PARAM_TYPE => array (
+ 'prfiltercascade' => array(
+ ApiBase::PARAM_DFLT => 'all',
+ ApiBase::PARAM_TYPE => array(
'cascading',
'noncascading',
'all'
),
),
- '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
+ '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 (
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
'ascending',
'descending'
)
),
'filterlanglinks' => array(
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_TYPE => array(
'withlanglinks',
'withoutlanglinks',
'all'
),
- ApiBase :: PARAM_DFLT => 'all'
+ ApiBase::PARAM_DFLT => 'all'
)
);
}
public function getParamDescription() {
- return array (
- 'from' => 'The page title to start enumerating from.',
- 'prefix' => 'Search for all page titles that begin with this value.',
- 'namespace' => 'The namespace to enumerate.',
- 'filterredir' => 'Which pages to list.',
+ $p = $this->getModulePrefix();
+ return array(
+ 'from' => 'The page title to start enumerating from',
+ '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 apprtype= parameter)',
- 'prfiltercascade' => 'Filter protections based on cascadingness (ignored when apprtype isn\'t set)',
+ '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' => 'Filter based on whether a page has langlinks',
'limit' => 'How many total pages to return.'
);
@@ -264,7 +274,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
public function getDescription() {
return 'Enumerate all pages sequentially in a given namespace';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'params', 'info' => 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator' ),
@@ -273,7 +283,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
+ return array(
'Simple Use',
' Show a list of pages starting at the letter "B"',
' api.php?action=query&list=allpages&apfrom=B',
@@ -286,6 +296,6 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllpages.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryAllpages.php 85354 2011-04-04 18:25:31Z demon $';
}
}
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index 648da069..b412d2d6 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 16, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Oct 16, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiQueryBase.php" );
+ require_once( "ApiQueryBase.php" );
}
/**
@@ -38,23 +39,23 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiQueryBacklinks extends ApiQueryGeneratorBase {
- private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID, $redirID, $redirect;
+ private $params, $rootTitle, $contID, $redirID, $redirect;
private $bl_ns, $bl_from, $bl_table, $bl_code, $bl_title, $bl_sort, $bl_fields, $hasNS;
private $pageMap, $resultArr;
// output element name, database column field prefix, database table
- private $backlinksSettings = array (
- 'backlinks' => array (
+ private $backlinksSettings = array(
+ 'backlinks' => array(
'code' => 'bl',
'prefix' => 'pl',
'linktbl' => 'pagelinks'
),
- 'embeddedin' => array (
+ 'embeddedin' => array(
'code' => 'ei',
'prefix' => 'tl',
'linktbl' => 'templatelinks'
),
- 'imageusage' => array (
+ 'imageusage' => array(
'code' => 'iu',
'prefix' => 'il',
'linktbl' => 'imagelinks'
@@ -62,27 +63,29 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
);
public function __construct( $query, $moduleName ) {
- extract( $this->backlinksSettings[$moduleName] );
+ $settings = $this->backlinksSettings[$moduleName];
+ $prefix = $settings['prefix'];
+ $code = $settings['code'];
$this->resultArr = array();
- parent :: __construct( $query, $moduleName, $code );
+ parent::__construct( $query, $moduleName, $code );
$this->bl_ns = $prefix . '_namespace';
$this->bl_from = $prefix . '_from';
- $this->bl_table = $linktbl;
+ $this->bl_table = $settings['linktbl'];
$this->bl_code = $code;
$this->hasNS = $moduleName !== 'imageusage';
if ( $this->hasNS ) {
$this->bl_title = $prefix . '_title';
$this->bl_sort = "{$this->bl_ns}, {$this->bl_title}, {$this->bl_from}";
- $this->bl_fields = array (
+ $this->bl_fields = array(
$this->bl_ns,
$this->bl_title
);
} else {
$this->bl_title = $prefix . '_to';
$this->bl_sort = "{$this->bl_title}, {$this->bl_from}";
- $this->bl_fields = array (
+ $this->bl_fields = array(
$this->bl_title
);
}
@@ -106,29 +109,32 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
* AND pl_title='Foo' AND pl_namespace=0
* LIMIT 11 ORDER BY pl_from
*/
- $db = $this->getDB();
$this->addTables( array( $this->bl_table, 'page' ) );
$this->addWhere( "{$this->bl_from}=page_id" );
- if ( is_null( $resultPageSet ) )
+ if ( is_null( $resultPageSet ) ) {
$this->addFields( array( 'page_id', 'page_title', 'page_namespace' ) );
- else
+ } else {
$this->addFields( $resultPageSet->getPageTableFields() );
+ }
$this->addFields( 'page_is_redirect' );
$this->addWhereFld( $this->bl_title, $this->rootTitle->getDBkey() );
- if ( $this->hasNS )
+ if ( $this->hasNS ) {
$this->addWhereFld( $this->bl_ns, $this->rootTitle->getNamespace() );
+ }
$this->addWhereFld( 'page_namespace', $this->params['namespace'] );
- if ( !is_null( $this->contID ) )
+ if ( !is_null( $this->contID ) ) {
$this->addWhere( "{$this->bl_from}>={$this->contID}" );
+ }
- if ( $this->params['filterredir'] == 'redirects' )
+ if ( $this->params['filterredir'] == 'redirects' ) {
$this->addWhereFld( 'page_is_redirect', 1 );
- else if ( $this->params['filterredir'] == 'nonredirects' && !$this->redirect )
+ } elseif ( $this->params['filterredir'] == 'nonredirects' && !$this->redirect ) {
// bug 22245 - Check for !redirect, as filtering nonredirects, when getting what links to them is contradictory
$this->addWhereFld( 'page_is_redirect', 0 );
+ }
$this->addOption( 'LIMIT', $this->params['limit'] + 1 );
$this->addOption( 'ORDER BY', $this->bl_from );
@@ -145,45 +151,48 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->addTables( array( 'page', $this->bl_table ) );
$this->addWhere( "{$this->bl_from}=page_id" );
- if ( is_null( $resultPageSet ) )
+ if ( is_null( $resultPageSet ) ) {
$this->addFields( array( 'page_id', 'page_title', 'page_namespace', 'page_is_redirect' ) );
- else
+ } else {
$this->addFields( $resultPageSet->getPageTableFields() );
+ }
$this->addFields( $this->bl_title );
- if ( $this->hasNS )
+ if ( $this->hasNS ) {
$this->addFields( $this->bl_ns );
+ }
// We can't use LinkBatch here because $this->hasNS may be false
$titleWhere = array();
- foreach ( $this->redirTitles as $t )
+ foreach ( $this->redirTitles as $t ) {
$titleWhere[] = "{$this->bl_title} = " . $db->addQuotes( $t->getDBkey() ) .
- ( $this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : "" );
+ ( $this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : '' );
+ }
$this->addWhere( $db->makeList( $titleWhere, LIST_OR ) );
$this->addWhereFld( 'page_namespace', $this->params['namespace'] );
- if ( !is_null( $this->redirID ) )
- {
+ if ( !is_null( $this->redirID ) ) {
$first = $this->redirTitles[0];
$title = $db->strencode( $first->getDBkey() );
$ns = $first->getNamespace();
$from = $this->redirID;
- if ( $this->hasNS )
+ if ( $this->hasNS ) {
$this->addWhere( "{$this->bl_ns} > $ns OR " .
"({$this->bl_ns} = $ns AND " .
"({$this->bl_title} > '$title' OR " .
"({$this->bl_title} = '$title' AND " .
"{$this->bl_from} >= $from)))" );
- else
+ } else {
$this->addWhere( "{$this->bl_title} > '$title' OR " .
"({$this->bl_title} = '$title' AND " .
"{$this->bl_from} >= $from)" );
-
+ }
}
- if ( $this->params['filterredir'] == 'redirects' )
+ if ( $this->params['filterredir'] == 'redirects' ) {
$this->addWhereFld( 'page_is_redirect', 1 );
- else if ( $this->params['filterredir'] == 'nonredirects' )
+ } elseif ( $this->params['filterredir'] == 'nonredirects' ) {
$this->addWhereFld( 'page_is_redirect', 0 );
+ }
$this->addOption( 'LIMIT', $this->params['limit'] + 1 );
$this->addOption( 'ORDER BY', $this->bl_sort );
@@ -197,20 +206,19 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 );
if ( $this->params['limit'] == 'max' ) {
$this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
- $this->getResult()->addValue( 'limits', $this->getModuleName(), $this->params['limit'] );
+ $this->getResult()->setParsedLimit( $this->getModuleName(), $this->params['limit'] );
}
$this->processContinue();
$this->prepareFirstQuery( $resultPageSet );
- $db = $this->getDB();
$res = $this->select( __METHOD__ . '::firstQuery' );
$count = 0;
$this->pageMap = array(); // Maps ns and title to pageid
$this->continueStr = null;
$this->redirTitles = array();
- while ( $row = $db->fetchObject( $res ) ) {
+ 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
@@ -218,93 +226,89 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
break;
}
- if ( is_null( $resultPageSet ) )
+ if ( is_null( $resultPageSet ) ) {
$this->extractRowInfo( $row );
- else
- {
+ } else {
$this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id;
- if ( $row->page_is_redirect )
+ if ( $row->page_is_redirect ) {
$this->redirTitles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
+ }
$resultPageSet->processDbRow( $row );
}
}
- $db->freeResult( $res );
- if ( $this->redirect && count( $this->redirTitles ) )
- {
+ if ( $this->redirect && count( $this->redirTitles ) ) {
$this->resetQueryParams();
$this->prepareSecondQuery( $resultPageSet );
$res = $this->select( __METHOD__ . '::secondQuery' );
$count = 0;
- while ( $row = $db->fetchObject( $res ) )
- {
- if ( ++$count > $this->params['limit'] )
- {
+ 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_IMAGE][$row-> { $this->bl_title } ];
+ if ( $this->hasNS ) {
+ $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}];
+ } else {
+ $parentID = $this->pageMap[NS_IMAGE][$row->{$this->bl_title}];
+ }
$this->continueStr = $this->getContinueRedirStr( $parentID, $row->page_id );
break;
}
- if ( is_null( $resultPageSet ) )
+ if ( is_null( $resultPageSet ) ) {
$this->extractRedirRowInfo( $row );
- else
+ } else {
$resultPageSet->processDbRow( $row );
+ }
}
- $db->freeResult( $res );
}
if ( is_null( $resultPageSet ) ) {
// Try to add the result data in one go and pray that it fits
$fit = $this->getResult()->addValue( 'query', $this->getModuleName(), array_values( $this->resultArr ) );
- if ( !$fit )
- {
+ if ( !$fit ) {
// It didn't fit. Add elements one by one until the
// result is full.
- foreach ( $this->resultArr as $pageID => $arr )
- {
+ foreach ( $this->resultArr as $pageID => $arr ) {
// Add the basic entry without redirlinks first
$fit = $this->getResult()->addValue(
array( 'query', $this->getModuleName() ),
null, array_diff_key( $arr, array( 'redirlinks' => '' ) ) );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->continueStr = $this->getContinueStr( $pageID );
break;
}
$hasRedirs = false;
- foreach ( (array)@$arr['redirlinks'] as $key => $redir )
- {
+ foreach ( (array)@$arr['redirlinks'] as $key => $redir ) {
$fit = $this->getResult()->addValue(
array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ),
$key, $redir );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->continueStr = $this->getContinueRedirStr( $pageID, $redir['pageid'] );
break;
}
$hasRedirs = true;
}
- if ( $hasRedirs )
+ if ( $hasRedirs ) {
$this->getResult()->setIndexedTagName_internal(
array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ),
$this->bl_code );
- if ( !$fit )
+ }
+ if ( !$fit ) {
break;
+ }
}
}
$this->getResult()->setIndexedTagName_internal(
- array( 'query', $this->getModuleName() ),
- $this->bl_code );
+ array( 'query', $this->getModuleName() ),
+ $this->bl_code
+ );
}
- if ( !is_null( $this->continueStr ) )
+ if ( !is_null( $this->continueStr ) ) {
$this->setContinueEnumParameter( 'continue', $this->continueStr );
+ }
}
private function extractRowInfo( $row ) {
@@ -312,8 +316,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$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 )
- {
+ if ( $row->page_is_redirect ) {
$a['redirect'] = '';
$this->redirTitles[] = $t;
}
@@ -321,12 +324,12 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->resultArr[$a['pageid']] = $a;
}
- private function extractRedirRowInfo( $row )
- {
+ 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 )
+ 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
@@ -335,24 +338,23 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
protected function processContinue() {
- if ( !is_null( $this->params['continue'] ) )
+ if ( !is_null( $this->params['continue'] ) ) {
$this->parseContinueParam();
- else {
- if ( $this->params['title'] !== "" ) {
+ } else {
+ if ( $this->params['title'] !== '' ) {
$title = Title::newFromText( $this->params['title'] );
if ( !$title ) {
$this->dieUsageMsg( array( 'invalidtitle', $this->params['title'] ) );
} else {
$this->rootTitle = $title;
}
- } else {
- $this->dieUsageMsg( array( 'missingparam', 'title' ) );
}
}
// only image titles are allowed for the root in imageinfo mode
- if ( !$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE )
+ 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() {
@@ -366,23 +368,27 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
// 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] );
- if ( $rootNs === 0 && $continueList[0] !== '0' )
+ if ( $rootNs === 0 && $continueList[0] !== '0' ) {
// Illegal continue parameter
- $this->dieUsage( "Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue" );
+ $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
+ }
$this->rootTitle = Title::makeTitleSafe( $rootNs, $continueList[1] );
- if ( !$this->rootTitle )
- $this->dieUsage( "Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue" );
+ if ( !$this->rootTitle ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
+ }
$contID = intval( $continueList[2] );
- if ( $contID === 0 && $continueList[2] !== '0' )
- $this->dieUsage( "Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue" );
+ if ( $contID === 0 && $continueList[2] !== '0' ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
+ }
$this->contID = $contID;
$redirID = intval( @$continueList[3] );
-
- if ( $redirID === 0 && @$continueList[3] !== '0' )
+
+ if ( $redirID === 0 && @$continueList[3] !== '0' ) {
// This one isn't required
return;
+ }
$this->redirID = $redirID;
}
@@ -398,88 +404,92 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- $retval = array (
- 'title' => null,
+ $retval = array(
+ 'title' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'continue' => null,
- 'namespace' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => 'namespace'
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace'
),
'filterredir' => array(
- ApiBase :: PARAM_DFLT => 'all',
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_DFLT => 'all',
+ ApiBase::PARAM_TYPE => array(
'all',
'redirects',
'nonredirects'
)
),
- '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
+ '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
)
);
- if ( $this->getModuleName() == 'embeddedin' )
+ if ( $this->getModuleName() == 'embeddedin' ) {
return $retval;
+ }
$retval['redirect'] = false;
return $retval;
}
public function getParamDescription() {
- $retval = array (
- 'title' => 'Title to search.',
- 'continue' => 'When more results are available, use this to continue.',
- 'namespace' => 'The namespace to enumerate.',
+ $retval = array(
+ 'title' => 'Title to search',
+ 'continue' => 'When more results are available, use this to continue',
+ 'namespace' => 'The namespace to enumerate',
);
- if ( $this->getModuleName() != 'embeddedin' )
+ 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.'
+ 'limit' => 'How many total pages to return'
) );
}
public function getDescription() {
switch ( $this->getModuleName() ) {
- case 'backlinks' :
+ case 'backlinks':
return 'Find all pages that link to the given page';
- case 'embeddedin' :
+ case 'embeddedin':
return 'Find all pages that embed (transclude) the given title';
- case 'imageusage' :
+ case 'imageusage':
return 'Find all pages that use the given image title.';
- default :
- ApiBase :: dieDebug( __METHOD__, 'Unknown module name' );
+ default:
+ ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
}
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'invalidtitle', 'title' ),
- array( 'missingparam', 'title' ),
array( 'code' => 'bad_image_title', 'info' => "The title for {$this->getModuleName()} query must be an image" ),
array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
protected function getExamples() {
- 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"
+ 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'
),
- 'embeddedin' => array (
- "api.php?action=query&list=embeddedin&eititle=Template:Stub",
- "api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info"
+ 'embeddedin' => array(
+ 'api.php?action=query&list=embeddedin&eititle=Template:Stub',
+ 'api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info'
),
- '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"
+ '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'
)
);
@@ -487,6 +497,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryBacklinks.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryBacklinks.php 75921 2010-11-03 12:49:21Z demon $';
}
}
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 893da566..61a5b4c8 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 7, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 7, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
@@ -39,18 +40,19 @@ abstract class ApiQueryBase extends ApiBase {
private $mQueryModule, $mDb, $tables, $where, $fields, $options, $join_conds;
- public function __construct( $query, $moduleName, $paramPrefix = '' ) {
- parent :: __construct( $query->getMain(), $moduleName, $paramPrefix );
+ public function __construct( ApiBase $query, $moduleName, $paramPrefix = '' ) {
+ parent::__construct( $query->getMain(), $moduleName, $paramPrefix );
$this->mQueryModule = $query;
$this->mDb = null;
$this->resetQueryParams();
}
/**
- * Get the cache mode for the data generated by this module. Override this
- * in the module subclass.
+ * Get the cache mode for the data generated by this module. Override
+ * this in the module subclass. For possible return values and other
+ * details about cache modes, see ApiMain::setCacheMode()
*
- * Public caching will only be allowed if *all* the modules that supply
+ * Public caching will only be allowed if *all* the modules that supply
* data for a given request return a cache mode of public.
*/
public function getCacheMode( $params ) {
@@ -61,11 +63,11 @@ abstract class ApiQueryBase extends ApiBase {
* Blank the internal arrays with query parameters
*/
protected function resetQueryParams() {
- $this->tables = array ();
- $this->where = array ();
- $this->fields = array ();
- $this->options = array ();
- $this->join_conds = array ();
+ $this->tables = array();
+ $this->where = array();
+ $this->fields = array();
+ $this->options = array();
+ $this->join_conds = array();
}
/**
@@ -76,16 +78,18 @@ abstract class ApiQueryBase extends ApiBase {
*/
protected function addTables( $tables, $alias = null ) {
if ( is_array( $tables ) ) {
- if ( !is_null( $alias ) )
- ApiBase :: dieDebug( __METHOD__, 'Multiple table aliases not supported' );
+ if ( !is_null( $alias ) ) {
+ ApiBase::dieDebug( __METHOD__, 'Multiple table aliases not supported' );
+ }
$this->tables = array_merge( $this->tables, $tables );
} else {
- if ( !is_null( $alias ) )
+ if ( !is_null( $alias ) ) {
$tables = $this->getAliasedName( $tables, $alias );
+ }
$this->tables[] = $tables;
}
}
-
+
/**
* Get the SQL for a table name with alias
* @param $table string Table name
@@ -95,7 +99,7 @@ abstract class ApiQueryBase extends ApiBase {
protected function getAliasedName( $table, $alias ) {
return $this->getDB()->tableName( $table ) . ' ' . $alias;
}
-
+
/**
* Add a set of JOIN conditions to the internal array
*
@@ -106,8 +110,9 @@ abstract class ApiQueryBase extends ApiBase {
* @param $join_conds array JOIN conditions
*/
protected function addJoinConds( $join_conds ) {
- if ( !is_array( $join_conds ) )
+ if ( !is_array( $join_conds ) ) {
ApiBase::dieDebug( __METHOD__, 'Join conditions have to be arrays' );
+ }
$this->join_conds = array_merge( $this->join_conds, $join_conds );
}
@@ -116,10 +121,11 @@ abstract class ApiQueryBase extends ApiBase {
* @param $value mixed Field name or array of field names
*/
protected function addFields( $value ) {
- if ( is_array( $value ) )
+ if ( is_array( $value ) ) {
$this->fields = array_merge( $this->fields, $value );
- else
+ } else {
$this->fields[] = $value;
+ }
}
/**
@@ -151,17 +157,18 @@ abstract class ApiQueryBase extends ApiBase {
if ( is_array( $value ) ) {
// Sanity check: don't insert empty arrays,
// Database::makeList() chokes on them
- if ( count( $value ) )
+ if ( count( $value ) ) {
$this->where = array_merge( $this->where, $value );
- }
- else
+ }
+ } else {
$this->where[] = $value;
+ }
}
/**
* Same as addWhere(), but add the WHERE clauses only if a condition is met
* @param $value mixed See addWhere()
- * @param $condition boolIf false, do nothing
+ * @param $condition bool If false, do nothing
* @return bool $condition
*/
protected function addWhereIf( $value, $condition ) {
@@ -178,10 +185,11 @@ abstract class ApiQueryBase extends ApiBase {
* @param $value string Value; ignored if null or empty array;
*/
protected function addWhereFld( $field, $value ) {
- // Use count() to its full documented capabilities to simultaneously
+ // Use count() to its full documented capabilities to simultaneously
// test for null, empty array or empty countable object
- if ( count( $value ) )
+ if ( count( $value ) ) {
$this->where[$field] = $value;
+ }
}
/**
@@ -202,18 +210,21 @@ abstract class ApiQueryBase extends ApiBase {
$before = ( $isDirNewer ? '<=' : '>=' );
$db = $this->getDB();
- if ( !is_null( $start ) )
+ if ( !is_null( $start ) ) {
$this->addWhere( $field . $after . $db->addQuotes( $start ) );
+ }
- if ( !is_null( $end ) )
+ if ( !is_null( $end ) ) {
$this->addWhere( $field . $before . $db->addQuotes( $end ) );
+ }
if ( $sort ) {
$order = $field . ( $isDirNewer ? '' : ' DESC' );
- if ( !isset( $this->options['ORDER BY'] ) )
+ if ( !isset( $this->options['ORDER BY'] ) ) {
$this->addOption( 'ORDER BY', $order );
- else
+ } else {
$this->addOption( 'ORDER BY', $this->options['ORDER BY'] . ', ' . $order );
+ }
}
}
@@ -224,24 +235,34 @@ abstract class ApiQueryBase extends ApiBase {
* @param $value string Option value
*/
protected function addOption( $name, $value = null ) {
- if ( is_null( $value ) )
+ if ( is_null( $value ) ) {
$this->options[] = $name;
- else
+ } else {
$this->options[$name] = $value;
+ }
}
/**
* Execute a SELECT query based on the values in the internal arrays
* @param $method string Function the query should be attributed to.
* You should usually use __METHOD__ here
+ * @param $extraQuery array Query data to add but not store in the object
+ * Format is array( 'tables' => ..., 'fields' => ..., 'where' => ..., 'options' => ..., 'join_conds' => ... )
* @return ResultWrapper
*/
- protected function select( $method ) {
+ protected function select( $method, $extraQuery = array() ) {
+
+ $tables = array_merge( $this->tables, isset( $extraQuery['tables'] ) ? (array)$extraQuery['tables'] : array() );
+ $fields = array_merge( $this->fields, isset( $extraQuery['fields'] ) ? (array)$extraQuery['fields'] : array() );
+ $where = array_merge( $this->where, isset( $extraQuery['where'] ) ? (array)$extraQuery['where'] : array() );
+ $options = array_merge( $this->options, isset( $extraQuery['options'] ) ? (array)$extraQuery['options'] : array() );
+ $join_conds = array_merge( $this->join_conds, isset( $extraQuery['join_conds'] ) ? (array)$extraQuery['join_conds'] : array() );
+
// getDB has its own profileDBIn/Out calls
$db = $this->getDB();
$this->profileDBIn();
- $res = $db->select( $this->tables, $this->fields, $this->where, $method, $this->options, $this->join_conds );
+ $res = $db->select( $tables, $fields, $where, $method, $options, $join_conds );
$this->profileDBOut();
return $res;
@@ -259,8 +280,9 @@ abstract class ApiQueryBase extends ApiBase {
$this->profileDBOut();
global $wgAPIMaxDBRows;
- if ( $rowcount > $wgAPIMaxDBRows )
+ if ( $rowcount > $wgAPIMaxDBRows ) {
return false;
+ }
return true;
}
@@ -295,7 +317,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a sub-element under the page element with the given page ID
* @param $pageId int Page ID
- * @param $data array Data array à la ApiResult
+ * @param $data array Data array à la ApiResult
* @return bool Whether the element fit in the result
*/
protected function addPageSubItems( $pageId, $data ) {
@@ -305,23 +327,25 @@ abstract class ApiQueryBase extends ApiBase {
$this->getModuleName(),
$data );
}
-
+
/**
* Same as addPageSubItems(), but one element of $data at a time
* @param $pageId int Page ID
- * @param $data array Data array à la ApiResult
+ * @param $item array Data array à la ApiResult
* @param $elemname string XML element name. If null, getModuleName()
* is used
* @return bool Whether the element fit in the result
*/
protected function addPageSubItem( $pageId, $item, $elemname = null ) {
- if ( is_null( $elemname ) )
+ if ( is_null( $elemname ) ) {
$elemname = $this->getModulePrefix();
+ }
$result = $this->getResult();
$fit = $result->addValue( array( 'query', 'pages', $pageId,
$this->getModuleName() ), null, $item );
- if ( !$fit )
+ if ( !$fit ) {
return false;
+ }
$result->setIndexedTagName_internal( array( 'query', 'pages', $pageId,
$this->getModuleName() ), $elemname );
return true;
@@ -345,8 +369,10 @@ abstract class ApiQueryBase extends ApiBase {
* @return Database
*/
protected function getDB() {
- if ( is_null( $this->mDb ) )
- $this->mDb = $this->getQuery()->getDB();
+ if ( is_null( $this->mDb ) ) {
+ $apiQuery = $this->getQuery();
+ $this->mDb = $apiQuery->getDB();
+ }
return $this->mDb;
}
@@ -356,7 +382,7 @@ abstract class ApiQueryBase extends ApiBase {
* @param $name string Name to assign to the database connection
* @param $db int One of the DB_* constants
* @param $groups array Query groups
- * @return Database
+ * @return Database
*/
public function selectNamedDB( $name, $db, $groups ) {
$this->mDb = $this->getQuery()->getNamedDB( $name, $db, $groups );
@@ -377,11 +403,13 @@ abstract class ApiQueryBase extends ApiBase {
*/
public function titleToKey( $title ) {
// Don't throw an error if we got an empty string
- if ( trim( $title ) == '' )
+ if ( trim( $title ) == '' ) {
return '';
+ }
$t = Title::newFromText( $title );
- if ( !$t )
+ if ( !$t ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
+ }
return $t->getPrefixedDbKey();
}
@@ -392,15 +420,17 @@ abstract class ApiQueryBase extends ApiBase {
*/
public function keyToTitle( $key ) {
// Don't throw an error if we got an empty string
- if ( trim( $key ) == '' )
+ if ( trim( $key ) == '' ) {
return '';
+ }
$t = Title::newFromDbKey( $key );
// This really shouldn't happen but we gotta check anyway
- if ( !$t )
+ if ( !$t ) {
$this->dieUsageMsg( array( 'invalidtitle', $key ) );
+ }
return $t->getPrefixedText();
}
-
+
/**
* An alternative to titleToKey() that doesn't trim trailing spaces
* @param $titlePart string Title part with spaces
@@ -409,7 +439,7 @@ abstract class ApiQueryBase extends ApiBase {
public function titlePartToKey( $titlePart ) {
return substr( $this->titleToKey( $titlePart . 'x' ), 0, - 1 );
}
-
+
/**
* An alternative to keyToTitle() that doesn't trim trailing spaces
* @param $keyPart string Key part with spaces
@@ -418,7 +448,37 @@ abstract class ApiQueryBase extends ApiBase {
public function keyPartToTitle( $keyPart ) {
return substr( $this->keyToTitle( $keyPart . 'x' ), 0, - 1 );
}
-
+
+ /**
+ * Filters hidden users (where the user doesn't have the right to view them)
+ * Also adds relevant block information
+ *
+ * @param bool $showBlockInfo
+ * @return void
+ */
+ public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
+ global $wgUser;
+ $userCanViewHiddenUsers = $wgUser->isAllowed( 'hideuser' );
+
+ if ( $showBlockInfo || !$userCanViewHiddenUsers ) {
+ $this->addTables( 'ipblocks' );
+ $this->addJoinConds( array(
+ 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=user_id' ),
+ ) );
+
+ $this->addFields( 'ipb_deleted' );
+
+ if ( $showBlockInfo ) {
+ $this->addFields( array( 'ipb_reason', 'ipb_by_text', 'ipb_expiry' ) );
+ }
+
+ // Don't show hidden names
+ if ( !$userCanViewHiddenUsers ) {
+ $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' );
+ }
+ }
+ }
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'invalidtitle', 'title' ),
@@ -431,7 +491,7 @@ abstract class ApiQueryBase extends ApiBase {
* @return string
*/
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiQueryBase.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryBase.php 85435 2011-04-05 14:00:08Z demon $';
}
}
@@ -443,7 +503,7 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
private $mIsGenerator;
public function __construct( $query, $moduleName, $paramPrefix = '' ) {
- parent :: __construct( $query, $moduleName, $paramPrefix );
+ parent::__construct( $query, $moduleName, $paramPrefix );
$this->mIsGenerator = false;
}
@@ -457,14 +517,15 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
/**
* Overrides base class to prepend 'g' to every generator parameter
- * @param $paramNames string Parameter name
+ * @param $paramName string Parameter name
* @return string Prefixed parameter name
*/
public function encodeParamName( $paramName ) {
- if ( $this->mIsGenerator )
- return 'g' . parent :: encodeParamName( $paramName );
- else
- return parent :: encodeParamName( $paramName );
+ if ( $this->mIsGenerator ) {
+ return 'g' . parent::encodeParamName( $paramName );
+ } else {
+ return parent::encodeParamName( $paramName );
+ }
}
/**
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index 8b321044..4edda645 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 10, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Sep 10, 2007
+ *
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -34,19 +35,20 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* @ingroup API
*/
class ApiQueryBlocks extends ApiQueryBase {
-
+
var $users;
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'bk' );
+ parent::__construct( $query, $moduleName, 'bk' );
}
public function execute() {
global $wgUser;
$params = $this->extractRequestParams();
- if ( isset( $params['users'] ) && isset( $params['ip'] ) )
+ if ( isset( $params['users'] ) && isset( $params['ip'] ) ) {
$this->dieUsage( 'bkusers and bkip cannot be used together', 'usersandip' );
+ }
$prop = array_flip( $params['prop'] );
$fld_id = isset( $prop['id'] );
@@ -59,60 +61,62 @@ class ApiQueryBlocks extends ApiQueryBase {
$fld_flags = isset( $prop['flags'] );
$result = $this->getResult();
- $pageSet = $this->getPageSet();
- $titles = $pageSet->getTitles();
- $data = array();
$this->addTables( 'ipblocks' );
$this->addFields( 'ipb_auto' );
- if ( $fld_id )
+ if ( $fld_id ) {
$this->addFields( 'ipb_id' );
- if ( $fld_user )
+ }
+ if ( $fld_user ) {
$this->addFields( array( 'ipb_address', 'ipb_user' ) );
- if ( $fld_by )
- {
+ }
+ if ( $fld_by ) {
$this->addTables( 'user' );
$this->addFields( array( 'ipb_by', 'user_name' ) );
$this->addWhere( 'user_id = ipb_by' );
}
- if ( $fld_timestamp )
+ if ( $fld_timestamp ) {
$this->addFields( 'ipb_timestamp' );
- if ( $fld_expiry )
+ }
+ if ( $fld_expiry ) {
$this->addFields( 'ipb_expiry' );
- if ( $fld_reason )
+ }
+ if ( $fld_reason ) {
$this->addFields( 'ipb_reason' );
- if ( $fld_range )
+ }
+ if ( $fld_range ) {
$this->addFields( array( 'ipb_range_start', 'ipb_range_end' ) );
- if ( $fld_flags )
+ }
+ if ( $fld_flags ) {
$this->addFields( array( 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock', 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ) );
+ }
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$this->addWhereRange( 'ipb_timestamp', $params['dir'], $params['start'], $params['end'] );
- if ( isset( $params['ids'] ) )
+ if ( isset( $params['ids'] ) ) {
$this->addWhereFld( 'ipb_id', $params['ids'] );
- if ( isset( $params['users'] ) )
- {
- foreach ( (array)$params['users'] as $u )
+ }
+ if ( isset( $params['users'] ) ) {
+ foreach ( (array)$params['users'] as $u ) {
$this->prepareUsername( $u );
+ }
$this->addWhereFld( 'ipb_address', $this->usernames );
$this->addWhereFld( 'ipb_auto', 0 );
}
- if ( isset( $params['ip'] ) )
- {
+ if ( isset( $params['ip'] ) ) {
list( $ip, $range ) = IP::parseCIDR( $params['ip'] );
- if ( $ip && $range )
- {
+ if ( $ip && $range ) {
// We got a CIDR range
if ( $range < 16 )
$this->dieUsage( 'CIDR ranges broader than /16 are not accepted', 'cidrtoobroad' );
$lower = wfBaseConvert( $ip, 10, 16, 8, false );
$upper = wfBaseConvert( $ip + pow( 2, 32 - $range ) - 1, 10, 16, 8, false );
- }
- else
+ } else {
$lower = $upper = IP::toHex( $params['ip'] );
+ }
$prefix = substr( $lower, 0, 4 );
-
+
$db = $this->getDB();
$this->addWhere( array(
'ipb_range_start' . $db->buildLike( $prefix, $db->anyString() ),
@@ -121,147 +125,169 @@ class ApiQueryBlocks extends ApiQueryBase {
'ipb_auto' => 0
) );
}
- if ( !$wgUser->isAllowed( 'hideuser' ) )
+
+ if ( !$wgUser->isAllowed( 'hideuser' ) ) {
$this->addWhereFld( 'ipb_deleted', 0 );
+ }
// Purge expired entries on one in every 10 queries
- if ( !mt_rand( 0, 10 ) )
+ if ( !mt_rand( 0, 10 ) ) {
Block::purgeExpired();
+ }
$res = $this->select( __METHOD__ );
$count = 0;
- while ( $row = $res->fetchObject() )
- {
- if ( ++$count > $params['limit'] )
- {
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
// We've had enough
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ) );
break;
}
$block = array();
- if ( $fld_id )
+ if ( $fld_id ) {
$block['id'] = $row->ipb_id;
- if ( $fld_user && !$row->ipb_auto )
+ }
+ if ( $fld_user && !$row->ipb_auto ) {
$block['user'] = $row->ipb_address;
- if ( $fld_by )
+ }
+ if ( $fld_by ) {
$block['by'] = $row->user_name;
- if ( $fld_timestamp )
+ }
+ if ( $fld_timestamp ) {
$block['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
- if ( $fld_expiry )
+ }
+ if ( $fld_expiry ) {
$block['expiry'] = Block::decodeExpiry( $row->ipb_expiry, TS_ISO_8601 );
- if ( $fld_reason )
+ }
+ if ( $fld_reason ) {
$block['reason'] = $row->ipb_reason;
- if ( $fld_range && !$row->ipb_auto )
- {
+ }
+ if ( $fld_range && !$row->ipb_auto ) {
$block['rangestart'] = IP::hexToQuad( $row->ipb_range_start );
$block['rangeend'] = IP::hexToQuad( $row->ipb_range_end );
}
- if ( $fld_flags )
- {
+ if ( $fld_flags ) {
// For clarity, these flags use the same names as their action=block counterparts
- if ( $row->ipb_auto )
+ if ( $row->ipb_auto ) {
$block['automatic'] = '';
- if ( $row->ipb_anon_only )
+ }
+ if ( $row->ipb_anon_only ) {
$block['anononly'] = '';
- if ( $row->ipb_create_account )
+ }
+ if ( $row->ipb_create_account ) {
$block['nocreate'] = '';
- if ( $row->ipb_enable_autoblock )
+ }
+ if ( $row->ipb_enable_autoblock ) {
$block['autoblock'] = '';
- if ( $row->ipb_block_email )
+ }
+ if ( $row->ipb_block_email ) {
$block['noemail'] = '';
- if ( $row->ipb_deleted )
+ }
+ if ( $row->ipb_deleted ) {
$block['hidden'] = '';
- if ( $row->ipb_allow_usertalk )
+ }
+ if ( $row->ipb_allow_usertalk ) {
$block['allowusertalk'] = '';
+ }
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $block );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ) );
break;
}
}
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'block' );
}
-
- protected function prepareUsername( $user )
- {
- if ( !$user )
+
+ protected function prepareUsername( $user ) {
+ if ( !$user ) {
$this->dieUsage( 'User parameter may not be empty', 'param_user' );
+ }
$name = User::isIP( $user )
? $user
: User::getCanonicalName( $user, 'valid' );
- if ( $name === false )
+ if ( $name === false ) {
$this->dieUsage( "User name {$user} is not valid", 'param_user' );
+ }
$this->usernames[] = $name;
}
public function getAllowedParams() {
- return array (
+ return array(
'start' => array(
- ApiBase :: PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'end' => array(
- ApiBase :: PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_TYPE => 'timestamp',
),
'dir' => array(
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_TYPE => array(
'newer',
'older'
),
- ApiBase :: PARAM_DFLT => 'older'
+ ApiBase::PARAM_DFLT => 'older'
),
'ids' => array(
- ApiBase :: PARAM_TYPE => 'integer',
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_ISMULTI => true
),
'users' => array(
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true
),
'ip' => 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
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'prop' => array(
- ApiBase :: PARAM_DFLT => 'id|user|by|timestamp|expiry|reason|flags',
- ApiBase :: PARAM_TYPE => array(
- 'id',
- 'user',
- 'by',
- 'timestamp',
- 'expiry',
- 'reason',
- 'range',
- 'flags'
- ),
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_DFLT => 'id|user|by|timestamp|expiry|reason|flags',
+ ApiBase::PARAM_TYPE => array(
+ 'id',
+ 'user',
+ 'by',
+ 'timestamp',
+ 'expiry',
+ 'reason',
+ 'range',
+ 'flags'
+ ),
+ ApiBase::PARAM_ISMULTI => true
)
);
}
public function getParamDescription() {
- return array (
+ return array(
'start' => 'The timestamp to start enumerating from',
'end' => 'The timestamp to stop enumerating at',
'dir' => 'The direction in which to enumerate',
'ids' => 'Pipe-separated list of block IDs to list (optional)',
'users' => 'Pipe-separated 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 /16 are not accepted.' ),
+ 'Cannot be used together with bkusers. CIDR ranges broader than /16 are not accepted' ),
'limit' => 'The maximum amount of blocks to list',
- 'prop' => 'Which properties to get',
+ 'prop' => array(
+ 'Which properties to get',
+ ' id - Adds the id of the block',
+ ' user - Adds the username of the blocked user',
+ ' by - Adds the username of the blocking admin',
+ ' 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)',
+ ),
);
}
public function getDescription() {
- return 'List all blocked users and IP addresses.';
+ return 'List all blocked users and IP addresses';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'usersandip', 'info' => 'bkusers and bkip cannot be used together' ),
@@ -272,12 +298,13 @@ class ApiQueryBlocks extends ApiQueryBase {
}
protected function getExamples() {
- return array ( 'api.php?action=query&list=blocks',
- 'api.php?action=query&list=blocks&bkusers=Alice|Bob'
+ return array(
+ 'api.php?action=query&list=blocks',
+ 'api.php?action=query&list=blocks&bkusers=Alice|Bob'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryBlocks.php 69578 2010-07-20 02:46:20Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryBlocks.php 73858 2010-09-28 01:21:15Z reedy $';
}
-} \ No newline at end of file
+}
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index 03135052..b2769dc2 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on May 13, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on May 13, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiQueryBase.php" );
+ require_once( "ApiQueryBase.php" );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryCategories extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'cl' );
+ parent::__construct( $query, $moduleName, 'cl' );
}
public function execute() {
@@ -52,52 +53,55 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
}
private function run( $resultPageSet = null ) {
-
- if ( $this->getPageSet()->getGoodTitleCount() == 0 )
+ if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
return; // nothing to do
+ }
$params = $this->extractRequestParams();
$prop = array_flip( (array)$params['prop'] );
$show = array_flip( (array)$params['show'] );
- $this->addFields( array (
+ $this->addFields( array(
'cl_from',
'cl_to'
) );
- $this->addFieldsIf( 'cl_sortkey', isset( $prop['sortkey'] ) );
+ $this->addFieldsIf( array( 'cl_sortkey', 'cl_sortkey_prefix' ), isset( $prop['sortkey'] ) );
$this->addFieldsIf( 'cl_timestamp', isset( $prop['timestamp'] ) );
$this->addTables( 'categorylinks' );
$this->addWhereFld( 'cl_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
- if ( !is_null( $params['categories'] ) )
- {
+ if ( !is_null( $params['categories'] ) ) {
$cats = array();
- foreach ( $params['categories'] as $cat )
- {
+ foreach ( $params['categories'] as $cat ) {
$title = Title::newFromText( $cat );
- if ( !$title || $title->getNamespace() != NS_CATEGORY )
+ if ( !$title || $title->getNamespace() != NS_CATEGORY ) {
$this->setWarning( "``$cat'' is not a category" );
- else
+ } else {
$cats[] = $title->getDBkey();
+ }
}
$this->addWhereFld( 'cl_to', $cats );
}
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 )
+ if ( count( $cont ) != 2 ) {
$this->dieUsage( "Invalid continue param. You should pass the " .
"original value returned by the previous query", "_badcontinue" );
+ }
$clfrom = intval( $cont[0] );
$clto = $this->getDB()->strencode( $this->titleToKey( $cont[1] ) );
- $this->addWhere( "cl_from > $clfrom OR " .
- "(cl_from = $clfrom AND " .
- "cl_to >= '$clto')" );
+ $this->addWhere(
+ "cl_from > $clfrom OR " .
+ "(cl_from = $clfrom AND " .
+ "cl_to >= '$clto')"
+ );
}
- if ( isset( $show['hidden'] ) && isset( $show['!hidden'] ) )
+ if ( isset( $show['hidden'] ) && isset( $show['!hidden'] ) ) {
$this->dieUsageMsg( array( 'show' ) );
+ }
if ( isset( $show['hidden'] ) || isset( $show['!hidden'] ) || isset( $prop['hidden'] ) )
{
$this->addOption( 'STRAIGHT_JOIN' );
@@ -111,26 +115,26 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
'pp_page=page_id',
'pp_propname' => 'hiddencat' ) )
) );
- if ( isset( $show['hidden'] ) )
+ if ( isset( $show['hidden'] ) ) {
$this->addWhere( array( 'pp_propname IS NOT NULL' ) );
- else if ( isset( $show['!hidden'] ) )
+ } elseif ( isset( $show['!hidden'] ) ) {
$this->addWhere( array( 'pp_propname IS NULL' ) );
+ }
}
$this->addOption( 'USE INDEX', array( 'categorylinks' => 'cl_from' ) );
// Don't order by cl_from if it's constant in the WHERE clause
- if ( count( $this->getPageSet()->getGoodTitles() ) == 1 )
+ if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
$this->addOption( 'ORDER BY', 'cl_to' );
- else
+ } else {
$this->addOption( 'ORDER BY', "cl_from, cl_to" );
+ }
- $db = $this->getDB();
$res = $this->select( __METHOD__ );
+ $count = 0;
if ( is_null( $resultPageSet ) ) {
-
- $count = 0;
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
@@ -139,28 +143,30 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
break;
}
- $title = Title :: makeTitle( NS_CATEGORY, $row->cl_to );
+ $title = Title::makeTitle( NS_CATEGORY, $row->cl_to );
$vals = array();
- ApiQueryBase :: addTitleInfo( $vals, $title );
- if ( isset( $prop['sortkey'] ) )
- $vals['sortkey'] = $row->cl_sortkey;
- if ( isset( $prop['timestamp'] ) )
+ ApiQueryBase::addTitleInfo( $vals, $title );
+ if ( isset( $prop['sortkey'] ) ) {
+ $vals['sortkey'] = bin2hex( $row->cl_sortkey );
+ $vals['sortkeyprefix'] = $row->cl_sortkey_prefix;
+ }
+ if ( isset( $prop['timestamp'] ) ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cl_timestamp );
- if ( isset( $prop['hidden'] ) && !is_null( $row->pp_propname ) )
+ }
+ if ( isset( $prop['hidden'] ) && !is_null( $row->pp_propname ) ) {
$vals['hidden'] = '';
+ }
$fit = $this->addPageSubItem( $row->cl_from, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $row->cl_from .
'|' . $this->keyToTitle( $row->cl_to ) );
break;
}
}
} else {
-
$titles = array();
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
@@ -173,44 +179,47 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
}
$resultPageSet->populateFromTitles( $titles );
}
-
- $db->freeResult( $res );
}
public function getAllowedParams() {
- return array (
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array (
'sortkey',
'timestamp',
'hidden',
)
),
'show' => array(
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'hidden',
'!hidden',
)
),
'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
+ 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,
'categories' => array(
- ApiBase :: PARAM_ISMULTI => true,
+ ApiBase::PARAM_ISMULTI => true,
),
);
}
public function getParamDescription() {
- return array (
- 'prop' => 'Which additional properties to get for each category.',
+ 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',
@@ -221,7 +230,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
public function getDescription() {
return 'List all categories the page(s) belong to';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'show' ),
@@ -229,15 +238,15 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
- "Get a list of categories [[Albert Einstein]] belongs to:",
- " api.php?action=query&prop=categories&titles=Albert%20Einstein",
- "Get information about all categories used in the [[Albert Einstein]]:",
- " api.php?action=query&generator=categories&titles=Albert%20Einstein&prop=info"
- );
+ return array(
+ 'Get a list of categories [[Albert Einstein]] belongs to:',
+ ' api.php?action=query&prop=categories&titles=Albert%20Einstein',
+ 'Get information about all categories used in the [[Albert Einstein]]:',
+ ' api.php?action=query&generator=categories&titles=Albert%20Einstein&prop=info'
+ );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryCategories.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryCategories.php 86474 2011-04-20 13:22:05Z catrope $';
}
}
diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php
index 4df2f181..d4b64025 100644
--- a/includes/api/ApiQueryCategoryInfo.php
+++ b/includes/api/ApiQueryCategoryInfo.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on May 13, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on May 13, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiQueryBase.php" );
+ require_once( "ApiQueryBase.php" );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryCategoryInfo extends ApiQueryBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'ci' );
+ parent::__construct( $query, $moduleName, 'ci' );
}
public function execute() {
@@ -50,8 +51,7 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
$titles = $this->getPageSet()->getGoodTitles() +
$this->getPageSet()->getMissingTitles();
$cattitles = array();
- foreach ( $categories as $c )
- {
+ foreach ( $categories as $c ) {
$t = $titles[$c];
$cattitles[$c] = $t->getDBkey();
}
@@ -69,34 +69,30 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
$this->addFields( array( 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files', 'pp_propname AS cat_hidden' ) );
$this->addWhere( array( 'cat_title' => $cattitles ) );
- if ( !is_null( $params['continue'] ) )
- {
+ if ( !is_null( $params['continue'] ) ) {
$title = $this->getDB()->addQuotes( $params['continue'] );
$this->addWhere( "cat_title >= $title" );
}
$this->addOption( 'ORDER BY', 'cat_title' );
- $db = $this->getDB();
$res = $this->select( __METHOD__ );
$catids = array_flip( $cattitles );
- while ( $row = $db->fetchObject( $res ) )
- {
+ foreach ( $res as $row ) {
$vals = array();
$vals['size'] = intval( $row->cat_pages );
$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 )
+ if ( $row->cat_hidden ) {
$vals['hidden'] = '';
+ }
$fit = $this->addPageSubItems( $catids[$row->cat_title], $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $row->cat_title );
break;
}
}
- $db->freeResult( $res );
}
public function getCacheMode( $params ) {
@@ -104,13 +100,13 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'continue' => null,
);
}
public function getParamDescription() {
- return array (
+ return array(
'continue' => 'When more results are available, use this to continue',
);
}
@@ -120,10 +116,10 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
}
protected function getExamples() {
- return "api.php?action=query&prop=categoryinfo&titles=Category:Foo|Category:Bar";
+ return 'api.php?action=query&prop=categoryinfo&titles=Category:Foo|Category:Bar';
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index 107f5049..bbcf8b9b 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on June 14, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on June 14, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiQueryBase.php" );
+ require_once( "ApiQueryBase.php" );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'cm' );
+ parent::__construct( $query, $moduleName, 'cm' );
}
public function execute() {
@@ -52,42 +53,40 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
private function run( $resultPageSet = null ) {
-
$params = $this->extractRequestParams();
- if ( !isset( $params['title'] ) || is_null( $params['title'] ) )
- $this->dieUsage( "The cmtitle parameter is required", 'notitle' );
$categoryTitle = Title::newFromText( $params['title'] );
- if ( is_null( $categoryTitle ) || $categoryTitle->getNamespace() != NS_CATEGORY )
- $this->dieUsage( "The category name you entered is not valid", 'invalidcategory' );
+ if ( is_null( $categoryTitle ) || $categoryTitle->getNamespace() != NS_CATEGORY ) {
+ $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' );
+ }
$prop = array_flip( $params['prop'] );
$fld_ids = isset( $prop['ids'] );
$fld_title = isset( $prop['title'] );
$fld_sortkey = isset( $prop['sortkey'] );
+ $fld_sortkeyprefix = isset( $prop['sortkeyprefix'] );
$fld_timestamp = isset( $prop['timestamp'] );
+ $fld_type = isset( $prop['type'] );
if ( is_null( $resultPageSet ) ) {
- $this->addFields( array( 'cl_from', 'cl_sortkey', 'page_namespace', 'page_title' ) );
+ $this->addFields( array( 'cl_from', 'cl_sortkey', 'cl_type', 'page_namespace', 'page_title' ) );
$this->addFieldsIf( 'page_id', $fld_ids );
+ $this->addFieldsIf( 'cl_sortkey_prefix', $fld_sortkeyprefix );
} else {
$this->addFields( $resultPageSet->getPageTableFields() ); // will include page_ id, ns, title
- $this->addFields( array( 'cl_from', 'cl_sortkey' ) );
+ $this->addFields( array( 'cl_from', 'cl_sortkey', 'cl_type' ) );
}
$this->addFieldsIf( 'cl_timestamp', $fld_timestamp || $params['sort'] == 'timestamp' );
+
$this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX'
- // Not needed after bug 10280 is applied to servers
- if ( $params['sort'] == 'timestamp' )
- $this->addOption( 'USE INDEX', 'cl_timestamp' );
- else
- $this->addOption( 'USE INDEX', 'cl_sortkey' );
- $this->addWhere( 'cl_from=page_id' );
- $this->setContinuation( $params['continue'], $params['dir'] );
$this->addWhereFld( 'cl_to', $categoryTitle->getDBkey() );
- // Scanning large datasets for rare categories sucks, and I already told
+ $queryTypes = $params['type'];
+ $contWhere = false;
+
+ // Scanning large datasets for rare categories sucks, and I already told
// how to have efficient subcategory access :-) ~~~~ (oh well, domas)
global $wgMiserMode;
$miser_ns = array();
@@ -96,69 +95,146 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
} else {
$this->addWhereFld( 'page_namespace', $params['namespace'] );
}
- if ( $params['sort'] == 'timestamp' )
- $this->addWhereRange( 'cl_timestamp', ( $params['dir'] == 'asc' ? 'newer' : 'older' ), $params['start'], $params['end'] );
- else
- {
- $this->addWhereRange( 'cl_sortkey', ( $params['dir'] == 'asc' ? 'newer' : 'older' ), $params['startsortkey'], $params['endsortkey'] );
- $this->addWhereRange( 'cl_from', ( $params['dir'] == 'asc' ? 'newer' : 'older' ), null, null );
+
+ $dir = $params['dir'] == 'asc' ? 'newer' : 'older';
+
+ if ( $params['sort'] == 'timestamp' ) {
+ $this->addWhereRange( 'cl_timestamp',
+ $dir,
+ $params['start'],
+ $params['end'] );
+
+ $this->addOption( 'USE INDEX', 'cl_timestamp' );
+ } else {
+ if ( $params['continue'] ) {
+ $cont = explode( '|', $params['continue'], 3 );
+ if ( count( $cont ) != 3 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the original value returned '.
+ 'by the previous query', '_badcontinue'
+ );
+ }
+
+ // Remove the types to skip from $queryTypes
+ $contTypeIndex = array_search( $cont[0], $queryTypes );
+ $queryTypes = array_slice( $queryTypes, $contTypeIndex );
+
+ // Add a WHERE clause for sortkey and from
+ // pack( "H*", $foo ) is used to convert hex back to binary
+ $escSortkey = $this->getDB()->addQuotes( pack( "H*", $cont[1] ) );
+ $from = intval( $cont[2] );
+ $op = $dir == 'newer' ? '>' : '<';
+ // $contWhere is used further down
+ $contWhere = "cl_sortkey $op $escSortkey OR " .
+ "(cl_sortkey = $escSortkey AND " .
+ "cl_from $op= $from)";
+
+ } else {
+ // The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them
+ $this->addWhereRange( 'cl_sortkey',
+ $dir,
+ $params['startsortkey'],
+ $params['endsortkey'] );
+ $this->addWhereRange( 'cl_from', $dir, null, null );
+ }
+ $this->addOption( 'USE INDEX', 'cl_sortkey' );
}
+ $this->addWhere( 'cl_from=page_id' );
+
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
- $db = $this->getDB();
-
- $data = array ();
+ if ( $params['sort'] == 'sortkey' ) {
+ // Run a separate SELECT query for each value of cl_type.
+ // This is needed because cl_type is an enum, and MySQL has
+ // inconsistencies between ORDER BY cl_type and
+ // WHERE cl_type >= 'foo' making proper paging impossible
+ // and unindexed.
+ $rows = array();
+ $first = true;
+ foreach ( $queryTypes as $type ) {
+ $extraConds = array( 'cl_type' => $type );
+ if ( $first && $contWhere ) {
+ // Continuation condition. Only added to the
+ // first query, otherwise we'll skip things
+ $extraConds[] = $contWhere;
+ }
+ $res = $this->select( __METHOD__, array( 'where' => $extraConds ) );
+ $rows = array_merge( $rows, iterator_to_array( $res ) );
+ if ( count( $rows ) >= $limit + 1 ) {
+ break;
+ }
+ $first = false;
+ }
+ } else {
+ // Sorting by timestamp
+ // No need to worry about per-type queries because we
+ // aren't sorting or filtering by type anyway
+ $res = $this->select( __METHOD__ );
+ $rows = iterator_to_array( $res );
+ }
$count = 0;
- $lastSortKey = null;
- $res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $rows as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
// TODO: Security issue - if the user has no right to view next title, it will still be shown
- if ( $params['sort'] == 'timestamp' )
+ if ( $params['sort'] == 'timestamp' ) {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) );
- else
- $this->setContinueEnumParameter( 'continue', $this->getContinueStr( $row, $lastSortKey ) );
+ } else {
+ $sortkey = bin2hex( $row->cl_sortkey );
+ $this->setContinueEnumParameter( 'continue',
+ "{$row->cl_type}|$sortkey|{$row->cl_from}"
+ );
+ }
break;
}
- // Since domas won't tell anyone what he told long ago, apply
- // cmnamespace here. This means the query may return 0 actual
- // results, but on the other hand it could save returning 5000
+ // Since domas won't tell anyone what he told long ago, apply
+ // cmnamespace here. This means the query may return 0 actual
+ // results, but on the other hand it could save returning 5000
// useless results to the client. ~~~~
- if ( count( $miser_ns ) && !in_array( $row->page_namespace, $miser_ns ) )
+ if ( count( $miser_ns ) && !in_array( $row->page_namespace, $miser_ns ) ) {
continue;
+ }
if ( is_null( $resultPageSet ) ) {
$vals = array();
- if ( $fld_ids )
+ if ( $fld_ids ) {
$vals['pageid'] = intval( $row->page_id );
+ }
if ( $fld_title ) {
- $title = Title :: makeTitle( $row->page_namespace, $row->page_title );
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
ApiQueryBase::addTitleInfo( $vals, $title );
}
- if ( $fld_sortkey )
- $vals['sortkey'] = $row->cl_sortkey;
- if ( $fld_timestamp )
+ if ( $fld_sortkey ) {
+ $vals['sortkey'] = bin2hex( $row->cl_sortkey );
+ }
+ if ( $fld_sortkeyprefix ) {
+ $vals['sortkeyprefix'] = $row->cl_sortkey_prefix;
+ }
+ if ( $fld_type ) {
+ $vals['type'] = $row->cl_type;
+ }
+ if ( $fld_timestamp ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cl_timestamp );
+ }
$fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ),
null, $vals );
- if ( !$fit )
- {
- if ( $params['sort'] == 'timestamp' )
+ if ( !$fit ) {
+ if ( $params['sort'] == 'timestamp' ) {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) );
- else
- $this->setContinueEnumParameter( 'continue', $this->getContinueStr( $row, $lastSortKey ) );
+ } else {
+ $sortkey = bin2hex( $row->cl_sortkey );
+ $this->setContinueEnumParameter( 'continue',
+ "{$row->cl_type}|$sortkey|{$row->cl_from}"
+ );
+ }
break;
}
} else {
$resultPageSet->processDbRow( $row );
}
- $lastSortKey = $row->cl_sortkey; // detect duplicate sortkeys
}
- $db->freeResult( $res );
if ( is_null( $resultPageSet ) ) {
$this->getResult()->setIndexedTagName_internal(
@@ -166,85 +242,64 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
}
- private function getContinueStr( $row, $lastSortKey ) {
- $ret = $row->cl_sortkey . '|';
- if ( $row->cl_sortkey == $lastSortKey ) // duplicate sort key, add cl_from
- $ret .= $row->cl_from;
- return $ret;
- }
-
- /**
- * Add DB WHERE clause to continue previous query based on 'continue' parameter
- */
- private function setContinuation( $continue, $dir ) {
- if ( is_null( $continue ) )
- return; // This is not a continuation request
-
- $pos = strrpos( $continue, '|' );
- $sortkey = substr( $continue, 0, $pos );
- $fromstr = substr( $continue, $pos + 1 );
- $from = intval( $fromstr );
-
- if ( $from == 0 && strlen( $fromstr ) > 0 )
- $this->dieUsage( "Invalid continue param. You should pass the original value returned by the previous query", "badcontinue" );
-
- $encSortKey = $this->getDB()->addQuotes( $sortkey );
- $encFrom = $this->getDB()->addQuotes( $from );
-
- $op = ( $dir == 'desc' ? '<' : '>' );
-
- if ( $from != 0 ) {
- // Duplicate sort key continue
- $this->addWhere( "cl_sortkey$op$encSortKey OR (cl_sortkey=$encSortKey AND cl_from$op=$encFrom)" );
- } else {
- $this->addWhere( "cl_sortkey$op=$encSortKey" );
- }
- }
-
public function getAllowedParams() {
- return array (
- 'title' => null,
- 'prop' => array (
- ApiBase :: PARAM_DFLT => 'ids|title',
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ return array(
+ 'title' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_DFLT => 'ids|title',
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array (
'ids',
'title',
'sortkey',
+ 'sortkeyprefix',
+ 'type',
'timestamp',
)
),
'namespace' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ),
+ 'type' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'page|subcat|file',
+ ApiBase::PARAM_TYPE => array(
+ 'page',
+ 'subcat',
+ 'file'
+ )
),
'continue' => null,
- 'limit' => array (
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_DFLT => 10,
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
- ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ 'limit' => array(
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'sort' => array(
- ApiBase :: PARAM_DFLT => 'sortkey',
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_DFLT => 'sortkey',
+ ApiBase::PARAM_TYPE => array(
'sortkey',
'timestamp'
)
),
'dir' => array(
- ApiBase :: PARAM_DFLT => 'asc',
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_DFLT => 'asc',
+ ApiBase::PARAM_TYPE => array(
'asc',
'desc'
)
),
'start' => array(
- ApiBase :: PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'end' => array(
- ApiBase :: PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'startsortkey' => null,
'endsortkey' => null,
@@ -253,16 +308,26 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
public function getParamDescription() {
global $wgMiserMode;
- $desc = array (
+ $p = $this->getModulePrefix();
+ $desc = array(
'title' => 'Which category to enumerate (required). Must include Category: prefix',
- 'prop' => 'What pieces of information to include',
+ '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 cmsort=timestamp',
- 'end' => 'Timestamp to end listing at. Can only be used with cmsort=timestamp',
- 'startsortkey' => 'Sortkey to start listing from. Can only be used with cmsort=sortkey',
- 'endsortkey' => 'Sortkey to end listing at. Can only be used with cmsort=sortkey',
+ 'start' => "Timestamp to start listing from. Can only be used with {$p}sort=timestamp",
+ 'end' => "Timestamp to end listing at. Can only be used with {$p}sort=timestamp",
+ 'startsortkey' => "Sortkey to start listing from. Can only be used with {$p}sort=sortkey",
+ 'endsortkey' => "Sortkey to end listing at. Can only be used with {$p}sort=sortkey",
'continue' => 'For large categories, give the value retured from previous query',
'limit' => 'The maximum number of pages to return.',
);
@@ -271,6 +336,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$desc['namespace'],
'NOTE: Due to $wgMiserMode, using this may result in fewer than "limit" results',
'returned before continuing; in extreme cases, zero results may be returned.',
+ 'Note that you can use cmtype=subcat or cmtype=file instead of cmnamespace=14 or 6.',
);
}
return $desc;
@@ -279,7 +345,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
public function getDescription() {
return 'List all pages in a given category';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'notitle', 'info' => 'The cmtitle parameter is required' ),
@@ -289,15 +355,15 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
- "Get first 10 pages in [[Category:Physics]]:",
- " api.php?action=query&list=categorymembers&cmtitle=Category:Physics",
- "Get page info about first 10 pages in [[Category:Physics]]:",
- " api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info",
- );
+ return array(
+ 'Get first 10 pages in [[Category:Physics]]:',
+ ' api.php?action=query&list=categorymembers&cmtitle=Category:Physics',
+ 'Get page info about first 10 pages in [[Category:Physics]]:',
+ ' api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info',
+ );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 86474 2011-04-20 13:22:05Z catrope $';
}
}
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index b26c7051..523862c0 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Jul 2, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Jul 2, 2007
+ *
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,101 +18,115 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
- * Query module to enumerate all available pages.
+ * Query module to enumerate all deleted revisions.
*
* @ingroup API
*/
class ApiQueryDeletedrevs extends ApiQueryBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'dr' );
+ parent::__construct( $query, $moduleName, 'dr' );
}
public function execute() {
-
global $wgUser;
// Before doing anything at all, let's check permissions
- if ( !$wgUser->isAllowed( 'deletedhistory' ) )
+ if ( !$wgUser->isAllowed( 'deletedhistory' ) ) {
$this->dieUsage( 'You don\'t have permission to view deleted revision information', 'permissiondenied' );
+ }
$db = $this->getDB();
$params = $this->extractRequestParams( false );
$prop = array_flip( $params['prop'] );
$fld_revid = isset( $prop['revid'] );
$fld_user = isset( $prop['user'] );
+ $fld_userid = isset( $prop['userid'] );
$fld_comment = isset( $prop['comment'] );
$fld_parsedcomment = isset ( $prop['parsedcomment'] );
$fld_minor = isset( $prop['minor'] );
$fld_len = isset( $prop['len'] );
$fld_content = isset( $prop['content'] );
$fld_token = isset( $prop['token'] );
-
+
$result = $this->getResult();
$pageSet = $this->getPageSet();
$titles = $pageSet->getTitles();
- $data = array();
-
+
// This module operates in three modes:
// 'revs': List deleted revs for certain titles
// 'user': List deleted revs by a certain user
// 'all': List all deleted revs
$mode = 'all';
- if ( count( $titles ) > 0 )
+ if ( count( $titles ) > 0 ) {
$mode = 'revs';
- else if ( !is_null( $params['user'] ) )
+ } elseif ( !is_null( $params['user'] ) ) {
$mode = 'user';
-
- if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) )
+ }
+
+ if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
$this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
+ }
$this->addTables( 'archive' );
$this->addWhere( 'ar_deleted = 0' );
$this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp' ) );
- if ( $fld_revid )
+ if ( $fld_revid ) {
$this->addFields( 'ar_rev_id' );
- if ( $fld_user )
+ }
+ if ( $fld_user ) {
$this->addFields( 'ar_user_text' );
- if ( $fld_comment || $fld_parsedcomment )
+ }
+ if ( $fld_userid ) {
+ $this->addFields( 'ar_user' );
+ }
+ if ( $fld_comment || $fld_parsedcomment ) {
$this->addFields( 'ar_comment' );
- if ( $fld_minor )
+ }
+ if ( $fld_minor ) {
$this->addFields( 'ar_minor_edit' );
- if ( $fld_len )
+ }
+ if ( $fld_len ) {
$this->addFields( 'ar_len' );
+ }
if ( $fld_content ) {
$this->addTables( 'text' );
$this->addFields( array( 'ar_text', 'ar_text_id', 'old_text', 'old_flags' ) );
$this->addWhere( 'ar_text_id = old_id' );
// This also means stricter restrictions
- if ( !$wgUser->isAllowed( 'undelete' ) )
+ if ( !$wgUser->isAllowed( 'undelete' ) ) {
$this->dieUsage( 'You don\'t have permission to view deleted revision content', 'permissiondenied' );
+ }
}
// Check limits
- $userMax = $fld_content ? ApiBase :: LIMIT_SML1 : ApiBase :: LIMIT_BIG1;
- $botMax = $fld_content ? ApiBase :: LIMIT_SML2 : ApiBase :: LIMIT_BIG2;
+ $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1;
+ $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2;
$limit = $params['limit'];
if ( $limit == 'max' ) {
$limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
- $this->getResult()->addValue( 'limits', $this->getModuleName(), $limit );
+ $this->getResult()->setParsedLimit( $this->getModuleName(), $limit );
}
$this->validateLimit( 'limit', $limit, 1, $userMax, $botMax );
- if ( $fld_token )
+ if ( $fld_token ) {
// Undelete tokens are identical for all pages, so we cache one here
$token = $wgUser->editToken();
+ }
// We need a custom WHERE clause that matches all titles.
if ( $mode == 'revs' ) {
@@ -122,25 +135,25 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addWhere( $where );
} elseif ( $mode == 'all' ) {
$this->addWhereFld( 'ar_namespace', $params['namespace'] );
- if ( !is_null( $params['from'] ) )
- {
+ if ( !is_null( $params['from'] ) ) {
$from = $this->getDB()->strencode( $this->titleToKey( $params['from'] ) );
$this->addWhere( "ar_title >= '$from'" );
}
}
-
+
if ( !is_null( $params['user'] ) ) {
$this->addWhereFld( 'ar_user_text', $params['user'] );
} elseif ( !is_null( $params['excludeuser'] ) ) {
$this->addWhere( 'ar_user_text != ' .
$this->getDB()->addQuotes( $params['excludeuser'] ) );
}
-
+
if ( !is_null( $params['continue'] ) && ( $mode == 'all' || $mode == 'revs' ) )
{
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 )
- $this->dieUsage( "Invalid continue param. You should pass the original value returned by the previous query", "badcontinue" );
+ if ( count( $cont ) != 3 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', 'badcontinue' );
+ }
$ns = intval( $cont[0] );
$title = $this->getDB()->strencode( $this->titleToKey( $cont[1] ) );
$ts = $this->getDB()->strencode( $cont[2] );
@@ -155,15 +168,14 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addOption( 'LIMIT', $limit + 1 );
$this->addOption( 'USE INDEX', array( 'archive' => ( $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp' ) ) );
if ( $mode == 'all' ) {
- if ( $params['unique'] )
- {
+ if ( $params['unique'] ) {
$this->addOption( 'GROUP BY', 'ar_title' );
$this->addOption( 'ORDER BY', 'ar_title' );
- } else
+ } else {
$this->addOption( 'ORDER BY', 'ar_title, ar_timestamp' );
+ }
} else {
- if ( $mode == 'revs' )
- {
+ if ( $mode == 'revs' ) {
// Sort by ns and title in the same order as timestamp for efficiency
$this->addWhereRange( 'ar_namespace', $params['dir'], null, null );
$this->addWhereRange( 'ar_title', $params['dir'], null, null );
@@ -174,39 +186,47 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$pageMap = array(); // Maps ns&title to (fake) pageid
$count = 0;
$newPageID = 0;
- while ( $row = $db->fetchObject( $res ) )
- {
+ foreach ( $res as $row ) {
if ( ++$count > $limit ) {
// We've had enough
- if ( $mode == 'all' || $mode == 'revs' )
+ if ( $mode == 'all' || $mode == 'revs' ) {
$this->setContinueEnumParameter( 'continue', intval( $row->ar_namespace ) . '|' .
$this->keyToTitle( $row->ar_title ) . '|' . $row->ar_timestamp );
- else
+ } else {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ar_timestamp ) );
+ }
break;
}
$rev = array();
$rev['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ar_timestamp );
- if ( $fld_revid )
+ if ( $fld_revid ) {
$rev['revid'] = intval( $row->ar_rev_id );
- if ( $fld_user )
+ }
+ if ( $fld_user ) {
$rev['user'] = $row->ar_user_text;
- if ( $fld_comment )
+ }
+ if ( $fld_userid ) {
+ $rev['userid'] = $row->ar_user;
+ }
+ if ( $fld_comment ) {
$rev['comment'] = $row->ar_comment;
+ }
$title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
if ( $fld_parsedcomment ) {
- global $wgUser;
$rev['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->ar_comment, $title );
}
- if ( $fld_minor && $row->ar_minor_edit == 1 )
+ if ( $fld_minor && $row->ar_minor_edit == 1 ) {
$rev['minor'] = '';
- if ( $fld_len )
+ }
+ if ( $fld_len ) {
$rev['len'] = $row->ar_len;
- if ( $fld_content )
+ }
+ if ( $fld_content ) {
ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
+ }
if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
$pageID = $newPageID++;
@@ -214,8 +234,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$a['revisions'] = array( $rev );
$result->setIndexedTagName( $a['revisions'], 'rev' );
ApiQueryBase::addTitleInfo( $a, $title );
- if ( $fld_token )
+ if ( $fld_token ) {
$a['token'] = $token;
+ }
$fit = $result->addValue( array( 'query', $this->getModuleName() ), $pageID, $a );
} else {
$pageID = $pageMap[$row->ar_namespace][$row->ar_title];
@@ -224,58 +245,59 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
null, $rev );
}
if ( !$fit ) {
- if ( $mode == 'all' || $mode == 'revs' )
+ if ( $mode == 'all' || $mode == 'revs' ) {
$this->setContinueEnumParameter( 'continue', intval( $row->ar_namespace ) . '|' .
$this->keyToTitle( $row->ar_title ) . '|' . $row->ar_timestamp );
- else
+ } else {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ar_timestamp ) );
+ }
break;
}
}
- $db->freeResult( $res );
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
}
public function getAllowedParams() {
- return array (
+ return array(
'start' => array(
- ApiBase :: PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'end' => array(
- ApiBase :: PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_TYPE => 'timestamp',
),
'dir' => array(
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_TYPE => array(
'newer',
'older'
),
- ApiBase :: PARAM_DFLT => 'older'
+ ApiBase::PARAM_DFLT => 'older'
),
'from' => null,
'continue' => null,
'unique' => false,
'user' => array(
- ApiBase :: PARAM_TYPE => 'user'
+ ApiBase::PARAM_TYPE => 'user'
),
'excludeuser' => array(
- ApiBase :: PARAM_TYPE => 'user'
+ ApiBase::PARAM_TYPE => 'user'
),
'namespace' => array(
- ApiBase :: PARAM_TYPE => 'namespace',
- ApiBase :: PARAM_DFLT => 0,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_DFLT => 0,
),
'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
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'prop' => array(
- ApiBase :: PARAM_DFLT => 'user|comment',
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_DFLT => 'user|comment',
+ ApiBase::PARAM_TYPE => array(
'revid',
'user',
+ 'userid',
'comment',
'parsedcomment',
'minor',
@@ -283,18 +305,29 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'content',
'token'
),
- ApiBase :: PARAM_ISMULTI => true
+ 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' => 'The direction in which to enumerate. (1,2)',
+ return array(
+ 'start' => 'The timestamp to start enumerating from (1,2)',
+ 'end' => 'The timestamp to stop enumerating at (1,2)',
+ 'dir' => 'The direction in which to enumerate (1,2)',
'limit' => 'The maximum amount of revisions to list',
- 'prop' => 'Which properties to get',
+ 'prop' => array(
+ 'Which properties to get',
+ ' revid - Adds the revision id of the deleted revision',
+ ' 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 of the revision',
+ ' content - Adds the content of the revision',
+ ' token - Gives the edit token',
+ ),
'namespace' => 'Only list pages in this namespace (3)',
'user' => 'Only list revisions by this user',
'excludeuser' => 'Don\'t list revisions by this user',
@@ -305,16 +338,17 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
public function getDescription() {
- return array( 'List deleted revisions.',
- 'This module 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, druser 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.',
+ return array(
+ 'List deleted revisions.',
+ 'This module 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, druser 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 getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revision information' ),
@@ -325,7 +359,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
protected function getExamples() {
- return array (
+ return array(
'List the last deleted revisions of Main Page and Talk:Main Page, with content (mode 1):',
' api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content',
'List the last 50 deleted contributions by Bob (mode 2):',
@@ -333,11 +367,11 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'List the first 50 deleted revisions in the main namespace (mode 3):',
' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50',
'List the first 50 deleted pages in the Talk namespace (mode 3):',
- ' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique',
+ ' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique=',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 69578 2010-07-20 02:46:20Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 77192 2010-11-23 22:05:27Z btongminh $';
}
-} \ No newline at end of file
+}
diff --git a/includes/api/ApiQueryDisabled.php b/includes/api/ApiQueryDisabled.php
index 4bd3f5fd..b5712069 100644
--- a/includes/api/ApiQueryDisabled.php
+++ b/includes/api/ApiQueryDisabled.php
@@ -1,10 +1,10 @@
<?php
-
-/*
- * Created on Sep 25, 2008
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Sep 25, 2008
+ *
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,18 +18,19 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
-
/**
- * API module that does nothing
+ * API module that does nothing
*
* Use this to disable core modules with e.g.
* $wgAPIPropModules['modulename'] = 'ApiQueryDisabled';
@@ -41,7 +42,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryDisabled extends ApiQueryBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
@@ -49,11 +50,11 @@ class ApiQueryDisabled extends ApiQueryBase {
}
public function getAllowedParams() {
- return array ();
+ return array();
}
public function getParamDescription() {
- return array ();
+ return array();
}
public function getDescription() {
@@ -63,10 +64,10 @@ class ApiQueryDisabled extends ApiQueryBase {
}
protected function getExamples() {
- return array ();
+ return array();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryDisabled.php 60930 2010-01-11 15:55:52Z simetrical $';
+ return __CLASS__ . ': $Id: ApiQueryDisabled.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index ed070069..ffe98038 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 27, 2008
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Roan Kattow <Firstname>,<Lastname>@home.nl
+ * Created on Sep 27, 2008
+ *
+ * Copyright © 2008 Roan Kattow <Firstname>,<Lastname>@home.nl
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiQueryBase.php" );
+ require_once( "ApiQueryBase.php" );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'df' );
+ parent::__construct( $query, $moduleName, 'df' );
}
public function execute() {
@@ -58,7 +59,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
return;
}
$images = $namespaces[NS_FILE];
-
+
$this->addTables( 'image', 'i1' );
$this->addTables( 'image', 'i2' );
$this->addFields( array(
@@ -74,30 +75,29 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
'i1.img_name != i2.img_name',
) );
- if ( isset( $params['continue'] ) )
- {
+ if ( isset( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 )
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
+ if ( count( $cont ) != 2 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the ' .
+ 'original value returned by the previous query', '_badcontinue' );
+ }
$orig = $this->getDB()->strencode( $this->titleTokey( $cont[0] ) );
$dup = $this->getDB()->strencode( $this->titleToKey( $cont[1] ) );
- $this->addWhere( "i1.img_name > '$orig' OR " .
- "(i1.img_name = '$orig' AND " .
- "i2.img_name >= '$dup')" );
+ $this->addWhere(
+ "i1.img_name > '$orig' OR " .
+ "(i1.img_name = '$orig' AND " .
+ "i2.img_name >= '$dup')"
+ );
}
$this->addOption( 'ORDER BY', 'i1.img_name' );
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$res = $this->select( __METHOD__ );
- $db = $this->getDB();
$count = 0;
$titles = array();
- while ( $row = $db->fetchObject( $res ) )
- {
- if ( ++$count > $params['limit'] )
- {
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'continue',
@@ -105,18 +105,16 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$this->keyToTitle( $row->dup_name ) );
break;
}
- if ( !is_null( $resultPageSet ) )
+ if ( !is_null( $resultPageSet ) ) {
$titles[] = Title::makeTitle( NS_FILE, $row->dup_name );
- else
- {
+ } else {
$r = array(
'name' => $row->dup_name,
'user' => $row->dup_user_text,
'timestamp' => wfTimestamp( TS_ISO_8601, $row->dup_timestamp )
);
$fit = $this->addPageSubItem( $images[$row->orig_name], $r );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'continue',
$this->keyToTitle( $row->orig_name ) . '|' .
$this->keyToTitle( $row->dup_name ) );
@@ -124,35 +122,35 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
}
}
}
- if ( !is_null( $resultPageSet ) )
+ if ( !is_null( $resultPageSet ) ) {
$resultPageSet->populateFromTitles( $titles );
- $db->freeResult( $res );
+ }
}
public function getAllowedParams() {
- return array (
+ 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
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'continue' => null,
);
}
public function getParamDescription() {
- return array (
+ return array(
'limit' => 'How many files to return',
'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
- return 'List all files that are duplicates of the given file(s).';
+ return 'List all files that are duplicates of the given file(s)';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
@@ -160,12 +158,13 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array ( 'api.php?action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles',
- 'api.php?action=query&generator=allimages&prop=duplicatefiles',
- );
+ return array(
+ 'api.php?action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles',
+ 'api.php?action=query&generator=allimages&prop=duplicatefiles',
+ );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index 0e171e44..ecd9e699 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on July 7, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on July 7, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -34,7 +35,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'eu' );
+ parent::__construct( $query, $moduleName, 'eu' );
}
public function execute() {
@@ -50,7 +51,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
private function run( $resultPageSet = null ) {
-
$params = $this->extractRequestParams();
$protocol = $params['protocol'];
@@ -58,17 +58,16 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
// Find the right prefix
global $wgUrlProtocols;
- if ( $protocol && !in_array( $protocol, $wgUrlProtocols ) )
- {
+ if ( $protocol && !in_array( $protocol, $wgUrlProtocols ) ) {
foreach ( $wgUrlProtocols as $p ) {
if ( substr( $p, 0, strlen( $protocol ) ) === $protocol ) {
$protocol = $p;
break;
}
}
- }
- else
+ } else {
$protocol = null;
+ }
$db = $this->getDB();
$this->addTables( array( 'page', 'externallinks' ) ); // must be in this order for 'USE INDEX'
@@ -76,20 +75,21 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$this->addWhere( 'page_id=el_from' );
$this->addWhereFld( 'page_namespace', $params['namespace'] );
- if ( !is_null( $query ) || $query != '' )
- {
- if ( is_null( $protocol ) )
+ if ( !is_null( $query ) || $query != '' ) {
+ if ( is_null( $protocol ) ) {
$protocol = 'http://';
+ }
$likeQuery = LinkFilter::makeLikeArray( $query, $protocol );
- if ( !$likeQuery )
+ if ( !$likeQuery ) {
$this->dieUsage( 'Invalid query', 'bad_query' );
+ }
$likeQuery = LinkFilter::keepOneWildcard( $likeQuery );
$this->addWhere( 'el_index ' . $db->buildLike( $likeQuery ) );
- }
- else if ( !is_null( $protocol ) )
+ } elseif ( !is_null( $protocol ) ) {
$this->addWhere( 'el_index ' . $db->buildLike( "$protocol", $db->anyString() ) );
+ }
$prop = array_flip( $params['prop'] );
$fld_ids = isset( $prop['ids'] );
@@ -97,7 +97,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$fld_url = isset( $prop['url'] );
if ( is_null( $resultPageSet ) ) {
- $this->addFields( array (
+ $this->addFields( array(
'page_id',
'page_namespace',
'page_title'
@@ -110,14 +110,15 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$limit = $params['limit'];
$offset = $params['offset'];
$this->addOption( 'LIMIT', $limit + 1 );
- if ( isset ( $offset ) )
+ if ( isset( $offset ) ) {
$this->addOption( 'OFFSET', $offset );
+ }
$res = $this->select( __METHOD__ );
$result = $this->getResult();
$count = 0;
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'offset', $offset + $limit );
@@ -126,17 +127,18 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
if ( is_null( $resultPageSet ) ) {
$vals = array();
- if ( $fld_ids )
+ if ( $fld_ids ) {
$vals['pageid'] = intval( $row->page_id );
+ }
if ( $fld_title ) {
- $title = Title :: makeTitle( $row->page_namespace, $row->page_title );
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
ApiQueryBase::addTitleInfo( $vals, $title );
}
- if ( $fld_url )
+ if ( $fld_url ) {
$vals['url'] = $row->el_to;
+ }
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'offset', $offset + $count - 1 );
break;
}
@@ -144,7 +146,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$resultPageSet->processDbRow( $row );
}
}
- $db->freeResult( $res );
if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ),
@@ -159,44 +160,52 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$protocols[] = substr( $p, 0, strpos( $p, ':' ) );
}
- return array (
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_DFLT => 'ids|title|url',
- ApiBase :: PARAM_TYPE => array (
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'ids|title|url',
+ ApiBase::PARAM_TYPE => array(
'ids',
'title',
'url'
)
),
- 'offset' => array (
- ApiBase :: PARAM_TYPE => 'integer'
+ 'offset' => array(
+ ApiBase::PARAM_TYPE => 'integer'
),
- 'protocol' => array (
- ApiBase :: PARAM_TYPE => $protocols,
- ApiBase :: PARAM_DFLT => '',
+ 'protocol' => array(
+ ApiBase::PARAM_TYPE => $protocols,
+ ApiBase::PARAM_DFLT => '',
),
'query' => null,
- 'namespace' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => 'namespace'
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace'
),
- 'limit' => array (
- ApiBase :: PARAM_DFLT => 10,
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
- ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ '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
)
);
}
public function getParamDescription() {
- return array (
- 'prop' => 'What pieces of information to include',
+ $p = $this->getModulePrefix();
+ return 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 euquery set, the protocol is http.',
- 'Leave both this and euquery empty to list all external links' ),
+ '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.'
@@ -206,7 +215,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
public function getDescription() {
return 'Enumerate pages that contain a given URL';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'bad_query', 'info' => 'Invalid query' ),
@@ -214,12 +223,12 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&list=exturlusage&euquery=www.mediawiki.org'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index a748e036..fbfcbfb9 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on May 13, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on May 13, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiQueryBase.php" );
+ require_once( "ApiQueryBase.php" );
}
/**
@@ -36,15 +37,16 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryExternalLinks extends ApiQueryBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'el' );
+ parent::__construct( $query, $moduleName, 'el' );
}
public function execute() {
- if ( $this->getPageSet()->getGoodTitleCount() == 0 )
+ if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
return;
+ }
$params = $this->extractRequestParams();
- $this->addFields( array (
+ $this->addFields( array(
'el_from',
'el_to'
) );
@@ -53,18 +55,19 @@ class ApiQueryExternalLinks extends ApiQueryBase {
$this->addWhereFld( 'el_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
// Don't order by el_from if it's constant in the WHERE clause
- if ( count( $this->getPageSet()->getGoodTitles() ) != 1 )
+ if ( count( $this->getPageSet()->getGoodTitles() ) != 1 ) {
$this->addOption( 'ORDER BY', 'el_from' );
+ }
$this->addOption( 'LIMIT', $params['limit'] + 1 );
- if ( !is_null( $params['offset'] ) )
+ if ( !is_null( $params['offset'] ) ) {
$this->addOption( 'OFFSET', $params['offset'] );
+ }
- $db = $this->getDB();
$res = $this->select( __METHOD__ );
$count = 0;
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
@@ -72,15 +75,13 @@ class ApiQueryExternalLinks extends ApiQueryBase {
break;
}
$entry = array();
- ApiResult :: setContent( $entry, $row->el_to );
+ ApiResult::setContent( $entry, $row->el_to );
$fit = $this->addPageSubItem( $row->el_from, $entry );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'offset', @$params['offset'] + $count - 1 );
break;
}
}
- $db->freeResult( $res );
}
public function getCacheMode( $params ) {
@@ -89,18 +90,18 @@ class ApiQueryExternalLinks extends ApiQueryBase {
public function getAllowedParams() {
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
- ),
- 'offset' => 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
+ ),
+ 'offset' => null,
);
}
- public function getParamDescription () {
+ public function getParamDescription() {
return array(
'limit' => 'How many links to return',
'offset' => 'When more results are available, use this to continue',
@@ -112,13 +113,13 @@ class ApiQueryExternalLinks extends ApiQueryBase {
}
protected function getExamples() {
- return array (
- "Get a list of external links on the [[Main Page]]:",
- " api.php?action=query&prop=extlinks&titles=Main%20Page",
- );
+ return array(
+ 'Get a list of external links on the [[Main Page]]:',
+ ' api.php?action=query&prop=extlinks&titles=Main%20Page',
+ );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php
new file mode 100644
index 00000000..05ccb346
--- /dev/null
+++ b/includes/api/ApiQueryFilearchive.php
@@ -0,0 +1,264 @@
+<?php
+/**
+ * API for MediaWiki 1.12+
+ *
+ * Created on May 10, 2010
+ *
+ * Copyright © 2010 Sam Reed
+ * Copyright © 2008 Vasiliev Victor vasilvv@gmail.com,
+ * based on ApiQueryAllpages.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
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ // Eclipse helper - will be ignored in production
+ require_once( 'ApiQueryBase.php' );
+}
+
+/**
+ * Query module to enumerate all deleted files.
+ *
+ * @ingroup API
+ */
+class ApiQueryFilearchive extends ApiQueryBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'fa' );
+ }
+
+ public function execute() {
+ global $wgUser;
+ // Before doing anything at all, let's check permissions
+ if ( !$wgUser->isAllowed( 'deletedhistory' ) ) {
+ $this->dieUsage( 'You don\'t have permission to view deleted file information', 'permissiondenied' );
+ }
+
+ $db = $this->getDB();
+
+ $params = $this->extractRequestParams();
+
+ $prop = array_flip( $params['prop'] );
+ $fld_sha1 = isset( $prop['sha1'] );
+ $fld_timestamp = isset( $prop['timestamp'] );
+ $fld_user = isset( $prop['user'] );
+ $fld_size = isset( $prop['size'] );
+ $fld_dimensions = isset( $prop['dimensions'] );
+ $fld_description = isset( $prop['description'] );
+ $fld_mime = isset( $prop['mime'] );
+ $fld_metadata = isset( $prop['metadata'] );
+ $fld_bitdepth = isset( $prop['bitdepth'] );
+
+ $this->addTables( 'filearchive' );
+
+ $this->addFields( array( 'fa_name', 'fa_deleted' ) );
+ $this->addFieldsIf( 'fa_storage_key', $fld_sha1 );
+ $this->addFieldsIf( 'fa_timestamp', $fld_timestamp );
+
+ if ( $fld_user ) {
+ $this->addFields( array( 'fa_user', 'fa_user_text' ) );
+ }
+ $this->addFieldsIf( 'fa_size', $fld_size );
+
+ if ( $fld_dimensions ) {
+ $this->addFields( array( 'fa_height', 'fa_width' ) );
+ }
+
+ $this->addFieldsIf( 'fa_description', $fld_description );
+
+ if ( $fld_mime ) {
+ $this->addFields( array( 'fa_major_mime', 'fa_minor_mime' ) );
+ }
+
+ $this->addFieldsIf( 'fa_metadata', $fld_metadata );
+ $this->addFieldsIf( 'fa_bits', $fld_bitdepth );
+
+ // Image filters
+ $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
+ $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
+ $this->addWhereRange( 'fa_name', $dir, $from, null );
+ if ( isset( $params['prefix'] ) ) {
+ $this->addWhere( 'fa_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ }
+
+ if ( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ // Filter out revisions that the user is not allowed to see. There
+ // is no way to indicate that we have skipped stuff because the
+ // continuation parameter is fa_name
+
+ // Note that this field is unindexed. This should however not be
+ // a big problem as files with fa_deleted are rare
+ $this->addWhereFld( 'fa_deleted', 0 );
+ }
+
+
+
+ $limit = $params['limit'];
+ $this->addOption( 'LIMIT', $limit + 1 );
+ $this->addOption( 'ORDER BY', 'fa_name' .
+ ( $params['dir'] == 'descending' ? ' DESC' : '' ) );
+
+ $res = $this->select( __METHOD__ );
+
+ $count = 0;
+ $result = $this->getResult();
+ foreach ( $res as $row ) {
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ // TODO: Security issue - if the user has no right to view next title, it will still be shown
+ $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->fa_name ) );
+ break;
+ }
+
+ $file = array();
+ $file['name'] = $row->fa_name;
+
+ if ( $fld_sha1 ) {
+ $file['sha1'] = wfBaseConvert( $row->fa_storage_key, 36, 16, 40 );
+ }
+ if ( $fld_timestamp ) {
+ $file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
+ }
+ if ( $fld_user ) {
+ $file['userid'] = $row->fa_user;
+ $file['user'] = $row->fa_user_text;
+ }
+ if ( $fld_size ) {
+ $file['size'] = $row->fa_size;
+ }
+ if ( $fld_dimensions ) {
+ $file['height'] = $row->fa_height;
+ $file['width'] = $row->fa_width;
+ }
+ if ( $fld_description ) {
+ $file['description'] = $row->fa_description;
+ }
+ if ( $fld_metadata ) {
+ $file['metadata'] = $row->fa_metadata ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result ) : null;
+ }
+ if ( $fld_bitdepth ) {
+ $file['bitdepth'] = $row->fa_bits;
+ }
+ if ( $fld_mime ) {
+ $file['mime'] = "$row->fa_major_mime/$row->fa_minor_mime";
+ }
+
+ if ( $row->fa_deleted & File::DELETED_FILE ) {
+ $file['filehidden'] = '';
+ }
+ if ( $row->fa_deleted & File::DELETED_COMMENT ) {
+ $file['commenthidden'] = '';
+ }
+ if ( $row->fa_deleted & File::DELETED_USER ) {
+ $file['userhidden'] = '';
+ }
+ if ( $row->fa_deleted & File::DELETED_RESTRICTED ) {
+ // This file is deleted for normal admins
+ $file['suppressed'] = '';
+ }
+
+
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $file );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->fa_name ) );
+ break;
+ }
+ }
+
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'fa' );
+ }
+
+ public function getAllowedParams() {
+ return array (
+ 'from' => 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(
+ 'ascending',
+ 'descending'
+ )
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_DFLT => 'timestamp',
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
+ 'sha1',
+ 'timestamp',
+ 'user',
+ 'size',
+ 'dimensions',
+ 'description',
+ 'mime',
+ 'metadata',
+ 'bitdepth'
+ ),
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'from' => 'The image title to start enumerating from',
+ 'prefix' => 'Search for all image titles that begin with this value',
+ 'dir' => 'The direction in which to list',
+ 'limit' => 'How many total images to return',
+ 'prop' => array(
+ 'What image information to get:',
+ ' sha1 - Adds sha1 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',
+ ' dimensions - Adds the height and width of the image',
+ ' description - Adds description the image version',
+ ' mime - Adds MIME of the image',
+ ' metadata - Lists EXIF metadata for the version of the image',
+ ' bitdepth - Adds the bit depth of the version',
+ ),
+ );
+ }
+
+ public function getDescription() {
+ return 'Enumerate all deleted files sequentially';
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted file information' ),
+ ) );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'Simple Use',
+ ' Show a list of all deleted files',
+ ' api.php?action=query&list=filearchive',
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryFilearchive.php 85354 2011-04-04 18:25:31Z demon $';
+ }
+}
diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php
new file mode 100644
index 00000000..6958a253
--- /dev/null
+++ b/includes/api/ApiQueryIWBacklinks.php
@@ -0,0 +1,217 @@
+<?php
+/**
+ * API for MediaWiki 1.17+
+ *
+ * Created on May 14, 2010
+ *
+ * Copyright © 2010 Sam Reed
+ * 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
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ // Eclipse helper - will be ignored in production
+ require_once( "ApiQueryBase.php" );
+}
+
+/**
+ * This gives links pointing to the given interwiki
+ * @ingroup API
+ */
+class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'iwbl' );
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ public function run( $resultPageSet = null ) {
+ $params = $this->extractRequestParams();
+
+ if ( isset( $params['title'] ) && !isset( $params['prefix'] ) ) {
+ $this->dieUsageMsg( array( 'missingparam', 'prefix' ) );
+ }
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ if ( count( $cont ) != 3 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the ' .
+ 'original value returned by the previous query', '_badcontinue' );
+ }
+
+ $prefix = $this->getDB()->strencode( $cont[0] );
+ $title = $this->getDB()->strencode( $this->titleToKey( $cont[1] ) );
+ $from = intval( $cont[2] );
+ $this->addWhere(
+ "iwl_prefix > '$prefix' OR " .
+ "(iwl_prefix = '$prefix' AND " .
+ "(iwl_title > '$title' OR " .
+ "(iwl_title = '$title' AND " .
+ "iwl_from >= $from)))"
+ );
+ }
+
+ $prop = array_flip( $params['prop'] );
+ $iwprefix = isset( $prop['iwprefix'] );
+ $iwtitle = isset( $prop['iwtitle'] );
+
+ $this->addTables( array( 'iwlinks', 'page' ) );
+ $this->addWhere( 'iwl_from = page_id' );
+
+ $this->addFields( array( 'page_id', 'page_title', 'page_namespace', 'page_is_redirect',
+ 'iwl_from', 'iwl_prefix', 'iwl_title' ) );
+
+ if ( isset( $params['prefix'] ) ) {
+ $this->addWhereFld( 'iwl_prefix', $params['prefix'] );
+ if ( isset( $params['title'] ) ) {
+ $this->addWhereFld( 'iwl_title', $params['title'] );
+ $this->addOption( 'ORDER BY', 'iwl_from' );
+ } else {
+ $this->addOption( 'ORDER BY', 'iwl_title, iwl_from' );
+ }
+ } else {
+ $this->addOption( 'ORDER BY', 'iwl_prefix, iwl_title, iwl_from' );
+ }
+
+ $this->addOption( 'LIMIT', $params['limit'] + 1 );
+
+ $res = $this->select( __METHOD__ );
+
+ $pages = array();
+
+ $count = 0;
+ $result = $this->getResult();
+ foreach ( $res as $row ) {
+ if ( ++ $count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ // Continue string preserved in case the redirect query doesn't pass the limit
+ $this->setContinueEnumParameter( 'continue', "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}" );
+ break;
+ }
+
+ if ( !is_null( $resultPageSet ) ) {
+ $pages[] = Title::newFromRow( $row );
+ } else {
+ $entry = array();
+
+ $entry['pageid'] = intval( $row->page_id );
+ $entry['ns'] = intval( $row->page_namespace );
+ $entry['title'] = $row->page_title;
+
+ if ( $row->page_is_redirect ) {
+ $entry['redirect'] = '';
+ }
+
+ if ( $iwprefix ) {
+ $entry['iwprefix'] = $row->iwl_prefix;
+ }
+
+ if ( $iwtitle ) {
+ $entry['iwtitle'] = $row->iwl_title;
+ }
+
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $entry );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}" );
+ break;
+ }
+ }
+ }
+
+ if ( is_null( $resultPageSet ) ) {
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'iw' );
+ } else {
+ $resultPageSet->populateFromTitles( $pages );
+ }
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'prefix' => null,
+ 'title' => null,
+ 'continue' => 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
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => '',
+ ApiBase::PARAM_TYPE => array(
+ 'iwprefix',
+ 'iwtitle',
+ ),
+ ),
+ );
+ }
+
+ 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',
+ );
+ }
+
+ 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 getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'missingparam', 'prefix' ),
+ array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
+ ) );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=query&list=iwbacklinks&iwbltitle=Test&iwblprefix=wikibooks',
+ 'api.php?action=query&generator=iwbacklinks&giwbltitle=Test&iwblprefix=wikibooks&prop=info'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryIWBacklinks.php 70647 2010-08-07 19:59:42Z ialex $';
+ }
+}
diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php
new file mode 100644
index 00000000..e980d6a5
--- /dev/null
+++ b/includes/api/ApiQueryIWLinks.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * API for MediaWiki 1.17+
+ *
+ * Created on May 14, 2010
+ *
+ * Copyright © 2010 Sam Reed
+ * 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
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ // Eclipse helper - will be ignored in production
+ require_once( "ApiQueryBase.php" );
+}
+
+/**
+ * A query module to list all interwiki links on a page
+ *
+ * @ingroup API
+ */
+class ApiQueryIWLinks extends ApiQueryBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'iw' );
+ }
+
+ public function execute() {
+ if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
+ return;
+ }
+
+ $params = $this->extractRequestParams();
+ $this->addFields( array(
+ 'iwl_from',
+ 'iwl_prefix',
+ 'iwl_title'
+ ) );
+
+ $this->addTables( 'iwlinks' );
+ $this->addWhereFld( 'iwl_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ if ( count( $cont ) != 3 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the ' .
+ 'original value returned by the previous query', '_badcontinue' );
+ }
+ $iwlfrom = intval( $cont[0] );
+ $iwlprefix = $this->getDB()->strencode( $cont[1] );
+ $iwltitle = $this->getDB()->strencode( $this->titleToKey( $cont[2] ) );
+ $this->addWhere(
+ "iwl_from > $iwlfrom OR " .
+ "(iwl_from = $iwlfrom AND " .
+ "(iwl_prefix > '$iwlprefix' OR " .
+ "(iwl_prefix = '$iwlprefix' AND " .
+ "iwl_title >= '$iwltitle')))"
+ );
+ }
+
+ // Don't order by iwl_from if it's constant in the WHERE clause
+ if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
+ $this->addOption( 'ORDER BY', 'iwl_prefix' );
+ } else {
+ $this->addOption( 'ORDER BY', 'iwl_from, iwl_prefix' );
+ }
+ $this->addOption( 'LIMIT', $params['limit'] + 1 );
+ $res = $this->select( __METHOD__ );
+
+ $count = 0;
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that
+ // there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}" );
+ break;
+ }
+ $entry = array( 'prefix' => $row->iwl_prefix );
+
+ if ( !is_null( $params['url'] ) ) {
+ $title = Title::newFromText( "{$row->iwl_prefix}:{$row->iwl_title}" );
+ if ( $title ) {
+ $entry['url'] = $title->getFullURL();
+ }
+ }
+
+ ApiResult::setContent( $entry, $row->iwl_title );
+ $fit = $this->addPageSubItem( $row->iwl_from, $entry );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}" );
+ break;
+ }
+ }
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'url' => 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
+ ),
+ 'continue' => null,
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'url' => 'Whether to get the full URL',
+ 'limit' => 'How many interwiki links to return',
+ 'continue' => 'When more results are available, use this to continue',
+ );
+ }
+
+ public function getDescription() {
+ return 'Returns all interwiki links from the given page(s)';
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
+ ) );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'Get interwiki links from the [[Main Page]]:',
+ ' api.php?action=query&prop=iwlinks&titles=Main%20Page',
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryIWLinks.php 77080 2010-11-21 17:27:13Z reedy $';
+ }
+}
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index 3704710a..21696be2 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on July 6, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on July 6, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -35,8 +36,13 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiQueryImageInfo extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'ii' );
+ public function __construct( $query, $moduleName, $prefix = 'ii' ) {
+ // We allow a subclass to override the prefix, to create a related API module.
+ // Some other parts of MediaWiki construct this with a null $prefix, which used to be ignored when this only took two arguments
+ if ( is_null( $prefix ) ) {
+ $prefix = 'ii';
+ }
+ parent::__construct( $query, $moduleName, $prefix );
}
public function execute() {
@@ -44,16 +50,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$prop = array_flip( $params['prop'] );
- if ( $params['urlheight'] != - 1 && $params['urlwidth'] == - 1 )
- $this->dieUsage( "iiurlheight cannot be used without iiurlwidth", 'iiurlwidth' );
-
- if ( $params['urlwidth'] != - 1 ) {
- $scale = array();
- $scale['width'] = $params['urlwidth'];
- $scale['height'] = $params['urlheight'];
- } else {
- $scale = null;
- }
+ $scale = $this->getScale( $params );
$pageIds = $this->getPageSet()->getAllTitlesByNamespace();
if ( !empty( $pageIds[NS_FILE] ) ) {
@@ -61,30 +58,33 @@ class ApiQueryImageInfo extends ApiQueryBase {
asort( $titles ); // Ensure the order is always the same
$skip = false;
- if ( !is_null( $params['continue'] ) )
- {
+ if ( !is_null( $params['continue'] ) ) {
$skip = true;
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 )
- $this->dieUsage( "Invalid continue param. You should pass the original " .
- "value returned by the previous query", "_badcontinue" );
+ if ( count( $cont ) != 2 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the original ' .
+ 'value returned by the previous query', '_badcontinue' );
+ }
$fromTitle = strval( $cont[0] );
$fromTimestamp = $cont[1];
// Filter out any titles before $fromTitle
- foreach ( $titles as $key => $title )
- if ( $title < $fromTitle )
+ foreach ( $titles as $key => $title ) {
+ if ( $title < $fromTitle ) {
unset( $titles[$key] );
- else
+ } else {
break;
+ }
+ }
}
$result = $this->getResult();
$images = RepoGroup::singleton()->findFiles( $titles );
foreach ( $images as $img ) {
// Skip redirects
- if ( $img->getOriginalTitle()->isRedirect() )
+ if ( $img->getOriginalTitle()->isRedirect() ) {
continue;
-
+ }
+
$start = $skip ? $fromTimestamp : $params['start'];
$pageId = $pageIds[NS_IMAGE][ $img->getOriginalTitle()->getDBkey() ];
@@ -92,9 +92,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
array( 'query', 'pages', intval( $pageId ) ),
'imagerepository', $img->getRepoName()
);
- if ( !$fit )
- {
- if ( count( $pageIds[NS_IMAGE] ) == 1 )
+ if ( !$fit ) {
+ if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
// The user is screwed. imageinfo can't be solely
// responsible for exceeding the limit in this case,
// so set a query-continue that just returns the same
@@ -102,29 +101,33 @@ class ApiQueryImageInfo extends ApiQueryBase {
// out-continued, the result will get through
$this->setContinueEnumParameter( 'start',
wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) );
- else
+ } else {
$this->setContinueEnumParameter( 'continue',
$this->getContinueStr( $img ) );
+ }
break;
}
// Get information about the current version first
// Check that the current version is within the start-end boundaries
$gotOne = false;
- if ( ( is_null( $start ) || $img->getTimestamp() <= $start ) &&
- ( is_null( $params['end'] ) || $img->getTimestamp() >= $params['end'] ) ) {
+ if (
+ ( is_null( $start ) || $img->getTimestamp() <= $start ) &&
+ ( is_null( $params['end'] ) || $img->getTimestamp() >= $params['end'] )
+ )
+ {
$gotOne = true;
$fit = $this->addPageSubItem( $pageId,
self::getInfo( $img, $prop, $result, $scale ) );
- if ( !$fit )
- {
- if ( count( $pageIds[NS_IMAGE] ) == 1 )
+ if ( !$fit ) {
+ if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
// See the 'the user is screwed' comment above
$this->setContinueEnumParameter( 'start',
wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) );
- else
+ } else {
$this->setContinueEnumParameter( 'continue',
$this->getContinueStr( $img ) );
+ }
break;
}
}
@@ -137,8 +140,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( ++$count > $params['limit'] ) {
// We've reached the extra one which shows that there are additional pages to be had. Stop here...
// Only set a query-continue if there was only one title
- if ( count( $pageIds[NS_FILE] ) == 1 )
- {
+ if ( count( $pageIds[NS_FILE] ) == 1 ) {
$this->setContinueEnumParameter( 'start',
wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) );
}
@@ -146,97 +148,210 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
$fit = $this->addPageSubItem( $pageId,
self::getInfo( $oldie, $prop, $result ) );
- if ( !$fit )
- {
- if ( count( $pageIds[NS_IMAGE] ) == 1 )
+ if ( !$fit ) {
+ if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
$this->setContinueEnumParameter( 'start',
wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) );
- else
+ } else {
$this->setContinueEnumParameter( 'continue',
$this->getContinueStr( $oldie ) );
+ }
break;
}
}
- if ( !$fit )
+ if ( !$fit ) {
break;
+ }
$skip = false;
}
-
+
$data = $this->getResultData();
foreach ( $data['query']['pages'] as $pageid => $arr ) {
- if ( !isset( $arr['imagerepository'] ) )
+ if ( !isset( $arr['imagerepository'] ) ) {
$result->addValue(
array( 'query', 'pages', $pageid ),
'imagerepository', ''
);
- // The above can't fail because it doesn't increase the result size
+ }
+ // The above can't fail because it doesn't increase the result size
}
}
}
/**
+ * From parameters, construct a 'scale' array
+ * @param $params Array:
+ * @return Array or Null: key-val array of 'width' and 'height', or null
+ */
+ public function getScale( $params ) {
+ $p = $this->getModulePrefix();
+ if ( $params['urlheight'] != -1 && $params['urlwidth'] == -1 ) {
+ $this->dieUsage( "${p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" );
+ }
+
+ if ( $params['urlwidth'] != -1 ) {
+ $scale = array();
+ $scale['width'] = $params['urlwidth'];
+ $scale['height'] = $params['urlheight'];
+ } else {
+ $scale = null;
+ }
+ return $scale;
+ }
+
+
+ /**
* Get result information for an image revision
- * @param File f The image
- * @return array Result array
+ *
+ * @param $file File object
+ * @param $prop Array of properties to get (in the keys)
+ * @param $result ApiResult object
+ * @param $scale Array containing 'width' and 'height' items, or null
+ * @return Array: result array
*/
static function getInfo( $file, $prop, $result, $scale = null ) {
$vals = array();
- if ( isset( $prop['timestamp'] ) )
+ // Timestamp is shown even if the file is revdelete'd in interface
+ // so do same here.
+ if ( isset( $prop['timestamp'] ) ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
- if ( isset( $prop['user'] ) ) {
- $vals['user'] = $file->getUser();
- if ( !$file->getUser( 'id' ) )
- $vals['anon'] = '';
}
+
+ $user = isset( $prop['user'] );
+ $userid = isset( $prop['userid'] );
+
+ if ( $user || $userid ) {
+ if ( $file->isDeleted( File::DELETED_USER ) ) {
+ $vals['userhidden'] = '';
+ } else {
+ if ( $user ) {
+ $vals['user'] = $file->getUser();
+ }
+ if ( $userid ) {
+ $vals['userid'] = $file->getUser( 'id' );
+ }
+ if ( !$file->getUser( 'id' ) ) {
+ $vals['anon'] = '';
+ }
+ }
+ }
+
+ // This is shown even if the file is revdelete'd in interface
+ // so do same here.
if ( isset( $prop['size'] ) || isset( $prop['dimensions'] ) ) {
$vals['size'] = intval( $file->getSize() );
$vals['width'] = intval( $file->getWidth() );
$vals['height'] = intval( $file->getHeight() );
+
+ $pageCount = $file->pageCount();
+ if ( $pageCount !== false ) {
+ $vals['pagecount'] = $pageCount;
+ }
+ }
+
+ $pcomment = isset( $prop['parsedcomment'] );
+ $comment = isset( $prop['comment'] );
+
+ if ( $pcomment || $comment ) {
+ if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
+ $vals['commenthidden'] = '';
+ } else {
+ if ( $pcomment ) {
+ global $wgUser;
+ $vals['parsedcomment'] = $wgUser->getSkin()->formatComment(
+ $file->getDescription(), $file->getTitle() );
+ }
+ if ( $comment ) {
+ $vals['comment'] = $file->getDescription();
+ }
+ }
}
- if ( isset( $prop['url'] ) ) {
+
+ $url = isset( $prop['url'] );
+ $sha1 = isset( $prop['sha1'] );
+ $meta = isset( $prop['metadata'] );
+ $mime = isset( $prop['mime'] );
+ $archive = isset( $prop['archivename'] );
+ $bitdepth = isset( $prop['bitdepth'] );
+
+ if ( ( $url || $sha1 || $meta || $mime || $archive || $bitdepth )
+ && $file->isDeleted( File::DELETED_FILE ) ) {
+ $vals['filehidden'] = '';
+
+ //Early return, tidier than indenting all following things one level
+ return $vals;
+ }
+
+ if ( $url ) {
if ( !is_null( $scale ) && !$file->isOld() ) {
$mto = $file->transform( array( 'width' => $scale['width'], 'height' => $scale['height'] ) );
- if ( $mto && !$mto->isError() )
- {
+ if ( $mto && !$mto->isError() ) {
$vals['thumburl'] = wfExpandUrl( $mto->getUrl() );
- $vals['thumbwidth'] = intval( $mto->getWidth() );
- $vals['thumbheight'] = intval( $mto->getHeight() );
+
+ // bug 23834 - If the URL's are the same, we haven't resized it, so shouldn't give the wanted
+ // thumbnail sizes for the thumbnail actual size
+ if ( $mto->getUrl() !== $file->getUrl() ) {
+ $vals['thumbwidth'] = intval( $mto->getWidth() );
+ $vals['thumbheight'] = intval( $mto->getHeight() );
+ } else {
+ $vals['thumbwidth'] = intval( $file->getWidth() );
+ $vals['thumbheight'] = intval( $file->getHeight() );
+ }
+
+ if ( isset( $prop['thumbmime'] ) && $file->getHandler() ) {
+ list( $ext, $mime ) = $file->getHandler()->getThumbType(
+ substr( $mto->getPath(), strrpos( $mto->getPath(), '.' ) + 1 ),
+ $file->getMimeType(), $thumbParams );
+ $vals['thumbmime'] = $mime;
+ }
+ } else if ( $mto && $mto->isError() ) {
+ $vals['thumberror'] = $mto->toText();
}
}
$vals['url'] = $file->getFullURL();
$vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl() );
}
- if ( isset( $prop['comment'] ) )
- $vals['comment'] = $file->getDescription();
- if ( isset( $prop['sha1'] ) )
+
+ if ( $sha1 ) {
$vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 );
- if ( isset( $prop['metadata'] ) ) {
+ }
+
+ if ( $meta ) {
$metadata = $file->getMetadata();
$vals['metadata'] = $metadata ? self::processMetaData( unserialize( $metadata ), $result ) : null;
}
- if ( isset( $prop['mime'] ) )
+
+ if ( $mime ) {
$vals['mime'] = $file->getMimeType();
-
- if ( isset( $prop['archivename'] ) && $file->isOld() )
+ }
+
+ if ( $archive && $file->isOld() ) {
$vals['archivename'] = $file->getArchiveName();
-
- if ( isset( $prop['bitdepth'] ) )
+ }
+
+ if ( $bitdepth ) {
$vals['bitdepth'] = $file->getBitDepth();
+ }
return $vals;
}
-
- public static function processMetaData( $metadata, $result )
- {
+
+ /*
+ *
+ * @param $metadata Array
+ * @param $result ApiResult
+ * @return Array
+ */
+ public static function processMetaData( $metadata, $result ) {
$retval = array();
if ( is_array( $metadata ) ) {
- foreach ( $metadata as $key => $value )
- {
+ foreach ( $metadata as $key => $value ) {
$r = array( 'name' => $key );
- if ( is_array( $value ) )
+ if ( is_array( $value ) ) {
$r['value'] = self::processMetaData( $value, $result );
- else
+ } else {
$r['value'] = $value;
+ }
$retval[] = $r;
}
}
@@ -248,82 +363,104 @@ class ApiQueryImageInfo extends ApiQueryBase {
return 'public';
}
- private function getContinueStr( $img )
- {
+ private function getContinueStr( $img ) {
return $img->getOriginalTitle()->getText() .
'|' . $img->getTimestamp();
}
public function getAllowedParams() {
- return array (
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_DFLT => 'timestamp|user',
- ApiBase :: PARAM_TYPE => self::getPropertyNames()
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'timestamp|user',
+ ApiBase::PARAM_TYPE => self::getPropertyNames()
),
'limit' => array(
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_DFLT => 1,
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
- ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_DFLT => 1,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'start' => array(
- ApiBase :: PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'end' => array(
- ApiBase :: PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'urlwidth' => array(
- ApiBase :: PARAM_TYPE => 'integer',
- ApiBase :: PARAM_DFLT => - 1
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_DFLT => -1
),
'urlheight' => array(
- ApiBase :: PARAM_TYPE => 'integer',
- ApiBase :: PARAM_DFLT => - 1
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_DFLT => -1
),
'continue' => null,
);
}
-
+
/**
* Returns all possible parameters to iiprop
*/
public static function getPropertyNames() {
- return array (
- 'timestamp',
- 'user',
- 'comment',
- 'url',
- 'size',
- 'dimensions', // For backwards compatibility with Allimages
- 'sha1',
- 'mime',
- 'metadata',
- 'archivename',
- 'bitdepth',
- );
+ return array(
+ 'timestamp',
+ 'user',
+ 'userid',
+ 'comment',
+ 'parsedcomment',
+ 'url',
+ 'size',
+ 'dimensions', // For backwards compatibility with Allimages
+ 'sha1',
+ 'mime',
+ 'thumbmime',
+ 'metadata',
+ 'archivename',
+ 'bitdepth',
+ );
}
+
+ /**
+ * Return the API documentation for the parameters.
+ * @return {Array} parameter documentation.
+ */
public function getParamDescription() {
- return array (
- 'prop' => 'What image information to get.',
+ $p = $this->getModulePrefix();
+ return array(
+ 'prop' => array(
+ 'What image information to get:',
+ ' timestamp - Adds timestamp for the uploaded version',
+ ' user - Adds the user who uploaded the image version',
+ ' userid - Add the user id that uploaded the image version',
+ ' comment - Comment on the version',
+ ' parsedcomment - Parse the comment on the version',
+ ' url - Gives URL to the image and the description page',
+ ' size - Adds the size of the image in bytes and the height and width',
+ ' dimensions - Alias for size',
+ ' sha1 - Adds sha1 hash for the image',
+ ' mime - Adds MIME of the image',
+ ' thumbmime - Adss MIME of the image thumbnail (requires url)',
+ ' metadata - Lists EXIF metadata for the version of the image',
+ ' archivename - Adds the file name of the archive version for non-latest versions',
+ ' bitdepth - Adds the bit depth of the version',
+ ),
+ 'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
+ 'Only the current version of the image can be scaled' ),
+ 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
'limit' => 'How many image revisions to return',
'start' => 'Timestamp to start listing from',
'end' => 'Timestamp to stop listing at',
- 'urlwidth' => array( 'If iiprop=url is set, a URL to an image scaled to this width will be returned.',
- 'Only the current version of the image can be scaled.' ),
- 'urlheight' => 'Similar to iiurlwidth. Cannot be used without iiurlwidth',
- 'continue' => 'When more results are available, use this to continue',
+ 'continue' => 'If the query response includes a continue value, use it here to get another page of results'
);
}
public function getDescription() {
- return array (
- 'Returns image information and upload history'
- );
+ return 'Returns image information and upload history';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'iiurlwidth', 'info' => 'iiurlheight cannot be used without iiurlwidth' ),
@@ -331,13 +468,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
protected function getExamples() {
- return array (
+ 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',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryImageInfo.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryImageInfo.php 85435 2011-04-05 14:00:08Z demon $';
}
}
diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php
index 65df94dc..af2920c7 100644
--- a/includes/api/ApiQueryImages.php
+++ b/includes/api/ApiQueryImages.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on May 13, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on May 13, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiQueryBase.php" );
+ require_once( "ApiQueryBase.php" );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryImages extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'im' );
+ parent::__construct( $query, $moduleName, 'im' );
}
public function execute() {
@@ -48,12 +49,12 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
}
private function run( $resultPageSet = null ) {
-
- if ( $this->getPageSet()->getGoodTitleCount() == 0 )
+ if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
return; // nothing to do
+ }
$params = $this->extractRequestParams();
- $this->addFields( array (
+ $this->addFields( array(
'il_from',
'il_to'
) );
@@ -62,29 +63,32 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
$this->addWhereFld( 'il_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 )
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
+ if ( count( $cont ) != 2 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the ' .
+ 'original value returned by the previous query', '_badcontinue' );
+ }
$ilfrom = intval( $cont[0] );
$ilto = $this->getDB()->strencode( $this->titleToKey( $cont[1] ) );
- $this->addWhere( "il_from > $ilfrom OR " .
- "(il_from = $ilfrom AND " .
- "il_to >= '$ilto')" );
+ $this->addWhere(
+ "il_from > $ilfrom OR " .
+ "(il_from = $ilfrom AND " .
+ "il_to >= '$ilto')"
+ );
}
// Don't order by il_from if it's constant in the WHERE clause
- if ( count( $this->getPageSet()->getGoodTitles() ) == 1 )
+ if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
$this->addOption( 'ORDER BY', 'il_to' );
- else
+ } else {
$this->addOption( 'ORDER BY', 'il_from, il_to' );
+ }
$this->addOption( 'LIMIT', $params['limit'] + 1 );
- $db = $this->getDB();
$res = $this->select( __METHOD__ );
if ( is_null( $resultPageSet ) ) {
$count = 0;
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
@@ -93,20 +97,18 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
break;
}
$vals = array();
- ApiQueryBase :: addTitleInfo( $vals, Title :: makeTitle( NS_FILE, $row->il_to ) );
+ ApiQueryBase::addTitleInfo( $vals, Title::makeTitle( NS_FILE, $row->il_to ) );
$fit = $this->addPageSubItem( $row->il_from, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $row->il_from .
'|' . $this->keyToTitle( $row->il_to ) );
break;
}
}
} else {
-
$titles = array();
$count = 0;
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
@@ -114,12 +116,10 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
'|' . $this->keyToTitle( $row->il_to ) );
break;
}
- $titles[] = Title :: makeTitle( NS_FILE, $row->il_to );
+ $titles[] = Title::makeTitle( NS_FILE, $row->il_to );
}
$resultPageSet->populateFromTitles( $titles );
}
-
- $db->freeResult( $res );
}
public function getCacheMode( $params ) {
@@ -128,18 +128,18 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
public function getAllowedParams() {
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,
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'continue' => null,
);
}
- public function getParamDescription () {
+ public function getParamDescription() {
return array(
'limit' => 'How many images to return',
'continue' => 'When more results are available, use this to continue',
@@ -149,7 +149,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
public function getDescription() {
return 'Returns all images contained on the given page(s)';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
@@ -157,15 +157,15 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
- "Get a list of images used in the [[Main Page]]:",
- " api.php?action=query&prop=images&titles=Main%20Page",
- "Get information about all images used in the [[Main Page]]:",
- " api.php?action=query&generator=images&titles=Main%20Page&prop=info"
- );
+ return array(
+ 'Get a list of images used in the [[Main Page]]:',
+ ' api.php?action=query&prop=images&titles=Main%20Page',
+ 'Get information about all images used in the [[Main Page]]:',
+ ' api.php?action=query&generator=images&titles=Main%20Page&prop=info'
+ );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryImages.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryImages.php 73543 2010-09-22 16:50:09Z platonides $';
}
}
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index b1c2963c..59f61de1 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 25, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 25, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -38,17 +39,23 @@ class ApiQueryInfo extends ApiQueryBase {
private $fld_protection = false, $fld_talkid = false,
$fld_subjectid = false, $fld_url = false,
$fld_readable = false, $fld_watched = false,
- $fld_preload = false;
+ $fld_preload = false, $fld_displaytitle = false;
+
+ private $tokenFunctions;
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'in' );
+ parent::__construct( $query, $moduleName, 'in' );
}
public function requestExtraData( $pageSet ) {
+ global $wgDisableCounters;
+
$pageSet->requestField( 'page_restrictions' );
$pageSet->requestField( 'page_is_redirect' );
$pageSet->requestField( 'page_is_new' );
- $pageSet->requestField( 'page_counter' );
+ if ( !$wgDisableCounters ) {
+ $pageSet->requestField( 'page_counter' );
+ }
$pageSet->requestField( 'page_touched' );
$pageSet->requestField( 'page_latest' );
$pageSet->requestField( 'page_len' );
@@ -62,12 +69,14 @@ class ApiQueryInfo extends ApiQueryBase {
*/
protected function getTokenFunctions() {
// Don't call the hooks twice
- if ( isset( $this->tokenFunctions ) )
+ if ( isset( $this->tokenFunctions ) ) {
return $this->tokenFunctions;
+ }
// If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) )
+ if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
return array();
+ }
$this->tokenFunctions = array(
'edit' => array( 'ApiQueryInfo', 'getEditToken' ),
@@ -83,109 +92,115 @@ class ApiQueryInfo extends ApiQueryBase {
return $this->tokenFunctions;
}
- public static function getEditToken( $pageid, $title )
- {
+ public static function getEditToken( $pageid, $title ) {
// We could check for $title->userCan('edit') here,
// but that's too expensive for this purpose
// and would break caching
global $wgUser;
- if ( !$wgUser->isAllowed( 'edit' ) )
+ if ( !$wgUser->isAllowed( 'edit' ) ) {
return false;
+ }
// The edit token is always the same, let's exploit that
static $cachedEditToken = null;
- if ( !is_null( $cachedEditToken ) )
+ if ( !is_null( $cachedEditToken ) ) {
return $cachedEditToken;
+ }
$cachedEditToken = $wgUser->editToken();
return $cachedEditToken;
}
- public static function getDeleteToken( $pageid, $title )
- {
+ public static function getDeleteToken( $pageid, $title ) {
global $wgUser;
- if ( !$wgUser->isAllowed( 'delete' ) )
+ if ( !$wgUser->isAllowed( 'delete' ) ) {
return false;
+ }
static $cachedDeleteToken = null;
- if ( !is_null( $cachedDeleteToken ) )
+ if ( !is_null( $cachedDeleteToken ) ) {
return $cachedDeleteToken;
+ }
$cachedDeleteToken = $wgUser->editToken();
return $cachedDeleteToken;
}
- public static function getProtectToken( $pageid, $title )
- {
+ public static function getProtectToken( $pageid, $title ) {
global $wgUser;
- if ( !$wgUser->isAllowed( 'protect' ) )
+ if ( !$wgUser->isAllowed( 'protect' ) ) {
return false;
+ }
static $cachedProtectToken = null;
- if ( !is_null( $cachedProtectToken ) )
+ if ( !is_null( $cachedProtectToken ) ) {
return $cachedProtectToken;
+ }
$cachedProtectToken = $wgUser->editToken();
return $cachedProtectToken;
}
- public static function getMoveToken( $pageid, $title )
- {
+ public static function getMoveToken( $pageid, $title ) {
global $wgUser;
- if ( !$wgUser->isAllowed( 'move' ) )
+ if ( !$wgUser->isAllowed( 'move' ) ) {
return false;
+ }
static $cachedMoveToken = null;
- if ( !is_null( $cachedMoveToken ) )
+ if ( !is_null( $cachedMoveToken ) ) {
return $cachedMoveToken;
+ }
$cachedMoveToken = $wgUser->editToken();
return $cachedMoveToken;
}
- public static function getBlockToken( $pageid, $title )
- {
+ public static function getBlockToken( $pageid, $title ) {
global $wgUser;
- if ( !$wgUser->isAllowed( 'block' ) )
+ if ( !$wgUser->isAllowed( 'block' ) ) {
return false;
+ }
static $cachedBlockToken = null;
- if ( !is_null( $cachedBlockToken ) )
+ if ( !is_null( $cachedBlockToken ) ) {
return $cachedBlockToken;
+ }
$cachedBlockToken = $wgUser->editToken();
return $cachedBlockToken;
}
- public static function getUnblockToken( $pageid, $title )
- {
+ public static function getUnblockToken( $pageid, $title ) {
// Currently, this is exactly the same as the block token
return self::getBlockToken( $pageid, $title );
}
- public static function getEmailToken( $pageid, $title )
- {
+ public static function getEmailToken( $pageid, $title ) {
global $wgUser;
- if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser() )
+ if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser() ) {
return false;
+ }
static $cachedEmailToken = null;
- if ( !is_null( $cachedEmailToken ) )
+ if ( !is_null( $cachedEmailToken ) ) {
return $cachedEmailToken;
+ }
$cachedEmailToken = $wgUser->editToken();
return $cachedEmailToken;
}
- public static function getImportToken( $pageid, $title )
- {
+ public static function getImportToken( $pageid, $title ) {
global $wgUser;
- if ( !$wgUser->isAllowed( 'import' ) )
+ if ( !$wgUser->isAllowed( 'import' ) ) {
return false;
+ }
static $cachedImportToken = null;
- if ( !is_null( $cachedImportToken ) )
+ if ( !is_null( $cachedImportToken ) ) {
return $cachedImportToken;
+ }
$cachedImportToken = $wgUser->editToken();
return $cachedImportToken;
@@ -201,7 +216,8 @@ class ApiQueryInfo extends ApiQueryBase {
$this->fld_subjectid = isset( $prop['subjectid'] );
$this->fld_url = isset( $prop['url'] );
$this->fld_readable = isset( $prop['readable'] );
- $this->fld_preload = isset ( $prop['preload'] );
+ $this->fld_preload = isset( $prop['preload'] );
+ $this->fld_displaytitle = isset( $prop['displaytitle'] );
}
$pageSet = $this->getPageSet();
@@ -211,19 +227,19 @@ class ApiQueryInfo extends ApiQueryBase {
$result = $this->getResult();
uasort( $this->everything, array( 'Title', 'compare' ) );
- if ( !is_null( $this->params['continue'] ) )
- {
+ if ( !is_null( $this->params['continue'] ) ) {
// Throw away any titles we're gonna skip so they don't
// clutter queries
$cont = explode( '|', $this->params['continue'] );
- if ( count( $cont ) != 2 )
- $this->dieUsage( "Invalid continue param. You should pass the original " .
- "value returned by the previous query", "_badcontinue" );
+ if ( count( $cont ) != 2 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the original ' .
+ 'value returned by the previous query', '_badcontinue' );
+ }
$conttitle = Title::makeTitleSafe( $cont[0], $cont[1] );
- foreach ( $this->everything as $pageid => $title )
- {
- if ( Title::compare( $title, $conttitle ) >= 0 )
+ foreach ( $this->everything as $pageid => $title ) {
+ if ( Title::compare( $title, $conttitle ) >= 0 ) {
break;
+ }
unset( $this->titles[$pageid] );
unset( $this->missing[$pageid] );
unset( $this->everything[$pageid] );
@@ -233,31 +249,41 @@ class ApiQueryInfo extends ApiQueryBase {
$this->pageRestrictions = $pageSet->getCustomField( 'page_restrictions' );
$this->pageIsRedir = $pageSet->getCustomField( 'page_is_redirect' );
$this->pageIsNew = $pageSet->getCustomField( 'page_is_new' );
- $this->pageCounter = $pageSet->getCustomField( 'page_counter' );
+
+ global $wgDisableCounters;
+
+ if ( !$wgDisableCounters ) {
+ $this->pageCounter = $pageSet->getCustomField( 'page_counter' );
+ }
$this->pageTouched = $pageSet->getCustomField( 'page_touched' );
$this->pageLatest = $pageSet->getCustomField( 'page_latest' );
$this->pageLength = $pageSet->getCustomField( 'page_len' );
- $db = $this->getDB();
// Get protection info if requested
- if ( $this->fld_protection )
+ if ( $this->fld_protection ) {
$this->getProtectionInfo();
+ }
- if ( $this->fld_watched )
+ if ( $this->fld_watched ) {
$this->getWatchedInfo();
+ }
// Run the talkid/subjectid query if requested
- if ( $this->fld_talkid || $this->fld_subjectid )
+ if ( $this->fld_talkid || $this->fld_subjectid ) {
$this->getTSIDs();
+ }
+
+ if ( $this->fld_displaytitle ) {
+ $this->getDisplayTitle();
+ }
foreach ( $this->everything as $pageid => $title ) {
$pageInfo = $this->extractPageInfo( $pageid, $title );
- $fit = $result->addValue( array (
+ $fit = $result->addValue( array(
'query',
'pages'
), $pageid, $pageInfo );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'continue',
$title->getNamespace() . '|' .
$title->getText() );
@@ -272,81 +298,99 @@ class ApiQueryInfo extends ApiQueryBase {
* @param $title Title object
* @return array
*/
- private function extractPageInfo( $pageid, $title )
- {
+ private function extractPageInfo( $pageid, $title ) {
$pageInfo = array();
- if ( $title->exists() )
- {
+ if ( $title->exists() ) {
+ global $wgDisableCounters;
+
$pageInfo['touched'] = wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
$pageInfo['lastrevid'] = intval( $this->pageLatest[$pageid] );
- $pageInfo['counter'] = intval( $this->pageCounter[$pageid] );
+ $pageInfo['counter'] = $wgDisableCounters
+ ? ""
+ : intval( $this->pageCounter[$pageid] );
$pageInfo['length'] = intval( $this->pageLength[$pageid] );
- if ( $this->pageIsRedir[$pageid] )
+
+ if ( $this->pageIsRedir[$pageid] ) {
$pageInfo['redirect'] = '';
- if ( $this->pageIsNew[$pageid] )
+ }
+ if ( $this->pageIsNew[$pageid] ) {
$pageInfo['new'] = '';
+ }
}
if ( !is_null( $this->params['token'] ) ) {
$tokenFunctions = $this->getTokenFunctions();
$pageInfo['starttimestamp'] = wfTimestamp( TS_ISO_8601, time() );
- foreach ( $this->params['token'] as $t )
- {
+ foreach ( $this->params['token'] as $t ) {
$val = call_user_func( $tokenFunctions[$t], $pageid, $title );
- if ( $val === false )
+ if ( $val === false ) {
$this->setWarning( "Action '$t' is not allowed for the current user" );
- else
+ } else {
$pageInfo[$t . 'token'] = $val;
+ }
}
}
if ( $this->fld_protection ) {
$pageInfo['protection'] = array();
- if ( isset( $this->protections[$title->getNamespace()][$title->getDBkey()] ) )
+ if ( isset( $this->protections[$title->getNamespace()][$title->getDBkey()] ) ) {
$pageInfo['protection'] =
$this->protections[$title->getNamespace()][$title->getDBkey()];
+ }
$this->getResult()->setIndexedTagName( $pageInfo['protection'], 'pr' );
}
- if ( $this->fld_watched && isset( $this->watched[$title->getNamespace()][$title->getDBkey()] ) )
+ if ( $this->fld_watched && isset( $this->watched[$title->getNamespace()][$title->getDBkey()] ) ) {
$pageInfo['watched'] = '';
-
- if ( $this->fld_talkid && isset( $this->talkids[$title->getNamespace()][$title->getDBkey()] ) )
+ }
+
+ if ( $this->fld_talkid && isset( $this->talkids[$title->getNamespace()][$title->getDBkey()] ) ) {
$pageInfo['talkid'] = $this->talkids[$title->getNamespace()][$title->getDBkey()];
+ }
- if ( $this->fld_subjectid && isset( $this->subjectids[$title->getNamespace()][$title->getDBkey()] ) )
+ if ( $this->fld_subjectid && isset( $this->subjectids[$title->getNamespace()][$title->getDBkey()] ) ) {
$pageInfo['subjectid'] = $this->subjectids[$title->getNamespace()][$title->getDBkey()];
+ }
if ( $this->fld_url ) {
$pageInfo['fullurl'] = $title->getFullURL();
$pageInfo['editurl'] = $title->getFullURL( 'action=edit' );
}
- if ( $this->fld_readable && $title->userCanRead() )
+ if ( $this->fld_readable && $title->userCanRead() ) {
$pageInfo['readable'] = '';
-
+ }
+
if ( $this->fld_preload ) {
- if ( $title->exists() )
+ if ( $title->exists() ) {
$pageInfo['preload'] = '';
- else {
+ } else {
+ $text = null;
wfRunHooks( 'EditFormPreloadText', array( &$text, &$title ) );
-
+
$pageInfo['preload'] = $text;
}
}
+
+ if ( $this->fld_displaytitle ) {
+ if ( isset( $this->displaytitles[$title->getArticleId()] ) ) {
+ $pageInfo['displaytitle'] = $this->displaytitles[$title->getArticleId()];
+ } else {
+ $pageInfo['displaytitle'] = $title->getPrefixedText();
+ }
+ }
+
return $pageInfo;
}
/**
* Get information about protections and put it in $protections
*/
- private function getProtectionInfo()
- {
+ private function getProtectionInfo() {
$this->protections = array();
$db = $this->getDB();
// Get normal protections for existing titles
- if ( count( $this->titles ) )
- {
+ if ( count( $this->titles ) ) {
$this->resetQueryParams();
$this->addTables( array( 'page_restrictions', 'page' ) );
$this->addWhere( 'page_id=pr_page' );
@@ -356,14 +400,15 @@ class ApiQueryInfo extends ApiQueryBase {
$this->addWhereFld( 'pr_page', array_keys( $this->titles ) );
$res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$a = array(
'type' => $row->pr_type,
'level' => $row->pr_level,
'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 )
);
- if ( $row->pr_cascade )
+ if ( $row->pr_cascade ) {
$a['cascade'] = '';
+ }
$this->protections[$row->page_namespace][$row->page_title][] = $a;
// Also check old restrictions
@@ -375,8 +420,9 @@ class ApiQueryInfo extends ApiQueryBase {
// old old format should be treated as edit/move restriction
$restriction = trim( $temp[0] );
- if ( $restriction == '' )
+ if ( $restriction == '' ) {
continue;
+ }
$this->protections[$row->page_namespace][$row->page_title][] = array(
'type' => 'edit',
'level' => $restriction,
@@ -389,8 +435,9 @@ class ApiQueryInfo extends ApiQueryBase {
);
} else {
$restriction = trim( $temp[1] );
- if ( $restriction == '' )
+ if ( $restriction == '' ) {
continue;
+ }
$this->protections[$row->page_namespace][$row->page_title][] = array(
'type' => $temp[0],
'level' => $restriction,
@@ -400,35 +447,34 @@ class ApiQueryInfo extends ApiQueryBase {
}
}
}
- $db->freeResult( $res );
}
// Get protections for missing titles
- if ( count( $this->missing ) )
- {
+ if ( count( $this->missing ) ) {
$this->resetQueryParams();
$lb = new LinkBatch( $this->missing );
$this->addTables( 'protected_titles' );
$this->addFields( array( 'pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry' ) );
$this->addWhere( $lb->constructSet( 'pt', $db ) );
$res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$this->protections[$row->pt_namespace][$row->pt_title][] = array(
'type' => 'create',
'level' => $row->pt_create_perm,
'expiry' => Block::decodeExpiry( $row->pt_expiry, TS_ISO_8601 )
);
}
- $db->freeResult( $res );
}
// Cascading protections
$images = $others = array();
- foreach ( $this->everything as $title )
- if ( $title->getNamespace() == NS_FILE )
+ foreach ( $this->everything as $title ) {
+ if ( $title->getNamespace() == NS_FILE ) {
$images[] = $title->getDBkey();
- else
+ } else {
$others[] = $title;
+ }
+ }
if ( count( $others ) ) {
// Non-images: check templatelinks
@@ -444,7 +490,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->addWhereFld( 'pr_cascade', 1 );
$res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$source = Title::makeTitle( $row->page_namespace, $row->page_title );
$this->protections[$row->tl_namespace][$row->tl_title][] = array(
'type' => $row->pr_type,
@@ -453,7 +499,6 @@ class ApiQueryInfo extends ApiQueryBase {
'source' => $source->getPrefixedText()
);
}
- $db->freeResult( $res );
}
if ( count( $images ) ) {
@@ -468,7 +513,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->addWhereFld( 'il_to', $images );
$res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$source = Title::makeTitle( $row->page_namespace, $row->page_title );
$this->protections[NS_FILE][$row->il_to][] = array(
'type' => $row->pr_type,
@@ -477,30 +522,30 @@ class ApiQueryInfo extends ApiQueryBase {
'source' => $source->getPrefixedText()
);
}
- $db->freeResult( $res );
}
}
/**
* Get talk page IDs (if requested) and subject page IDs (if requested)
- * and put them in $talkids and $subjectids
+ * and put them in $talkids and $subjectids
*/
- private function getTSIDs()
- {
+ private function getTSIDs() {
$getTitles = $this->talkids = $this->subjectids = array();
- $db = $this->getDB();
- foreach ( $this->everything as $t )
- {
- if ( MWNamespace::isTalk( $t->getNamespace() ) )
- {
- if ( $this->fld_subjectid )
+
+ foreach ( $this->everything as $t ) {
+ if ( MWNamespace::isTalk( $t->getNamespace() ) ) {
+ if ( $this->fld_subjectid ) {
$getTitles[] = $t->getSubjectPage();
- }
- else if ( $this->fld_talkid )
+ }
+ } elseif ( $this->fld_talkid ) {
$getTitles[] = $t->getTalkPage();
+ }
}
- if ( !count( $getTitles ) )
+ if ( !count( $getTitles ) ) {
return;
+ }
+
+ $db = $this->getDB();
// Construct a custom WHERE clause that matches
// all titles in $getTitles
@@ -510,46 +555,65 @@ class ApiQueryInfo extends ApiQueryBase {
$this->addFields( array( 'page_title', 'page_namespace', 'page_id' ) );
$this->addWhere( $lb->constructSet( 'page', $db ) );
$res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) )
- {
- if ( MWNamespace::isTalk( $row->page_namespace ) )
+ foreach ( $res as $row ) {
+ if ( MWNamespace::isTalk( $row->page_namespace ) ) {
$this->talkids[MWNamespace::getSubject( $row->page_namespace )][$row->page_title] =
intval( $row->page_id );
- else
+ } else {
$this->subjectids[MWNamespace::getTalk( $row->page_namespace )][$row->page_title] =
intval( $row->page_id );
+ }
+ }
+ }
+
+ private function getDisplayTitle() {
+ $this->displaytitles = array();
+
+ $pageIds = array_keys( $this->titles );
+
+ if ( !count( $pageIds ) ) {
+ return;
+ }
+
+ $this->resetQueryParams();
+ $this->addTables( 'page_props' );
+ $this->addFields( array( 'pp_page', 'pp_value' ) );
+ $this->addWhereFld( 'pp_page', $pageIds );
+ $this->addWhereFld( 'pp_propname', 'displaytitle' );
+ $res = $this->select( __METHOD__ );
+
+ foreach ( $res as $row ) {
+ $this->displaytitles[$row->pp_page] = $row->pp_value;
}
}
/**
* Get information about watched status and put it in $this->watched
*/
- private function getWatchedInfo()
- {
+ private function getWatchedInfo() {
global $wgUser;
- if ( $wgUser->isAnon() || count( $this->titles ) == 0 )
+ if ( $wgUser->isAnon() || count( $this->everything ) == 0 ) {
return;
+ }
$this->watched = array();
$db = $this->getDB();
- $lb = new LinkBatch( $this->titles );
+ $lb = new LinkBatch( $this->everything );
$this->resetQueryParams();
- $this->addTables( array( 'page', 'watchlist' ) );
- $this->addFields( array( 'page_title', 'page_namespace' ) );
+ $this->addTables( array( 'watchlist' ) );
+ $this->addFields( array( 'wl_title', 'wl_namespace' ) );
$this->addWhere( array(
- $lb->constructSet( 'page', $db ),
- 'wl_namespace=page_namespace',
- 'wl_title=page_title',
+ $lb->constructSet( 'wl', $db ),
'wl_user' => $wgUser->getID()
) );
$res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
- $this->watched[$row->page_namespace][$row->page_title] = true;
+ foreach ( $res as $row ) {
+ $this->watched[$row->wl_namespace][$row->wl_title] = true;
}
}
@@ -560,6 +624,7 @@ class ApiQueryInfo extends ApiQueryBase {
'subjectid',
'url',
'preload',
+ 'displaytitle',
);
if ( !is_null( $params['prop'] ) ) {
foreach ( $params['prop'] as $prop ) {
@@ -575,33 +640,34 @@ class ApiQueryInfo extends ApiQueryBase {
}
public function getAllowedParams() {
- return array (
- 'prop' => array (
- ApiBase :: PARAM_DFLT => null,
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'protection',
'talkid',
'watched', # private
'subjectid',
'url',
'readable', # private
- 'preload'
- // If you add more properties here, please consider whether they
+ 'preload',
+ 'displaytitle',
+ // If you add more properties here, please consider whether they
// need to be added to getCacheMode()
) ),
- 'token' => array (
- ApiBase :: PARAM_DFLT => null,
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array_keys( $this->getTokenFunctions() )
+ 'token' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() )
),
'continue' => null,
);
}
public function getParamDescription() {
- return array (
- 'prop' => array (
+ 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',
@@ -609,7 +675,8 @@ class ApiQueryInfo extends ApiQueryBase {
' subjectid - The page ID of the parent page for each talk page',
' url - Gives a full URL to the page, and also an edit URL',
' readable - Whether the user can read this page',
- ' preload - Gives the text returned by EditFormPreloadText'
+ ' preload - Gives the text returned by EditFormPreloadText',
+ ' displaytitle - Gives the way the page title is actually displayed',
),
'token' => 'Request a token to perform a data-modifying action on a page',
'continue' => 'When more results are available, use this to continue',
@@ -619,7 +686,7 @@ class ApiQueryInfo extends ApiQueryBase {
public function getDescription() {
return 'Get basic page information such as namespace, title, last touched date, ...';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
@@ -627,13 +694,13 @@ class ApiQueryInfo extends ApiQueryBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&prop=info&titles=Main%20Page',
'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryInfo.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryInfo.php 78439 2010-12-15 14:23:46Z catrope $';
}
}
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index 9330e380..c2ecbfee 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on May 13, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on May 13, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiQueryBase.php" );
+ require_once( "ApiQueryBase.php" );
}
/**
@@ -36,15 +37,16 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryLangLinks extends ApiQueryBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'll' );
+ parent::__construct( $query, $moduleName, 'll' );
}
public function execute() {
- if ( $this->getPageSet()->getGoodTitleCount() == 0 )
+ if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
return;
+ }
$params = $this->extractRequestParams();
- $this->addFields( array (
+ $this->addFields( array(
'll_from',
'll_lang',
'll_title'
@@ -54,27 +56,30 @@ class ApiQueryLangLinks extends ApiQueryBase {
$this->addWhereFld( 'll_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 )
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
+ if ( count( $cont ) != 2 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the ' .
+ 'original value returned by the previous query', '_badcontinue' );
+ }
$llfrom = intval( $cont[0] );
$lllang = $this->getDB()->strencode( $cont[1] );
- $this->addWhere( "ll_from > $llfrom OR " .
- "(ll_from = $llfrom AND " .
- "ll_lang >= '$lllang')" );
+ $this->addWhere(
+ "ll_from > $llfrom OR " .
+ "(ll_from = $llfrom AND " .
+ "ll_lang >= '$lllang')"
+ );
}
// Don't order by ll_from if it's constant in the WHERE clause
- if ( count( $this->getPageSet()->getGoodTitles() ) == 1 )
+ if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
$this->addOption( 'ORDER BY', 'll_lang' );
- else
+ } else {
$this->addOption( 'ORDER BY', 'll_from, ll_lang' );
+ }
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$res = $this->select( __METHOD__ );
$count = 0;
- $db = $this->getDB();
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
@@ -82,15 +87,19 @@ class ApiQueryLangLinks extends ApiQueryBase {
break;
}
$entry = array( 'lang' => $row->ll_lang );
- ApiResult :: setContent( $entry, $row->ll_title );
+ if ( $params['url'] ) {
+ $title = Title::newFromText( "{$row->ll_lang}:{$row->ll_title}" );
+ if ( $title ) {
+ $entry['url'] = $title->getFullURL();
+ }
+ }
+ ApiResult::setContent( $entry, $row->ll_title );
$fit = $this->addPageSubItem( $row->ll_from, $entry );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', "{$row->ll_from}|{$row->ll_lang}" );
break;
}
}
- $db->freeResult( $res );
}
public function getCacheMode( $params ) {
@@ -99,28 +108,30 @@ class ApiQueryLangLinks extends ApiQueryBase {
public function getAllowedParams() {
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,
+ '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' => false,
);
}
- public function getParamDescription () {
+ 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',
);
}
public function getDescription() {
return 'Returns all interlanguage links from the given page(s)';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
@@ -128,13 +139,13 @@ class ApiQueryLangLinks extends ApiQueryBase {
}
protected function getExamples() {
- return array (
- "Get interlanguage links from the [[Main Page]]:",
- " api.php?action=query&prop=langlinks&titles=Main%20Page&redirects",
- );
+ return array(
+ 'Get interlanguage links from the [[Main Page]]:',
+ ' api.php?action=query&prop=langlinks&titles=Main%20Page&redirects=',
+ );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryLangLinks.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryLangLinks.php 77660 2010-12-03 14:44:07Z catrope $';
}
}
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index 52dfd591..4f3bad3b 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on May 12, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on May 12, 2007
+ *
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiQueryBase.php" );
+ require_once( "ApiQueryBase.php" );
}
/**
@@ -41,23 +42,24 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
private $table, $prefix, $description;
public function __construct( $query, $moduleName ) {
-
switch ( $moduleName ) {
- case self::LINKS :
+ case self::LINKS:
$this->table = 'pagelinks';
$this->prefix = 'pl';
$this->description = 'link';
+ $this->titlesParam = 'titles';
break;
- case self::TEMPLATES :
+ case self::TEMPLATES:
$this->table = 'templatelinks';
$this->prefix = 'tl';
$this->description = 'template';
+ $this->titlesParam = 'templates';
break;
- default :
- ApiBase :: dieDebug( __METHOD__, 'Unknown module name' );
+ default:
+ ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
}
- parent :: __construct( $query, $moduleName, $this->prefix );
+ parent::__construct( $query, $moduleName, $this->prefix );
}
public function execute() {
@@ -73,13 +75,13 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
}
private function run( $resultPageSet = null ) {
-
- if ( $this->getPageSet()->getGoodTitleCount() == 0 )
+ if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
return; // nothing to do
+ }
$params = $this->extractRequestParams();
- $this->addFields( array (
+ $this->addFields( array(
$this->prefix . '_from AS pl_from',
$this->prefix . '_namespace AS pl_namespace',
$this->prefix . '_title AS pl_title'
@@ -89,19 +91,38 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->addWhereFld( $this->prefix . '_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
$this->addWhereFld( $this->prefix . '_namespace', $params['namespace'] );
+ if ( !is_null( $params[$this->titlesParam] ) ) {
+ $lb = new LinkBatch;
+ foreach ( $params[$this->titlesParam] as $t ) {
+ $title = Title::newFromText( $t );
+ if ( !$title ) {
+ $this->setWarning( "``$t'' is not a valid title" );
+ } else {
+ $lb->addObj( $title );
+ }
+ }
+ $cond = $lb->constructSet( $this->prefix, $this->getDB() );
+ if ( $cond ) {
+ $this->addWhere( $cond );
+ }
+ }
+
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 )
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
+ if ( count( $cont ) != 3 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the ' .
+ 'original value returned by the previous query', '_badcontinue' );
+ }
$plfrom = intval( $cont[0] );
$plns = intval( $cont[1] );
$pltitle = $this->getDB()->strencode( $this->titleToKey( $cont[2] ) );
- $this->addWhere( "{$this->prefix}_from > $plfrom OR " .
- "({$this->prefix}_from = $plfrom AND " .
- "({$this->prefix}_namespace > $plns OR " .
- "({$this->prefix}_namespace = $plns AND " .
- "{$this->prefix}_title >= '$pltitle')))" );
+ $this->addWhere(
+ "{$this->prefix}_from > $plfrom OR " .
+ "({$this->prefix}_from = $plfrom AND " .
+ "({$this->prefix}_namespace > $plns OR " .
+ "({$this->prefix}_namespace = $plns AND " .
+ "{$this->prefix}_title >= '$pltitle')))"
+ );
}
// Here's some MySQL craziness going on: if you use WHERE foo='bar'
@@ -110,22 +131,23 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
// already. To work around this, we drop constant fields in the WHERE
// clause from the ORDER BY clause
$order = array();
- if ( count( $this->getPageSet()->getGoodTitles() ) != 1 )
+ if ( count( $this->getPageSet()->getGoodTitles() ) != 1 ) {
$order[] = "{$this->prefix}_from";
- if ( count( $params['namespace'] ) != 1 )
+ }
+ if ( count( $params['namespace'] ) != 1 ) {
$order[] = "{$this->prefix}_namespace";
+ }
$order[] = "{$this->prefix}_title";
- $this->addOption( 'ORDER BY', implode( ", ", $order ) );
+ $this->addOption( 'ORDER BY', implode( ', ', $order ) );
$this->addOption( 'USE INDEX', "{$this->prefix}_from" );
$this->addOption( 'LIMIT', $params['limit'] + 1 );
- $db = $this->getDB();
$res = $this->select( __METHOD__ );
if ( is_null( $resultPageSet ) ) {
$count = 0;
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
@@ -135,10 +157,9 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
break;
}
$vals = array();
- ApiQueryBase :: addTitleInfo( $vals, Title :: makeTitle( $row->pl_namespace, $row->pl_title ) );
+ ApiQueryBase::addTitleInfo( $vals, Title::makeTitle( $row->pl_namespace, $row->pl_title ) );
$fit = $this->addPageSubItem( $row->pl_from, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'continue',
"{$row->pl_from}|{$row->pl_namespace}|" .
$this->keyToTitle( $row->pl_title ) );
@@ -148,7 +169,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
} else {
$titles = array();
$count = 0;
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
@@ -157,39 +178,45 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->keyToTitle( $row->pl_title ) );
break;
}
- $titles[] = Title :: makeTitle( $row->pl_namespace, $row->pl_title );
+ $titles[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
}
$resultPageSet->populateFromTitles( $titles );
}
-
- $db->freeResult( $res );
}
- public function getAllowedParams()
- {
+ public function getAllowedParams() {
return array(
- 'namespace' => array(
- ApiBase :: PARAM_TYPE => 'namespace',
- ApiBase :: PARAM_ISMULTI => true
- ),
- 'limit' => array(
- ApiBase :: PARAM_DFLT => 10,
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
- ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
- ),
- 'continue' => null,
- );
+ 'namespace' => array(
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_ISMULTI => true
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'continue' => null,
+ $this->titlesParam => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ );
}
- public function getParamDescription()
- {
- return array(
- 'namespace' => "Show {$this->description}s in this namespace(s) only",
- 'limit' => "How many {$this->description}s to return",
- 'continue' => 'When more results are available, use this to continue',
+ public function getParamDescription() {
+ $desc = $this->description;
+ $arr = 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',
);
+ if ( $this->getModuleName() == self::LINKS ) {
+ $arr[$this->titlesParam] = 'Only list links to these titles. Useful for checking whether a certain page links to a certain title.';
+ } else if ( $this->getModuleName() == self::TEMPLATES ) {
+ $arr[$this->titlesParam] = 'Only list these templates. Useful for checking whether a certain page uses a certain template.';
+ }
+ return $arr;
}
public function getDescription() {
@@ -197,17 +224,17 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
- "Get {$this->description}s from the [[Main Page]]:",
- " api.php?action=query&prop={$this->getModuleName()}&titles=Main%20Page",
- "Get information about the {$this->description} pages in the [[Main Page]]:",
- " api.php?action=query&generator={$this->getModuleName()}&titles=Main%20Page&prop=info",
- "Get {$this->description}s from the Main Page in the User and Template namespaces:",
- " api.php?action=query&prop={$this->getModuleName()}&titles=Main%20Page&{$this->prefix}namespace=2|10"
- );
+ return array(
+ "Get {$this->description}s from the [[Main Page]]:",
+ " api.php?action=query&prop={$this->getModuleName()}&titles=Main%20Page",
+ "Get information about the {$this->description} pages in the [[Main Page]]:",
+ " api.php?action=query&generator={$this->getModuleName()}&titles=Main%20Page&prop=info",
+ "Get {$this->description}s from the Main Page in the User and Template namespaces:",
+ " api.php?action=query&prop={$this->getModuleName()}&titles=Main%20Page&{$this->prefix}namespace=2|10"
+ );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryLinks.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryLinks.php 70647 2010-08-07 19:59:42Z ialex $';
}
}
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index bdeee952..7d69ca39 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 16, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Oct 16, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -36,30 +37,36 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryLogEvents extends ApiQueryBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'le' );
+ parent::__construct( $query, $moduleName, 'le' );
}
+ private $fld_ids = false, $fld_title = false, $fld_type = false,
+ $fld_action = false, $fld_user = false, $fld_userid = false,
+ $fld_timestamp = false, $fld_comment = false, $fld_parsedcomment = false,
+ $fld_details = false, $fld_tags = false;
+
public function execute() {
$params = $this->extractRequestParams();
$db = $this->getDB();
-
+
$prop = array_flip( $params['prop'] );
-
+
$this->fld_ids = isset( $prop['ids'] );
$this->fld_title = isset( $prop['title'] );
$this->fld_type = isset( $prop['type'] );
+ $this->fld_action = isset ( $prop['action'] );
$this->fld_user = isset( $prop['user'] );
+ $this->fld_userid = isset( $prop['userid'] );
$this->fld_timestamp = isset( $prop['timestamp'] );
$this->fld_comment = isset( $prop['comment'] );
$this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
$this->fld_details = isset( $prop['details'] );
$this->fld_tags = isset( $prop['tags'] );
- list( $tbl_logging, $tbl_page, $tbl_user ) = $db->tableNamesN( 'logging', 'page', 'user' );
-
$hideLogs = LogEventsList::getExcludeClause( $db );
- if ( $hideLogs !== false )
+ if ( $hideLogs !== false ) {
$this->addWhere( $hideLogs );
+ }
// Order is significant here
$this->addTables( array( 'logging', 'user', 'page' ) );
@@ -72,7 +79,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
'log_title=page_title' ) ) ) );
$index = array( 'logging' => 'times' ); // default, may change
- $this->addFields( array (
+ $this->addFields( array(
'log_type',
'log_action',
'log_timestamp',
@@ -83,30 +90,36 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->addFieldsIf( 'page_id', $this->fld_ids );
$this->addFieldsIf( 'log_user', $this->fld_user );
$this->addFieldsIf( 'user_name', $this->fld_user );
- $this->addFieldsIf( 'log_namespace', $this->fld_title );
- $this->addFieldsIf( 'log_title', $this->fld_title );
+ $this->addFieldsIf( 'user_id', $this->fld_userid );
+ $this->addFieldsIf( 'log_namespace', $this->fld_title || $this->fld_parsedcomment );
+ $this->addFieldsIf( 'log_title', $this->fld_title || $this->fld_parsedcomment );
$this->addFieldsIf( 'log_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'log_params', $this->fld_details );
-
+
if ( $this->fld_tags ) {
$this->addTables( 'tag_summary' );
$this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', 'log_id=ts_log_id' ) ) );
$this->addFields( 'ts_tags' );
}
-
+
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'log_id=ct_log_id' ) ) ) );
$this->addWhereFld( 'ct_tag', $params['tag'] );
global $wgOldChangeTagsIndex;
- $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
+ $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
}
-
- if ( !is_null( $params['type'] ) ) {
+
+ if ( !is_null( $params['action'] ) ) {
+ list( $type, $action ) = explode( '/', $params['action'] );
+ $this->addWhereFld( 'log_type', $type );
+ $this->addWhereFld( 'log_action', $action );
+ }
+ else if ( !is_null( $params['type'] ) ) {
$this->addWhereFld( 'log_type', $params['type'] );
$index['logging'] = 'type_time';
}
-
+
$this->addWhereRange( 'log_timestamp', $params['dir'], $params['start'], $params['end'] );
$limit = $params['limit'];
@@ -115,17 +128,19 @@ class ApiQueryLogEvents extends ApiQueryBase {
$user = $params['user'];
if ( !is_null( $user ) ) {
$userid = User::idFromName( $user );
- if ( !$userid )
+ if ( !$userid ) {
$this->dieUsage( "User name $user not found", 'param_user' );
+ }
$this->addWhereFld( 'log_user', $userid );
$index['logging'] = 'user_time';
}
$title = $params['title'];
if ( !is_null( $title ) ) {
- $titleObj = Title :: newFromText( $title );
- if ( is_null( $titleObj ) )
+ $titleObj = Title::newFromText( $title );
+ if ( is_null( $titleObj ) ) {
$this->dieUsage( "Bad title value '$title'", 'param_title' );
+ }
$this->addWhereFld( 'log_namespace', $titleObj->getNamespace() );
$this->addWhereFld( 'log_title', $titleObj->getDBkey() );
@@ -145,7 +160,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
$count = 0;
$res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->log_timestamp ) );
@@ -153,33 +168,40 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
$vals = $this->extractRowInfo( $row );
- if ( !$vals )
+ if ( !$vals ) {
continue;
+ }
$fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->log_timestamp ) );
break;
}
}
- $db->freeResult( $res );
-
$this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
}
-
+
+ /**
+ * @static
+ * @param $result ApiResult
+ * @param $vals
+ * @param $params
+ * @param $type
+ * @param $ts
+ * @return array
+ */
public static function addLogParams( $result, &$vals, $params, $type, $ts ) {
$params = explode( "\n", $params );
switch ( $type ) {
case 'move':
- if ( isset ( $params[0] ) ) {
- $title = Title :: newFromText( $params[0] );
+ if ( isset( $params[0] ) ) {
+ $title = Title::newFromText( $params[0] );
if ( $title ) {
$vals2 = array();
- ApiQueryBase :: addTitleInfo( $vals2, $title, "new_" );
+ ApiQueryBase::addTitleInfo( $vals2, $title, 'new_' );
$vals[$type] = $vals2;
}
}
- if ( isset ( $params[1] ) && $params[1] ) {
+ if ( isset( $params[1] ) && $params[1] ) {
$vals[$type]['suppressedredirect'] = '';
}
$params = null;
@@ -199,8 +221,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
case 'block':
$vals2 = array();
list( $vals2['duration'], $vals2['flags'] ) = $params;
- $vals2['expiry'] = wfTimestamp( TS_ISO_8601,
+
+ // Indefinite blocks have no expiry time
+ if ( Block::parseExpiryInput( $params[0] ) !== Block::infinity() ) {
+ $vals2['expiry'] = wfTimestamp( TS_ISO_8601,
strtotime( $params[0], wfTimestamp( TS_UNIX, $ts ) ) );
+ }
$vals[$type] = $vals2;
$params = null;
break;
@@ -220,7 +246,9 @@ class ApiQueryLogEvents extends ApiQueryBase {
$vals['pageid'] = intval( $row->page_id );
}
- $title = Title::makeTitle( $row->log_namespace, $row->log_title );
+ if ( $this->fld_title || $this->fld_parsedcomment ) {
+ $title = Title::makeTitle( $row->log_namespace, $row->log_title );
+ }
if ( $this->fld_title ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
@@ -230,7 +258,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
}
- if ( $this->fld_type ) {
+ if ( $this->fld_type || $this->fld_action ) {
$vals['type'] = $row->log_type;
$vals['action'] = $row->log_action;
}
@@ -239,32 +267,42 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
$vals['actionhidden'] = '';
} else {
- self::addLogParams( $this->getResult(), $vals,
+ self::addLogParams(
+ $this->getResult(), $vals,
$row->log_params, $row->log_type,
- $row->log_timestamp );
+ $row->log_timestamp
+ );
}
}
- if ( $this->fld_user ) {
+ if ( $this->fld_user || $this->fld_userid ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
$vals['userhidden'] = '';
} else {
- $vals['user'] = $row->user_name;
- if ( !$row->log_user )
+ if ( $this->fld_user ) {
+ $vals['user'] = $row->user_name;
+ }
+ if ( $this->fld_userid ) {
+ $vals['userid'] = $row->user_id;
+ }
+
+ if ( !$row->log_user ) {
$vals['anon'] = '';
+ }
}
}
if ( $this->fld_timestamp ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->log_timestamp );
}
-
+
if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->log_comment ) ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
$vals['commenthidden'] = '';
} else {
- if ( $this->fld_comment )
+ if ( $this->fld_comment ) {
$vals['comment'] = $row->log_comment;
-
+ }
+
if ( $this->fld_parsedcomment ) {
global $wgUser;
$vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->log_comment, $title );
@@ -281,10 +319,10 @@ class ApiQueryLogEvents extends ApiQueryBase {
$vals['tags'] = array();
}
}
-
+
return $vals;
}
-
+
public function getCacheMode( $params ) {
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMsg() among other things
@@ -295,16 +333,17 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
public function getAllowedParams() {
- global $wgLogTypes;
- return array (
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_DFLT => 'ids|title|type|user|timestamp|comment|details',
- ApiBase :: PARAM_TYPE => array (
+ global $wgLogTypes, $wgLogActions;
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'ids|title|type|user|timestamp|comment|details',
+ ApiBase::PARAM_TYPE => array(
'ids',
'title',
'type',
'user',
+ 'userid',
'timestamp',
'comment',
'parsedcomment',
@@ -312,18 +351,21 @@ class ApiQueryLogEvents extends ApiQueryBase {
'tags'
)
),
- 'type' => array (
- ApiBase :: PARAM_TYPE => $wgLogTypes
+ 'type' => array(
+ ApiBase::PARAM_TYPE => $wgLogTypes
+ ),
+ 'action' => array(
+ ApiBase::PARAM_TYPE => array_keys( $wgLogActions )
),
- 'start' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ 'start' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
- 'end' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ 'end' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
- 'dir' => array (
- ApiBase :: PARAM_DFLT => 'older',
- ApiBase :: PARAM_TYPE => array (
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_TYPE => array(
'newer',
'older'
)
@@ -331,34 +373,47 @@ class ApiQueryLogEvents extends ApiQueryBase {
'user' => null,
'title' => null,
'tag' => 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
+ '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
)
);
}
public function getParamDescription() {
- return array (
- 'prop' => 'Which properties to get',
+ 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 addtional details about the event',
+ ' tags - Lists tags for the event',
+ ),
'type' => 'Filter log entries to only this type(s)',
- 'start' => 'The timestamp to start enumerating from.',
- 'end' => 'The timestamp to end enumerating.',
- 'dir' => 'In which direction to enumerate.',
- 'user' => 'Filter entries to those made by the given user.',
- 'title' => 'Filter entries to those related to a page.',
- 'limit' => 'How many total event entries to return.',
- 'tag' => 'Only list event entries tagged with this tag.',
+ 'action' => "Filter log actions to only this type. Overrides {$this->getModulePrefix()}type",
+ 'start' => 'The timestamp to start enumerating from',
+ 'end' => 'The timestamp to end enumerating',
+ 'dir' => 'In which direction to enumerate',
+ 'user' => 'Filter entries to those made by the given user',
+ 'title' => 'Filter entries to those related to a page',
+ 'limit' => 'How many total event entries to return',
+ 'tag' => 'Only list event entries tagged with this tag',
);
}
public function getDescription() {
- return 'Get events from logs.';
+ return 'Get events from logs';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'param_user', 'info' => 'User name $user not found' ),
@@ -367,12 +422,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&list=logevents'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryLogEvents.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryLogEvents.php 74535 2010-10-09 00:01:45Z reedy $';
}
}
diff --git a/includes/api/ApiQueryPageProps.php b/includes/api/ApiQueryPageProps.php
new file mode 100644
index 00000000..894e812d
--- /dev/null
+++ b/includes/api/ApiQueryPageProps.php
@@ -0,0 +1,150 @@
+<?php
+/**
+ * API for MediaWiki 1.8+
+ *
+ * Created on Aug 7, 2010
+ *
+ * Copyright © 2010 soxred93, Bryan Tong Minh
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ // Eclipse helper - will be ignored in production
+ require_once( 'ApiQueryBase.php' );
+}
+
+/**
+ * A query module to show basic page information.
+ *
+ * @ingroup API
+ */
+class ApiQueryPageProps extends ApiQueryBase {
+
+ private $params;
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'pp' );
+ }
+
+ public function execute() {
+ $this->params = $this->extractRequestParams();
+
+ # Only operate on existing pages
+ $pages = $this->getPageSet()->getGoodTitles();
+ if ( !count( $pages ) ) {
+ # Nothing to do
+ return;
+ }
+
+ $this->addTables( 'page_props' );
+ $this->addFields( array( 'pp_page', 'pp_propname', 'pp_value' ) );
+ $this->addWhereFld( 'pp_page', array_keys( $pages ) );
+
+ if ( $this->params['continue'] ) {
+ $this->addWhere( 'pp_page >=' . intval( $this->params['continue'] ) );
+ }
+
+ # Force a sort order to ensure that properties are grouped by page
+ $this->addOption( 'ORDER BY', 'pp_page' );
+
+ $res = $this->select( __METHOD__ );
+ $currentPage = 0; # Id of the page currently processed
+ $props = array();
+ $result = $this->getResult();
+
+ foreach ( $res as $row ) {
+ if ( $currentPage != $row->pp_page ) {
+ # Different page than previous row, so add the properties to
+ # the result and save the new page id
+
+ if ( $currentPage ) {
+ if ( !$this->addPageProps( $result, $currentPage, $props ) ) {
+ # addPageProps() indicated that the result did not fit
+ # so stop adding data. Reset props so that it doesn't
+ # get added again after loop exit
+
+ $props = array();
+ break;
+ }
+
+ $props = array();
+ }
+
+ $currentPage = $row->pp_page;
+ }
+
+ $props[$row->pp_propname] = $row->pp_value;
+ }
+
+ if ( count( $props ) ) {
+ # Add any remaining properties to the results
+ $this->addPageProps( $result, $currentPage, $props );
+ }
+ }
+
+ /**
+ * Add page properties to an ApiResult, adding a continue
+ * parameter if it doesn't fit.
+ *
+ * @param $result ApiResult
+ * @param $page int
+ * @param $props array
+ * @return bool True if it fits in the result
+ */
+ private function addPageProps( $result, $page, $props ) {
+ $fit = $result->addValue( array( 'query', 'pages', $page ), 'pageprops', $props );
+
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', $page );
+ }
+ return $fit;
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ return array( 'continue' => null );
+ }
+
+ public function getParamDescription() {
+ return array( 'continue' => 'When more results are available, use this to continue' );
+ }
+
+ public function getDescription() {
+ return 'Get various properties defined in the page content';
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
+ ) );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=query&prop=pageprops&titles=Category:Foo',
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryPageProps.php 85211 2011-04-02 21:01:00Z demon $';
+ }
+}
diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php
index ab794805..e647c39f 100644
--- a/includes/api/ApiQueryProtectedTitles.php
+++ b/includes/api/ApiQueryProtectedTitles.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Feb 13, 2009
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Feb 13, 2009
+ *
+ * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'pt' );
+ parent::__construct( $query, $moduleName, 'pt' );
}
public function execute() {
@@ -48,14 +49,13 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
}
private function run( $resultPageSet = null ) {
- $db = $this->getDB();
$params = $this->extractRequestParams();
$this->addTables( 'protected_titles' );
$this->addFields( array( 'pt_namespace', 'pt_title', 'pt_timestamp' ) );
$prop = array_flip( $params['prop'] );
- $this->addFieldsIf( 'pt_user', isset( $prop['user'] ) );
+ $this->addFieldsIf( 'pt_user', isset( $prop['user'] ) || isset( $prop['userid'] ) );
$this->addFieldsIf( 'pt_reason', isset( $prop['comment'] ) || isset( $prop['parsedcomment'] ) );
$this->addFieldsIf( 'pt_expiry', isset( $prop['expiry'] ) );
$this->addFieldsIf( 'pt_create_perm', isset( $prop['level'] ) );
@@ -63,9 +63,8 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$this->addWhereRange( 'pt_timestamp', $params['dir'], $params['start'], $params['end'] );
$this->addWhereFld( 'pt_namespace', $params['namespace'] );
$this->addWhereFld( 'pt_create_perm', $params['level'] );
-
- if ( isset( $prop['user'] ) )
- {
+
+ if ( isset( $prop['user'] ) ) {
$this->addTables( 'user' );
$this->addFields( 'user_name' );
$this->addJoinConds( array( 'user' => array( 'LEFT JOIN',
@@ -78,7 +77,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $params['limit'] ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->pt_timestamp ) );
@@ -89,26 +88,35 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
if ( is_null( $resultPageSet ) ) {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $title );
- if ( isset( $prop['timestamp'] ) )
+ if ( isset( $prop['timestamp'] ) ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->pt_timestamp );
-
- if ( isset( $prop['user'] ) && !is_null( $row->user_name ) )
+ }
+
+ if ( isset( $prop['user'] ) && !is_null( $row->user_name ) ) {
$vals['user'] = $row->user_name;
-
- if ( isset( $prop['comment'] ) )
+ }
+
+ if ( isset( $prop['user'] ) ) {
+ $vals['userid'] = $row->pt_user;
+ }
+
+ if ( isset( $prop['comment'] ) ) {
$vals['comment'] = $row->pt_reason;
-
+ }
+
if ( isset( $prop['parsedcomment'] ) ) {
global $wgUser;
$vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->pt_reason, $title );
}
-
- if ( isset( $prop['expiry'] ) )
+
+ if ( isset( $prop['expiry'] ) ) {
$vals['expiry'] = Block::decodeExpiry( $row->pt_expiry, TS_ISO_8601 );
-
- if ( isset( $prop['level'] ) )
+ }
+
+ if ( isset( $prop['level'] ) ) {
$vals['level'] = $row->pt_create_perm;
-
+ }
+
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'start',
@@ -119,11 +127,12 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$titles[] = $title;
}
}
- $db->freeResult( $res );
- if ( is_null( $resultPageSet ) )
+
+ if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $this->getModulePrefix() );
- else
+ } else {
$resultPageSet->populateFromTitles( $titles );
+ }
}
public function getCacheMode( $params ) {
@@ -137,41 +146,42 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
public function getAllowedParams() {
global $wgRestrictionLevels;
- return array (
- 'namespace' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => 'namespace',
+ return array(
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace',
),
'level' => array(
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array_diff( $wgRestrictionLevels, array( '' ) )
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array_diff( $wgRestrictionLevels, 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
+ 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 => 'older',
- ApiBase :: PARAM_TYPE => array (
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_TYPE => array(
'older',
'newer'
)
),
'start' => array(
- ApiBase :: PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'end' => array(
- ApiBase :: PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'prop' => array(
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_DFLT => 'timestamp|level',
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'timestamp|level',
+ ApiBase::PARAM_TYPE => array(
'timestamp',
'user',
+ 'userid',
'comment',
'parsedcomment',
'expiry',
@@ -182,13 +192,22 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
}
public function getParamDescription() {
- return array (
+ return array(
'namespace' => 'Only list titles in these namespaces',
'start' => 'Start listing at this protection timestamp',
'end' => 'Stop listing at this protection timestamp',
'dir' => 'The direction in which to list',
- 'limit' => 'How many total pages to return.',
- 'prop' => 'Which properties to get',
+ '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 to add the protection',
+ ' userid - Adds the user id to add 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',
+ ),
'level' => 'Only list titles with these protection levels',
);
}
@@ -198,12 +217,12 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&list=protectedtitles',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryProtectedTitles.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryProtectedTitles.php 71838 2010-08-28 01:18:18Z reedy $';
}
}
diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php
index 10796810..b3b840fd 100644
--- a/includes/api/ApiQueryRandom.php
+++ b/includes/api/ApiQueryRandom.php
@@ -1,11 +1,11 @@
<?php
-/*
- * Created on Monday, January 28, 2008
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Brent Garber
+ * Created on Monday, January 28, 2008
+ *
+ * Copyright © 2008 Brent Garber
*
* This 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,13 +19,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -37,7 +39,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryRandom extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'rn' );
+ parent::__construct( $query, $moduleName, 'rn' );
}
public function execute() {
@@ -56,38 +58,37 @@ if ( !defined( 'MEDIAWIKI' ) ) {
$this->addWhereRange( 'page_random', 'newer', $randstr, null );
$this->addWhereFld( 'page_is_redirect', $redirect );
$this->addOption( 'USE INDEX', 'page_random' );
- if ( is_null( $resultPageSet ) )
+ if ( is_null( $resultPageSet ) ) {
$this->addFields( array( 'page_id', 'page_title', 'page_namespace' ) );
- else
+ } else {
$this->addFields( $resultPageSet->getPageTableFields() );
+ }
}
protected function runQuery( &$resultPageSet ) {
- $db = $this->getDB();
$res = $this->select( __METHOD__ );
$count = 0;
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$count++;
- if ( is_null( $resultPageSet ) )
- {
+ if ( is_null( $resultPageSet ) ) {
// Prevent duplicates
- if ( !in_array( $row->page_id, $this->pageIDs ) )
- {
+ if ( !in_array( $row->page_id, $this->pageIDs ) ) {
$fit = $this->getResult()->addValue(
array( 'query', $this->getModuleName() ),
null, $this->extractRowInfo( $row ) );
- if ( !$fit )
+ if ( !$fit ) {
// We can't really query-continue a random list.
// Return an insanely high value so
// $count < $limit is false
return 1E9;
+ }
$this->pageIDs[] = $row->page_id;
}
- }
- else
+ } else {
$resultPageSet->processDbRow( $row );
+ }
}
- $db->freeResult( $res );
+
return $count;
}
@@ -95,17 +96,16 @@ if ( !defined( 'MEDIAWIKI' ) ) {
$params = $this->extractRequestParams();
$result = $this->getResult();
$this->pageIDs = array();
-
+
$this->prepareQuery( wfRandom(), $params['limit'], $params['namespace'], $resultPageSet, $params['redirect'] );
$count = $this->runQuery( $resultPageSet );
- if ( $count < $params['limit'] )
- {
+ if ( $count < $params['limit'] ) {
/* We got too few pages, we probably picked a high value
* for page_random. We'll just take the lowest ones, see
* also the comment in Title::getRandomTitle()
*/
- $this->prepareQuery( 0, $params['limit'] - $count, $params['namespace'], $resultPageSet, $params['redirect'] );
- $this->runQuery( $resultPageSet );
+ $this->prepareQuery( 0, $params['limit'] - $count, $params['namespace'], $resultPageSet, $params['redirect'] );
+ $this->runQuery( $resultPageSet );
}
if ( is_null( $resultPageSet ) ) {
@@ -126,24 +126,24 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
public function getAllowedParams() {
- return array (
+ return array(
'namespace' => array(
- ApiBase :: PARAM_TYPE => 'namespace',
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_ISMULTI => true
),
- 'limit' => array (
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_DFLT => 1,
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => 10,
- ApiBase :: PARAM_MAX2 => 20
+ 'limit' => array(
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_DFLT => 1,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => 10,
+ ApiBase::PARAM_MAX2 => 20
),
'redirect' => false,
);
}
public function getParamDescription() {
- return array (
+ 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'
@@ -151,10 +151,11 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
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.'
+ 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'
);
}
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
index 1f0de3be..fb0d42b8 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 19, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Oct 19, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -37,12 +38,15 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryRecentChanges extends ApiQueryBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'rc' );
+ parent::__construct( $query, $moduleName, 'rc' );
}
- private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_flags = false,
- $fld_timestamp = false, $fld_title = false, $fld_ids = false,
- $fld_sizes = false;
+ private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
+ $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
+ $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false, $fld_tags = false;
+
+ private $tokenFunctions;
+
/**
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title, $rc)
@@ -51,12 +55,14 @@ class ApiQueryRecentChanges extends ApiQueryBase {
*/
protected function getTokenFunctions() {
// Don't call the hooks twice
- if ( isset( $this->tokenFunctions ) )
+ if ( isset( $this->tokenFunctions ) ) {
return $this->tokenFunctions;
+ }
// If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) )
+ if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
return array();
+ }
$this->tokenFunctions = array(
'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' )
@@ -64,36 +70,38 @@ class ApiQueryRecentChanges extends ApiQueryBase {
wfRunHooks( 'APIQueryRecentChangesTokens', array( &$this->tokenFunctions ) );
return $this->tokenFunctions;
}
-
- public static function getPatrolToken( $pageid, $title, $rc )
- {
+
+ public static function getPatrolToken( $pageid, $title, $rc ) {
global $wgUser;
if ( !$wgUser->useRCPatrol() && ( !$wgUser->useNPPatrol() ||
- $rc->getAttribute( 'rc_type' ) != RC_NEW ) )
+ $rc->getAttribute( 'rc_type' ) != RC_NEW ) )
+ {
return false;
-
+ }
+
// The patrol token is always the same, let's exploit that
static $cachedPatrolToken = null;
- if ( !is_null( $cachedPatrolToken ) )
- return $cachedPatrolToken;
-
- $cachedPatrolToken = $wgUser->editToken();
+ if ( is_null( $cachedPatrolToken ) ) {
+ $cachedPatrolToken = $wgUser->editToken( 'patrol' );
+ }
+
return $cachedPatrolToken;
}
/**
* Sets internal state to include the desired properties in the output.
- * @param $prop associative array of properties, only keys are used here
+ * @param $prop Array associative array of properties, only keys are used here
*/
public function initProperties( $prop ) {
- $this->fld_comment = isset ( $prop['comment'] );
- $this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
- $this->fld_user = isset ( $prop['user'] );
- $this->fld_flags = isset ( $prop['flags'] );
- $this->fld_timestamp = isset ( $prop['timestamp'] );
- $this->fld_title = isset ( $prop['title'] );
- $this->fld_ids = isset ( $prop['ids'] );
- $this->fld_sizes = isset ( $prop['sizes'] );
+ $this->fld_comment = isset( $prop['comment'] );
+ $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
+ $this->fld_user = isset( $prop['user'] );
+ $this->fld_userid = isset( $prop['userid'] );
+ $this->fld_flags = isset( $prop['flags'] );
+ $this->fld_timestamp = isset( $prop['timestamp'] );
+ $this->fld_title = isset( $prop['title'] );
+ $this->fld_ids = isset( $prop['ids'] );
+ $this->fld_sizes = isset( $prop['sizes'] );
$this->fld_redirect = isset( $prop['redirect'] );
$this->fld_patrolled = isset( $prop['patrolled'] );
$this->fld_loginfo = isset( $prop['loginfo'] );
@@ -104,6 +112,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
* Generates and outputs the result of this query based upon the provided parameters.
*/
public function execute() {
+ global $wgUser;
/* Get the parameters of the request. */
$params = $this->extractRequestParams();
@@ -112,73 +121,78 @@ class ApiQueryRecentChanges extends ApiQueryBase {
* AND rc_timestamp < $end AND rc_namespace = $namespace
* AND rc_deleted = '0'
*/
- $db = $this->getDB();
$this->addTables( 'recentchanges' );
$index = array( 'recentchanges' => 'rc_timestamp' ); // May change
$this->addWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
$this->addWhereFld( 'rc_namespace', $params['namespace'] );
$this->addWhereFld( 'rc_deleted', 0 );
- if ( !is_null( $params['type'] ) )
- $this->addWhereFld( 'rc_type', $this->parseRCType( $params['type'] ) );
+ if ( !is_null( $params['type'] ) ) {
+ $this->addWhereFld( 'rc_type', $this->parseRCType( $params['type'] ) );
+ }
if ( !is_null( $params['show'] ) ) {
$show = array_flip( $params['show'] );
/* Check for conflicting parameters. */
- if ( ( isset ( $show['minor'] ) && isset ( $show['!minor'] ) )
- || ( isset ( $show['bot'] ) && isset ( $show['!bot'] ) )
- || ( isset ( $show['anon'] ) && isset ( $show['!anon'] ) )
- || ( isset ( $show['redirect'] ) && isset ( $show['!redirect'] ) )
- || ( isset ( $show['patrolled'] ) && isset ( $show['!patrolled'] ) ) ) {
-
+ if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
+ || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
+ || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
+ || ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) )
+ || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
+ )
+ {
$this->dieUsageMsg( array( 'show' ) );
}
-
+
// Check permissions
- global $wgUser;
- if ( ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() )
- $this->dieUsage( "You need the patrol right to request the patrolled flag", 'permissiondenied' );
+ if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
+ if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
+ $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
+ }
+ }
/* Add additional conditions to query depending upon parameters. */
- $this->addWhereIf( 'rc_minor = 0', isset ( $show['!minor'] ) );
- $this->addWhereIf( 'rc_minor != 0', isset ( $show['minor'] ) );
- $this->addWhereIf( 'rc_bot = 0', isset ( $show['!bot'] ) );
- $this->addWhereIf( 'rc_bot != 0', isset ( $show['bot'] ) );
- $this->addWhereIf( 'rc_user = 0', isset ( $show['anon'] ) );
- $this->addWhereIf( 'rc_user != 0', isset ( $show['!anon'] ) );
+ $this->addWhereIf( 'rc_minor = 0', isset( $show['!minor'] ) );
+ $this->addWhereIf( 'rc_minor != 0', isset( $show['minor'] ) );
+ $this->addWhereIf( 'rc_bot = 0', isset( $show['!bot'] ) );
+ $this->addWhereIf( 'rc_bot != 0', isset( $show['bot'] ) );
+ $this->addWhereIf( 'rc_user = 0', isset( $show['anon'] ) );
+ $this->addWhereIf( 'rc_user != 0', isset( $show['!anon'] ) );
$this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
$this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
- $this->addWhereIf( 'page_is_redirect = 1', isset ( $show['redirect'] ) );
-
+ $this->addWhereIf( 'page_is_redirect = 1', isset( $show['redirect'] ) );
+
// Don't throw log entries out the window here
- $this->addWhereIf( 'page_is_redirect = 0 OR page_is_redirect IS NULL', isset ( $show['!redirect'] ) );
+ $this->addWhereIf( 'page_is_redirect = 0 OR page_is_redirect IS NULL', isset( $show['!redirect'] ) );
}
-
- if ( !is_null( $params['user'] ) && !is_null( $param['excludeuser'] ) )
+
+ if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
$this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' );
-
- if ( !is_null( $params['user'] ) )
- {
+ }
+
+ if ( !is_null( $params['user'] ) ) {
$this->addWhereFld( 'rc_user_text', $params['user'] );
$index['recentchanges'] = 'rc_user_text';
}
-
- if ( !is_null( $params['excludeuser'] ) )
+
+ if ( !is_null( $params['excludeuser'] ) ) {
// We don't use the rc_user_text index here because
// * it would require us to sort by rc_user_text before rc_timestamp
// * the != condition doesn't throw out too many rows anyway
$this->addWhere( 'rc_user_text != ' . $this->getDB()->addQuotes( $params['excludeuser'] ) );
+ }
/* Add the fields we're concerned with to our query. */
- $this->addFields( array (
+ $this->addFields( array(
'rc_timestamp',
'rc_namespace',
'rc_title',
'rc_cur_id',
'rc_type',
'rc_moved_to_ns',
- 'rc_moved_to_title'
+ 'rc_moved_to_title',
+ 'rc_deleted'
) );
/* Determine what properties we need to display. */
@@ -188,9 +202,9 @@ class ApiQueryRecentChanges extends ApiQueryBase {
/* Set up internal members based upon params. */
$this->initProperties( $prop );
- global $wgUser;
- if ( $this->fld_patrolled && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() )
- $this->dieUsage( "You need the patrol right to request the patrolled flag", 'permissiondenied' );
+ if ( $this->fld_patrolled && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
+ $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
+ }
/* Add fields to our query if they are specified as a needed parameter. */
$this->addFieldsIf( 'rc_id', $this->fld_ids );
@@ -198,7 +212,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$this->addFieldsIf( 'rc_last_oldid', $this->fld_ids );
$this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'rc_user', $this->fld_user );
- $this->addFieldsIf( 'rc_user_text', $this->fld_user );
+ $this->addFieldsIf( 'rc_user_text', $this->fld_user || $this->fld_userid );
$this->addFieldsIf( 'rc_minor', $this->fld_flags );
$this->addFieldsIf( 'rc_bot', $this->fld_flags );
$this->addFieldsIf( 'rc_new', $this->fld_flags );
@@ -209,39 +223,37 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$this->addFieldsIf( 'rc_log_type', $this->fld_loginfo );
$this->addFieldsIf( 'rc_log_action', $this->fld_loginfo );
$this->addFieldsIf( 'rc_params', $this->fld_loginfo );
- if ( $this->fld_redirect || isset( $show['redirect'] ) || isset( $show['!redirect'] ) )
- {
+ if ( $this->fld_redirect || isset( $show['redirect'] ) || isset( $show['!redirect'] ) ) {
$this->addTables( 'page' );
$this->addJoinConds( array( 'page' => array( 'LEFT JOIN', array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) );
$this->addFields( 'page_is_redirect' );
}
}
-
+
if ( $this->fld_tags ) {
$this->addTables( 'tag_summary' );
$this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rc_id=ts_rc_id' ) ) ) );
$this->addFields( 'ts_tags' );
}
-
+
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) );
$this->addWhereFld( 'ct_tag' , $params['tag'] );
global $wgOldChangeTagsIndex;
- $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
+ $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
}
-
+
$this->token = $params['token'];
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$this->addOption( 'USE INDEX', $index );
$count = 0;
/* Perform the actual query. */
- $db = $this->getDB();
$res = $this->select( __METHOD__ );
/* Iterate through the rows, adding data extracted from them to our query result. */
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $params['limit'] ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
@@ -252,18 +264,16 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$vals = $this->extractRowInfo( $row );
/* Add that row's data to our final output. */
- if ( !$vals )
+ if ( !$vals ) {
continue;
+ }
$fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
break;
}
}
- $db->freeResult( $res );
-
/* Format the result */
$this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'rc' );
}
@@ -279,15 +289,17 @@ class ApiQueryRecentChanges extends ApiQueryBase {
/* If page was moved somewhere, get the title of the move target. */
$movedToTitle = false;
if ( isset( $row->rc_moved_to_title ) && $row->rc_moved_to_title !== '' )
- $movedToTitle = Title :: makeTitle( $row->rc_moved_to_ns, $row->rc_moved_to_title );
+ {
+ $movedToTitle = Title::makeTitle( $row->rc_moved_to_ns, $row->rc_moved_to_title );
+ }
/* Determine the title of the page that has been changed. */
- $title = Title :: makeTitle( $row->rc_namespace, $row->rc_title );
+ $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
/* Our output data. */
- $vals = array ();
+ $vals = array();
- $type = intval ( $row->rc_type );
+ $type = intval( $row->rc_type );
/* Determine what kind of change this was. */
switch ( $type ) {
@@ -312,9 +324,10 @@ class ApiQueryRecentChanges extends ApiQueryBase {
/* Create a new entry in the result for the title. */
if ( $this->fld_title ) {
- ApiQueryBase :: addTitleInfo( $vals, $title );
- if ( $movedToTitle )
- ApiQueryBase :: addTitleInfo( $vals, $movedToTitle, "new_" );
+ ApiQueryBase::addTitleInfo( $vals, $title );
+ if ( $movedToTitle ) {
+ ApiQueryBase::addTitleInfo( $vals, $movedToTitle, 'new_' );
+ }
}
/* Add ids, such as rcid, pageid, revid, and oldid to the change's info. */
@@ -326,20 +339,32 @@ class ApiQueryRecentChanges extends ApiQueryBase {
}
/* Add user data and 'anon' flag, if use is anonymous. */
- if ( $this->fld_user ) {
- $vals['user'] = $row->rc_user_text;
- if ( !$row->rc_user )
+ if ( $this->fld_user || $this->fld_userid ) {
+
+ if ( $this->fld_user ) {
+ $vals['user'] = $row->rc_user_text;
+ }
+
+ if ( $this->fld_userid ) {
+ $vals['userid'] = $row->rc_user;
+ }
+
+ if ( !$row->rc_user ) {
$vals['anon'] = '';
+ }
}
/* Add flags, such as new, minor, bot. */
if ( $this->fld_flags ) {
- if ( $row->rc_bot )
+ if ( $row->rc_bot ) {
$vals['bot'] = '';
- if ( $row->rc_new )
+ }
+ if ( $row->rc_new ) {
$vals['new'] = '';
- if ( $row->rc_minor )
+ }
+ if ( $row->rc_minor ) {
$vals['minor'] = '';
+ }
}
/* Add sizes of each revision. (Only available on 1.10+) */
@@ -349,35 +374,42 @@ class ApiQueryRecentChanges extends ApiQueryBase {
}
/* Add the timestamp. */
- if ( $this->fld_timestamp )
+ if ( $this->fld_timestamp ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
+ }
/* Add edit summary / log summary. */
- if ( $this->fld_comment && isset( $row->rc_comment ) )
+ if ( $this->fld_comment && isset( $row->rc_comment ) ) {
$vals['comment'] = $row->rc_comment;
-
+ }
+
if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
global $wgUser;
$vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->rc_comment, $title );
}
- if ( $this->fld_redirect )
- if ( $row->page_is_redirect )
+ if ( $this->fld_redirect ) {
+ if ( $row->page_is_redirect ) {
$vals['redirect'] = '';
+ }
+ }
/* Add the patrolled flag */
- if ( $this->fld_patrolled && $row->rc_patrolled == 1 )
+ if ( $this->fld_patrolled && $row->rc_patrolled == 1 ) {
$vals['patrolled'] = '';
-
+ }
+
if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
$vals['logid'] = intval( $row->rc_logid );
$vals['logtype'] = $row->rc_log_type;
$vals['logaction'] = $row->rc_log_action;
- ApiQueryLogEvents::addLogParams( $this->getResult(),
+ ApiQueryLogEvents::addLogParams(
+ $this->getResult(),
$vals, $row->rc_params,
- $row->rc_log_type, $row->rc_timestamp );
+ $row->rc_log_type, $row->rc_timestamp
+ );
}
-
+
if ( $this->fld_tags ) {
if ( $row->ts_tags ) {
$tags = explode( ',', $row->ts_tags );
@@ -387,39 +419,39 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$vals['tags'] = array();
}
}
-
- if ( !is_null( $this->token ) )
- {
+
+ if ( !is_null( $this->token ) ) {
$tokenFunctions = $this->getTokenFunctions();
- foreach ( $this->token as $t )
- {
+ foreach ( $this->token as $t ) {
$val = call_user_func( $tokenFunctions[$t], $row->rc_cur_id,
$title, RecentChange::newFromRow( $row ) );
- if ( $val === false )
+ if ( $val === false ) {
$this->setWarning( "Action '$t' is not allowed for the current user" );
- else
+ } else {
$vals[$t . 'token'] = $val;
+ }
}
}
return $vals;
}
- private function parseRCType( $type )
- {
- if ( is_array( $type ) )
- {
- $retval = array();
- foreach ( $type as $t )
- $retval[] = $this->parseRCType( $t );
- return $retval;
- }
- switch( $type )
- {
- case 'edit': return RC_EDIT;
- case 'new': return RC_NEW;
- case 'log': return RC_LOG;
+ private function parseRCType( $type ) {
+ if ( is_array( $type ) ) {
+ $retval = array();
+ foreach ( $type as $t ) {
+ $retval[] = $this->parseRCType( $t );
}
+ return $retval;
+ }
+ switch( $type ) {
+ case 'edit':
+ return RC_EDIT;
+ case 'new':
+ return RC_NEW;
+ case 'log':
+ return RC_LOG;
+ }
}
public function getCacheMode( $params ) {
@@ -441,36 +473,37 @@ class ApiQueryRecentChanges extends ApiQueryBase {
}
public function getAllowedParams() {
- return array (
- 'start' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ return array(
+ 'start' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
- 'end' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ 'end' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
- 'dir' => array (
- ApiBase :: PARAM_DFLT => 'older',
- ApiBase :: PARAM_TYPE => array (
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_TYPE => array(
'newer',
'older'
)
),
- 'namespace' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => 'namespace'
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace'
),
'user' => array(
- ApiBase :: PARAM_TYPE => 'user'
+ ApiBase::PARAM_TYPE => 'user'
),
'excludeuser' => array(
- ApiBase :: PARAM_TYPE => 'user'
+ ApiBase::PARAM_TYPE => 'user'
),
'tag' => null,
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_DFLT => 'title|timestamp|ids',
- ApiBase :: PARAM_TYPE => array (
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'title|timestamp|ids',
+ ApiBase::PARAM_TYPE => array(
'user',
+ 'userid',
'comment',
'parsedcomment',
'flags',
@@ -485,12 +518,12 @@ class ApiQueryRecentChanges extends ApiQueryBase {
)
),
'token' => array(
- ApiBase :: PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
+ ApiBase::PARAM_ISMULTI => true
),
- 'show' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ 'show' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'minor',
'!minor',
'bot',
@@ -503,16 +536,16 @@ class ApiQueryRecentChanges extends ApiQueryBase {
'!patrolled'
)
),
- '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
+ '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
),
- 'type' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ 'type' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'edit',
'new',
'log'
@@ -522,29 +555,44 @@ class ApiQueryRecentChanges extends ApiQueryBase {
}
public function getParamDescription() {
- return array (
- 'start' => 'The timestamp to start enumerating from.',
- 'end' => 'The timestamp to end enumerating.',
- 'dir' => 'In which direction to enumerate.',
+ return array(
+ 'start' => 'The timestamp to start enumerating from',
+ 'end' => 'The timestamp to end enumerating',
+ 'dir' => 'In which direction to enumerate',
'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' => 'Include additional pieces of information',
+ '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 edits have have been patrolled',
+ ' loginfo - Adds log information (logid, logtype, etc) to log entries',
+ ' tags - Lists tags for the entry',
+ ),
'token' => 'Which tokens to obtain for each change',
- 'show' => array (
+ 'show' => array(
'Show only items that meet this criteria.',
- 'For example, to see only minor edits done by logged-in users, set show=minor|!anon'
+ "For example, to see only minor edits done by logged-in users, set {$this->getModulePrefix()}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.',
+ 'type' => 'Which types of changes to show',
+ 'limit' => 'How many total changes to return',
+ 'tag' => 'Only list changes tagged with this tag',
);
}
public function getDescription() {
return 'Enumerate recent changes';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'show' ),
@@ -554,12 +602,12 @@ class ApiQueryRecentChanges extends ApiQueryBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&list=recentchanges'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 78437 2010-12-15 14:14:16Z catrope $';
}
}
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index 3992d6a9..64a0a4ea 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 7, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 7, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -37,12 +38,18 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiQueryRevisions extends ApiQueryBase {
+ private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
+ $token;
+
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'rv' );
+ parent::__construct( $query, $moduleName, 'rv' );
}
private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false,
- $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_content = false, $fld_tags = false;
+ $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
+ $fld_content = false, $fld_tags = false;
+
+ private $tokenFunctions;
protected function getTokenFunctions() {
// tokenname => function
@@ -50,12 +57,14 @@ class ApiQueryRevisions extends ApiQueryBase {
// should return token or false
// Don't call the hooks twice
- if ( isset( $this->tokenFunctions ) )
+ if ( isset( $this->tokenFunctions ) ) {
return $this->tokenFunctions;
+ }
// If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) )
+ if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
return array();
+ }
$this->tokenFunctions = array(
'rollback' => array( 'ApiQueryRevisions', 'getRollbackToken' )
@@ -64,11 +73,11 @@ class ApiQueryRevisions extends ApiQueryBase {
return $this->tokenFunctions;
}
- public static function getRollbackToken( $pageid, $title, $rev )
- {
+ public static function getRollbackToken( $pageid, $title, $rev ) {
global $wgUser;
- if ( !$wgUser->isAllowed( 'rollback' ) )
+ if ( !$wgUser->isAllowed( 'rollback' ) ) {
return false;
+ }
return $wgUser->editToken( array( $title->getPrefixedText(),
$rev->getUserText() ) );
}
@@ -91,31 +100,37 @@ class ApiQueryRevisions extends ApiQueryBase {
$revCount = $pageSet->getRevisionCount();
// Optimization -- nothing to do
- if ( $revCount === 0 && $pageCount === 0 )
+ if ( $revCount === 0 && $pageCount === 0 ) {
return;
+ }
- if ( $revCount > 0 && $enumRevMode )
+ if ( $revCount > 0 && $enumRevMode ) {
$this->dieUsage( 'The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids' );
+ }
- if ( $pageCount > 1 && $enumRevMode )
+ if ( $pageCount > 1 && $enumRevMode ) {
$this->dieUsage( 'titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.', 'multpages' );
+ }
- $this->diffto = $this->difftotext = null;
if ( !is_null( $params['difftotext'] ) ) {
$this->difftotext = $params['difftotext'];
- } else if ( !is_null( $params['diffto'] ) ) {
- if ( $params['diffto'] == 'cur' )
+ } 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 )
+ if ( !$difftoRev ) {
$this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
+ }
if ( !$difftoRev->userCan( Revision::DELETED_TEXT ) ) {
$this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
$params['diffto'] = null;
@@ -139,39 +154,49 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->fld_comment = isset ( $prop['comment'] );
$this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
$this->fld_size = isset ( $prop['size'] );
+ $this->fld_userid = isset( $prop['userid'] );
$this->fld_user = isset ( $prop['user'] );
$this->token = $params['token'];
// Possible indexes used
$index = array();
+ $userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
+ $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
+ $limit = $params['limit'];
+ if ( $limit == 'max' ) {
+ $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
+ $this->getResult()->setParsedLimit( $this->getModuleName(), $limit );
+ }
+
+
if ( !is_null( $this->token ) || $pageCount > 0 ) {
$this->addFields( Revision::selectPageFields() );
}
- if ( isset ( $prop['tags'] ) ) {
+ if ( isset( $prop['tags'] ) ) {
$this->fld_tags = true;
$this->addTables( 'tag_summary' );
$this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) ) );
$this->addFields( 'ts_tags' );
}
-
+
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
$this->addWhereFld( 'ct_tag' , $params['tag'] );
global $wgOldChangeTagsIndex;
- $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
+ $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
}
-
- if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) {
+ if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) {
// For each page we will request, the user must have read rights for that page
foreach ( $pageSet->getGoodTitles() as $title ) {
- if ( !$title->userCanRead() )
+ if ( !$title->userCanRead() ) {
$this->dieUsage(
'The current user is not allowed to read ' . $title->getPrefixedText(),
'accessdenied' );
+ }
}
$this->addTables( 'text' );
@@ -183,34 +208,39 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->expandTemplates = $params['expandtemplates'];
$this->generateXML = $params['generatexml'];
- if ( isset( $params['section'] ) )
+ $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
+ } else {
$this->section = false;
+ }
}
//Bug 24166 - API error when using rvprop=tags
$this->addTables( 'revision' );
- $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()->addValue( 'limits', $this->getModuleName(), $limit );
- }
if ( $enumRevMode ) {
-
// This is mostly to prevent parameter errors (and optimize SQL?)
- if ( !is_null( $params['startid'] ) && !is_null( $params['start'] ) )
+ if ( !is_null( $params['startid'] ) && !is_null( $params['start'] ) ) {
$this->dieUsage( 'start and startid cannot be used together', 'badparams' );
+ }
- if ( !is_null( $params['endid'] ) && !is_null( $params['end'] ) )
+ if ( !is_null( $params['endid'] ) && !is_null( $params['end'] ) ) {
$this->dieUsage( 'end and endid cannot be used together', 'badparams' );
+ }
- if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) )
+ if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
$this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
+ }
// This code makes an assumption that sorting by rev_id and rev_timestamp produces
// the same result. This way users may request revisions starting at a given time,
@@ -218,11 +248,10 @@ class ApiQueryRevisions extends ApiQueryBase {
// Switching to rev_id removes the potential problem of having more than
// one row with the same timestamp for the same page.
// The order needs to be the same as start parameter to avoid SQL filesort.
-
- if ( is_null( $params['startid'] ) && is_null( $params['endid'] ) )
+ if ( is_null( $params['startid'] ) && is_null( $params['endid'] ) ) {
$this->addWhereRange( 'rev_timestamp', $params['dir'],
$params['start'], $params['end'] );
- else {
+ } else {
$this->addWhereRange( 'rev_id', $params['dir'],
$params['startid'], $params['endid'] );
// One of start and end can be set
@@ -232,8 +261,9 @@ class ApiQueryRevisions extends ApiQueryBase {
}
// must manually initialize unset limit
- if ( is_null( $limit ) )
+ if ( is_null( $limit ) ) {
$limit = 10;
+ }
$this->validateLimit( 'limit', $limit, 1, $userMax, $botMax );
// There is only one ID, use it
@@ -250,114 +280,125 @@ class ApiQueryRevisions extends ApiQueryBase {
// Paranoia: avoid brute force searches (bug 17342)
$this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
}
- }
- elseif ( $revCount > 0 ) {
+ } elseif ( $revCount > 0 ) {
$max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
$revs = $pageSet->getRevisionIDs();
- if ( self::truncateArray( $revs, $max ) )
+ if ( self::truncateArray( $revs, $max ) ) {
$this->setWarning( "Too many values supplied for parameter 'revids': the limit is $max" );
+ }
// Get all revision IDs
$this->addWhereFld( 'rev_id', array_keys( $revs ) );
- if ( !is_null( $params['continue'] ) )
+ if ( !is_null( $params['continue'] ) ) {
$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 ) {
+ } elseif ( $pageCount > 0 ) {
$max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
$titles = $pageSet->getGoodTitles();
- if ( self::truncateArray( $titles, $max ) )
+ 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
$this->addWhereFld( 'page_id', array_keys( $titles ) );
// Every time someone relies on equality propagation, god kills a kitten :)
$this->addWhereFld( 'rev_page', array_keys( $titles ) );
-
- if ( !is_null( $params['continue'] ) )
- {
+
+ if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 )
- $this->dieUsage( "Invalid continue param. You should pass the original " .
- "value returned by the previous query", "_badcontinue" );
+ if ( count( $cont ) != 2 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the original ' .
+ 'value returned by the previous query', '_badcontinue' );
+ }
$pageid = intval( $cont[0] );
$revid = intval( $cont[1] );
- $this->addWhere( "rev_page > '$pageid' OR " .
- "(rev_page = '$pageid' AND " .
- "rev_id >= '$revid')" );
+ $this->addWhere(
+ "rev_page > '$pageid' OR " .
+ "(rev_page = '$pageid' AND " .
+ "rev_id >= '$revid')"
+ );
}
$this->addOption( 'ORDER BY', 'rev_page, rev_id' );
// assumption testing -- we should never get more then $pageCount rows.
$limit = $pageCount;
- } else
- ApiBase :: dieDebug( __METHOD__, 'param validation?' );
+ } else {
+ ApiBase::dieDebug( __METHOD__, 'param validation?' );
+ }
$this->addOption( 'LIMIT', $limit + 1 );
$this->addOption( 'USE INDEX', $index );
- $data = array ();
$count = 0;
$res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
-
+ foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
- if ( !$enumRevMode )
- ApiBase :: dieDebug( __METHOD__, 'Got more rows then expected' ); // bug report
+ if ( !$enumRevMode ) {
+ ApiBase::dieDebug( __METHOD__, 'Got more rows then expected' ); // bug report
+ }
$this->setContinueEnumParameter( 'startid', intval( $row->rev_id ) );
break;
}
-
- //
+
$fit = $this->addPageSubItem( $row->rev_page, $this->extractRowInfo( $row ), 'rev' );
- if ( !$fit )
- {
- if ( $enumRevMode )
+ if ( !$fit ) {
+ if ( $enumRevMode ) {
$this->setContinueEnumParameter( 'startid', intval( $row->rev_id ) );
- else if ( $revCount > 0 )
+ } elseif ( $revCount > 0 ) {
$this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
- else
+ } else {
$this->setContinueEnumParameter( 'continue', intval( $row->rev_page ) .
'|' . intval( $row->rev_id ) );
+ }
break;
}
}
- $db->freeResult( $res );
}
private function extractRowInfo( $row ) {
$revision = new Revision( $row );
$title = $revision->getTitle();
- $vals = array ();
+ $vals = array();
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['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() )
+ if ( $this->fld_flags && $revision->isMinor() ) {
$vals['minor'] = '';
+ }
- if ( $this->fld_user ) {
+ if ( $this->fld_user || $this->fld_userid ) {
if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
$vals['userhidden'] = '';
} else {
- $vals['user'] = $revision->getUserText();
- if ( !$revision->getUser() )
+ if ( $this->fld_user ) {
+ $vals['user'] = $revision->getUserText();
+ }
+ $userid = $revision->getUser();
+ if ( !$userid ) {
$vals['anon'] = '';
+ }
+
+ if ( $this->fld_userid ) {
+ $vals['userid'] = $userid;
+ }
}
}
@@ -374,15 +415,14 @@ class ApiQueryRevisions extends ApiQueryBase {
$vals['commenthidden'] = '';
} else {
$comment = $revision->getComment();
- if ( strval( $comment ) !== '' )
- {
- if ( $this->fld_comment )
- $vals['comment'] = $comment;
-
- if ( $this->fld_parsedcomment ) {
- global $wgUser;
- $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $comment, $title );
- }
+
+ if ( $this->fld_comment ) {
+ $vals['comment'] = $comment;
+ }
+
+ if ( $this->fld_parsedcomment ) {
+ global $wgUser;
+ $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $comment, $title );
}
}
}
@@ -396,31 +436,31 @@ class ApiQueryRevisions extends ApiQueryBase {
$vals['tags'] = array();
}
}
-
- if ( !is_null( $this->token ) )
- {
+
+ if ( !is_null( $this->token ) ) {
$tokenFunctions = $this->getTokenFunctions();
- foreach ( $this->token as $t )
- {
+ foreach ( $this->token as $t ) {
$val = call_user_func( $tokenFunctions[$t], $title->getArticleID(), $title, $revision );
- if ( $val === false )
+ if ( $val === false ) {
$this->setWarning( "Action '$t' is not allowed for the current user" );
- else
+ } else {
$vals[$t . 'token'] = $val;
+ }
}
}
-
+
$text = null;
+ global $wgParser;
if ( $this->fld_content || !is_null( $this->difftotext ) ) {
- global $wgParser;
$text = $revision->getText();
// Expand templates after getting section content because
// template-added sections don't count and Parser::preprocess()
// will have less input
if ( $this->section !== false ) {
$text = $wgParser->getSection( $text, $this->section, false );
- if ( $text === false )
+ if ( $text === false ) {
$this->dieUsage( "There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection' );
+ }
}
}
if ( $this->fld_content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
@@ -433,13 +473,36 @@ class ApiQueryRevisions extends ApiQueryBase {
$xml = $dom->__toString();
}
$vals['parsetree'] = $xml;
-
+
}
- if ( $this->expandTemplates ) {
+ if ( $this->expandTemplates && !$this->parseContent ) {
$text = $wgParser->preprocess( $text, $title, new ParserOptions() );
}
- ApiResult :: setContent( $vals, $text );
- } else if ( $this->fld_content ) {
+ if ( $this->parseContent ) {
+ global $wgEnableParserCache;
+
+ $popts = new ParserOptions();
+ $popts->setTidy( true );
+
+ $articleObj = new Article( $title );
+
+ $p_result = false;
+ $pcache = ParserCache::singleton();
+ if ( $wgEnableParserCache ) {
+ $p_result = $pcache->get( $articleObj, $popts );
+ }
+ if ( !$p_result ) {
+ $p_result = $wgParser->parse( $text, $title, $popts );
+
+ if ( $wgEnableParserCache ) {
+ $pcache->save( $p_result, $articleObj, $popts );
+ }
+ }
+
+ $text = $p_result->getText();
+ }
+ ApiResult::setContent( $vals, $text );
+ } elseif ( $this->fld_content ) {
$vals['texthidden'] = '';
}
@@ -458,8 +521,9 @@ class ApiQueryRevisions extends ApiQueryBase {
}
$difftext = $engine->getDiffBody();
ApiResult::setContent( $vals['diff'], $difftext );
- if ( !$engine->wasCacheHit() )
+ if ( !$engine->wasCacheHit() ) {
$n++;
+ }
} else {
$vals['diff']['notcached'] = '';
}
@@ -474,20 +538,21 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMsg() among other things
return 'anon-public-user-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 (
+ 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',
'comment',
'parsedcomment',
@@ -495,44 +560,45 @@ class ApiQueryRevisions extends ApiQueryBase {
'tags'
)
),
- 'limit' => array (
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
- ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ 'limit' => array(
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
- 'startid' => array (
- ApiBase :: PARAM_TYPE => 'integer'
+ 'startid' => array(
+ ApiBase::PARAM_TYPE => 'integer'
),
- 'endid' => array (
- ApiBase :: PARAM_TYPE => 'integer'
+ 'endid' => array(
+ ApiBase::PARAM_TYPE => 'integer'
),
- 'start' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ 'start' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
- 'end' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ 'end' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
- 'dir' => array (
- ApiBase :: PARAM_DFLT => 'older',
- ApiBase :: PARAM_TYPE => array (
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_TYPE => array(
'newer',
'older'
)
),
'user' => array(
- ApiBase :: PARAM_TYPE => 'user'
+ ApiBase::PARAM_TYPE => 'user'
),
'excludeuser' => array(
- ApiBase :: PARAM_TYPE => 'user'
+ ApiBase::PARAM_TYPE => 'user'
),
'tag' => null,
'expandtemplates' => false,
'generatexml' => false,
+ 'parse' => false,
'section' => null,
'token' => array(
- ApiBase :: PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
+ ApiBase::PARAM_ISMULTI => true
),
'continue' => null,
'diffto' => null,
@@ -541,8 +607,21 @@ class ApiQueryRevisions extends ApiQueryBase {
}
public function getParamDescription() {
- return array (
- 'prop' => 'Which properties to get for each revision.',
+ $p = $this->getModulePrefix();
+ 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 of the revision',
+ ' 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)',
@@ -553,28 +632,29 @@ class ApiQueryRevisions extends ApiQueryBase {
'excludeuser' => 'Exclude revisions made by user',
'expandtemplates' => 'Expand templates in revision content',
'generatexml' => 'Generate XML parse tree for revision content',
- 'section' => 'Only retrieve the content of this section',
+ 'parse' => 'Parse revision 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.' ),
+ '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 diffto. If rvsection is set, only that section will be diffed against this text.' ),
+ "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',
);
}
public function getDescription() {
- return array (
- 'Get revision information.',
+ return array(
+ 'Get revision information',
'This module may be used in several ways:',
- ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter.',
- ' 2) Get revisions for one given page, by using titles/pageids with start/end/limit params.',
- ' 3) Get data about a set of revisions by setting their IDs with revids parameter.',
- 'All parameters marked as (enum) may only be used with a single page (#2).'
+ ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter',
+ ' 2) Get revisions for one given page, by using titles/pageids with start/end/limit params',
+ ' 3) Get data about a set of revisions by setting their IDs with revids parameter',
+ 'All parameters marked as (enum) may only be used with a single page (#2)'
);
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'nosuchrevid', 'diffto' ),
@@ -589,7 +669,7 @@ class ApiQueryRevisions extends ApiQueryBase {
}
protected function getExamples() {
- return array (
+ 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":',
@@ -606,6 +686,6 @@ class ApiQueryRevisions extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRevisions.php 72117 2010-09-01 16:50:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryRevisions.php 75521 2010-10-27 11:50:20Z catrope $';
}
}
diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php
index 4e032321..3cf693af 100644
--- a/includes/api/ApiQuerySearch.php
+++ b/includes/api/ApiQuerySearch.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on July 30, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on July 30, 2007
+ *
+ * Copyright © 2007 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -36,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQuerySearch extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'sr' );
+ parent::__construct( $query, $moduleName, 'sr' );
}
public function execute() {
@@ -57,9 +58,6 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$what = $params['what'];
$searchInfo = array_flip( $params['info'] );
$prop = array_flip( $params['prop'] );
-
- if ( strval( $query ) === '' )
- $this->dieUsage( "empty search string is not allowed", 'param-search' );
// Create search engine instance and set options
$search = SearchEngine::create();
@@ -72,6 +70,8 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$matches = $search->searchText( $query );
} elseif ( $what == 'title' ) {
$matches = $search->searchTitle( $query );
+ } elseif ( $what == 'nearmatch' ) {
+ $matches = SearchEngine::getNearMatchResultSet( $query );
} else {
// We default to title searches; this is a terrible legacy
// of the way we initially set up the MySQL fulltext-based
@@ -79,7 +79,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
// In the future, the default should be for a combined index.
$what = 'title';
$matches = $search->searchTitle( $query );
-
+
// Not all search engines support a separate title search,
// for instance the Lucene-based engine we use on Wikipedia.
// In this case, fall back to full-text search (which will
@@ -89,9 +89,10 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$matches = $search->searchText( $query );
}
}
- if ( is_null( $matches ) )
+ if ( is_null( $matches ) ) {
$this->dieUsage( "{$what} search is disabled", "search-{$what}-disabled" );
-
+ }
+
// Add search meta data to result
if ( isset( $searchInfo['totalhits'] ) ) {
$totalhits = $matches->getTotalHits();
@@ -107,7 +108,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
// Add the search results to the result
$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
- $titles = array ();
+ $titles = array();
$count = 0;
while ( $result = $matches->next() ) {
if ( ++ $count > $limit ) {
@@ -117,23 +118,53 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
// Silently skip broken and missing titles
- if ( $result->isBrokenTitle() || $result->isMissingRevision() )
+ if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
continue;
-
+ }
+
$title = $result->getTitle();
if ( is_null( $resultPageSet ) ) {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $title );
-
- if ( isset( $prop['snippet'] ) )
+
+ if ( isset( $prop['snippet'] ) ) {
$vals['snippet'] = $result->getTextSnippet( $terms );
- if ( isset( $prop['size'] ) )
+ }
+ if ( isset( $prop['size'] ) ) {
$vals['size'] = $result->getByteSize();
- if ( isset( $prop['wordcount'] ) )
+ }
+ if ( isset( $prop['wordcount'] ) ) {
$vals['wordcount'] = $result->getWordCount();
- if ( isset( $prop['timestamp'] ) )
+ }
+ if ( isset( $prop['timestamp'] ) ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $result->getTimestamp() );
-
+ }
+ if ( !is_null( $result->getScore() ) && isset( $prop['score'] ) ) {
+ $vals['score'] = $result->getScore();
+ }
+ if ( isset( $prop['titlesnippet'] ) ) {
+ $vals['titlesnippet'] = $result->getTitleSnippet( $terms );
+ }
+ if ( !is_null( $result->getRedirectTitle() ) ) {
+ if ( isset( $prop['redirecttitle'] ) ) {
+ $vals['redirecttitle'] = $result->getRedirectTitle();
+ }
+ if ( isset( $prop['redirectsnippet'] ) ) {
+ $vals['redirectsnippet'] = $result->getRedirectSnippet( $terms );
+ }
+ }
+ if ( !is_null( $result->getSectionTitle() ) ) {
+ if ( isset( $prop['sectiontitle'] ) ) {
+ $vals['sectiontitle'] = $result->getSectionTitle();
+ }
+ if ( isset( $prop['sectionsnippet'] ) ) {
+ $vals['sectionsnippet'] = $result->getSectionSnippet();
+ }
+ }
+ if ( isset( $prop['hasrelated'] ) && $result->hasRelated() ) {
+ $vals['hasrelated'] = "";
+ }
+
// Add item to results and see whether it fits
$fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ),
null, $vals );
@@ -160,77 +191,100 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- return array (
- 'search' => null,
- 'namespace' => array (
- ApiBase :: PARAM_DFLT => 0,
- ApiBase :: PARAM_TYPE => 'namespace',
- ApiBase :: PARAM_ISMULTI => true,
+ return array(
+ 'search' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'namespace' => array(
+ ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_ISMULTI => true,
),
- 'what' => array (
- ApiBase :: PARAM_DFLT => null,
- ApiBase :: PARAM_TYPE => array (
+ 'what' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_TYPE => array(
'title',
'text',
+ 'nearmatch',
)
),
'info' => array(
- ApiBase :: PARAM_DFLT => 'totalhits|suggestion',
- ApiBase :: PARAM_TYPE => array (
+ ApiBase::PARAM_DFLT => 'totalhits|suggestion',
+ ApiBase::PARAM_TYPE => array(
'totalhits',
'suggestion',
),
- ApiBase :: PARAM_ISMULTI => true,
+ ApiBase::PARAM_ISMULTI => true,
),
'prop' => array(
- ApiBase :: PARAM_DFLT => 'size|wordcount|timestamp|snippet',
- ApiBase :: PARAM_TYPE => array (
+ ApiBase::PARAM_DFLT => 'size|wordcount|timestamp|snippet',
+ ApiBase::PARAM_TYPE => array(
'size',
'wordcount',
'timestamp',
+ 'score',
'snippet',
+ 'titlesnippet',
+ 'redirecttitle',
+ 'redirectsnippet',
+ 'sectiontitle',
+ 'sectionsnippet',
+ 'hasrelated',
),
- ApiBase :: PARAM_ISMULTI => true,
+ ApiBase::PARAM_ISMULTI => true,
),
'redirects' => false,
'offset' => 0,
- 'limit' => array (
- ApiBase :: PARAM_DFLT => 10,
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => ApiBase :: LIMIT_SML1,
- ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_SML2
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_SML1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_SML2
)
);
}
public function getParamDescription() {
- return 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' => 'What properties to return.',
- 'redirects' => 'Include redirect pages in the search.',
+ return 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 - Adds the score (if any) from the search engine',
+ ' 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',
+ ' redirecttitle - Adds a parsed snippet of the redirect title',
+ ' sectionsnippet - Adds a parsed snippet of the matching section',
+ ' sectiontitle - Adds a parsed snippet of the matching section title',
+ ' hasrelated - Indicates whether a related search is available',
+ ),
+ 'redirects' => 'Include redirect pages in the search',
'offset' => 'Use this value to continue paging (return by query)',
- 'limit' => 'How many total pages to return.'
+ 'limit' => 'How many total pages to return'
);
}
public function getDescription() {
return 'Perform a full text search';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'param-search', 'info' => 'empty search string is not allowed' ),
array( 'code' => 'search-text-disabled', 'info' => 'text search is disabled' ),
array( 'code' => 'search-title-disabled', 'info' => 'title search is disabled' ),
) );
}
protected function getExamples() {
- return array (
+ 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',
@@ -238,6 +292,6 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQuerySearch.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQuerySearch.php 76300 2010-11-08 12:23:24Z reedy $';
}
}
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index 0385e192..379a4228 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 25, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 25, 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
@@ -19,8 +18,10 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
@@ -36,16 +37,14 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQuerySiteinfo extends ApiQueryBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'si' );
+ parent::__construct( $query, $moduleName, 'si' );
}
public function execute() {
$params = $this->extractRequestParams();
$done = array();
- foreach ( $params['prop'] as $p )
- {
- switch ( $p )
- {
+ foreach ( $params['prop'] as $p ) {
+ switch ( $p ) {
case 'general':
$fit = $this->appendGeneralInfo( $p );
break;
@@ -86,11 +85,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
case 'languages':
$fit = $this->appendLanguages( $p );
break;
- default :
- ApiBase :: dieDebug( __METHOD__, "Unknown prop=$p" );
+ default:
+ ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
}
- if ( !$fit )
- {
+ if ( !$fit ) {
// Abuse siprop as a query-continue parameter
// and set it to all unprocessed props
$this->setContinueEnumParameter( 'prop', implode( '|',
@@ -103,10 +101,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
protected function appendGeneralInfo( $property ) {
global $wgContLang;
- global $wgLang;
$data = array();
- $mainPage = Title :: newFromText( wfMsgForContent( 'mainpage' ) );
+ $mainPage = Title::newMainPage();
$data['mainpage'] = $mainPage->getPrefixedText();
$data['base'] = $mainPage->getFullUrl();
$data['sitename'] = $GLOBALS['wgSitename'];
@@ -117,26 +114,30 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['dbversion'] = $this->getDB()->getServerVersion();
$svn = SpecialVersion::getSvnRevision( $GLOBALS['IP'] );
- if ( $svn )
+ if ( $svn ) {
$data['rev'] = $svn;
+ }
// 'case-insensitive' option is reserved for future
$data['case'] = $GLOBALS['wgCapitalLinks'] ? 'first-letter' : 'case-sensitive';
- if ( isset( $GLOBALS['wgRightsCode'] ) )
+ if ( isset( $GLOBALS['wgRightsCode'] ) ) {
$data['rightscode'] = $GLOBALS['wgRightsCode'];
+ }
$data['rights'] = $GLOBALS['wgRightsText'];
$data['lang'] = $GLOBALS['wgLanguageCode'];
- if ( $wgContLang->isRTL() )
+ if ( $wgContLang->isRTL() ) {
$data['rtl'] = '';
- $data['fallback8bitEncoding'] = $wgLang->fallback8bitEncoding();
-
+ }
+ $data['fallback8bitEncoding'] = $wgContLang->fallback8bitEncoding();
+
if ( wfReadOnly() ) {
$data['readonly'] = '';
$data['readonlyreason'] = wfReadOnlyReason();
}
- if ( $GLOBALS['wgEnableWriteAPI'] )
+ if ( $GLOBALS['wgEnableWriteAPI'] ) {
$data['writeapi'] = '';
+ }
$tz = $GLOBALS['wgLocaltimezone'];
$offset = $GLOBALS['wgLocalTZoffset'];
@@ -162,23 +163,25 @@ class ApiQuerySiteinfo extends ApiQueryBase {
protected function appendNamespaces( $property ) {
global $wgContLang;
$data = array();
- foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title )
- {
+ 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::setContent( $data[$ns], $title );
$canonical = MWNamespace::getCanonicalName( $ns );
-
- if ( MWNamespace::hasSubpages( $ns ) )
+
+ if ( MWNamespace::hasSubpages( $ns ) ) {
$data[$ns]['subpages'] = '';
-
- if ( $canonical )
+ }
+
+ if ( $canonical ) {
$data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
-
- if ( MWNamespace::isContent( $ns ) )
+ }
+
+ if ( MWNamespace::isContent( $ns ) ) {
$data[$ns]['content'] = '';
+ }
}
$this->getResult()->setIndexedTagName( $data, 'ns' );
@@ -198,7 +201,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$item = array(
'id' => intval( $ns )
);
- ApiResult :: setContent( $item, strtr( $title, '_', ' ' ) );
+ ApiResult::setContent( $item, strtr( $title, '_', ' ' ) );
$data[] = $item;
}
@@ -207,9 +210,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
protected function appendSpecialPageAliases( $property ) {
- global $wgLang;
+ global $wgContLang;
$data = array();
- foreach ( $wgLang->getSpecialPageAliases() as $specialpage => $aliases )
+ foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases )
{
$arr = array( 'realname' => $specialpage, 'aliases' => $aliases );
$this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
@@ -218,16 +221,16 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$this->getResult()->setIndexedTagName( $data, 'specialpage' );
return $this->getResult()->addValue( 'query', $property, $data );
}
-
+
protected function appendMagicWords( $property ) {
global $wgContLang;
$data = array();
- foreach ( $wgContLang->getMagicWords() as $magicword => $aliases )
- {
+ foreach ( $wgContLang->getMagicWords() as $magicword => $aliases ) {
$caseSensitive = array_shift( $aliases );
$arr = array( 'name' => $magicword, 'aliases' => $aliases );
- if ( $caseSensitive )
+ if ( $caseSensitive ) {
$arr['case-sensitive'] = '';
+ }
$this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
$data[] = $arr;
}
@@ -240,34 +243,34 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$this->addTables( 'interwiki' );
$this->addFields( array( 'iw_prefix', 'iw_local', 'iw_url' ) );
- if ( $filter === 'local' )
+ if ( $filter === 'local' ) {
$this->addWhere( 'iw_local = 1' );
- elseif ( $filter === '!local' )
+ } elseif ( $filter === '!local' ) {
$this->addWhere( 'iw_local = 0' );
- elseif ( $filter )
- ApiBase :: dieDebug( __METHOD__, "Unknown filter=$filter" );
+ } elseif ( $filter ) {
+ ApiBase::dieDebug( __METHOD__, "Unknown filter=$filter" );
+ }
$this->addOption( 'ORDER BY', 'iw_prefix' );
- $db = $this->getDB();
$res = $this->select( __METHOD__ );
$data = array();
$langNames = Language::getLanguageNames();
- while ( $row = $db->fetchObject( $res ) )
- {
+ foreach ( $res as $row ) {
$val = array();
$val['prefix'] = $row->iw_prefix;
- if ( $row->iw_local == '1' )
+ if ( $row->iw_local == '1' ) {
$val['local'] = '';
-// $val['trans'] = intval($row->iw_trans); // should this be exposed?
- if ( isset( $langNames[$row->iw_prefix] ) )
+ }
+ // $val['trans'] = intval( $row->iw_trans ); // should this be exposed?
+ if ( isset( $langNames[$row->iw_prefix] ) ) {
$val['language'] = $langNames[$row->iw_prefix];
+ }
$val['url'] = $row->iw_url;
$data[] = $val;
}
- $db->freeResult( $res );
$this->getResult()->setIndexedTagName( $data, 'iw' );
return $this->getResult()->addValue( 'query', $property, $data );
@@ -277,8 +280,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
global $wgShowHostnames;
$data = array();
if ( $includeAll ) {
- if ( !$wgShowHostnames )
+ if ( !$wgShowHostnames ) {
$this->dieUsage( 'Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied' );
+ }
$lb = wfGetLB();
$lags = $lb->getLagTimes();
@@ -319,27 +323,52 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
protected function appendUserGroups( $property, $numberInGroup ) {
- global $wgGroupPermissions;
+ global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
+
$data = array();
foreach ( $wgGroupPermissions as $group => $permissions ) {
$arr = array(
'name' => $group,
'rights' => array_keys( $permissions, true ),
);
- if ( $numberInGroup )
- $arr['number'] = SiteStats::numberInGroup( $group );
+
+ if ( $numberInGroup ) {
+ global $wgAutopromote;
+
+ if ( $group == 'user' ) {
+ $arr['number'] = SiteStats::users();
+
+ // '*' and autopromote groups have no size
+ } elseif ( $group !== '*' && !isset( $wgAutopromote[$group] ) ) {
+ $arr['number'] = SiteStats::numberInGroup( $group );
+ }
+ }
+
+ $groupArr = array(
+ 'add' => $wgAddGroups,
+ 'remove' => $wgRemoveGroups,
+ 'add-self' => $wgGroupsAddToSelf,
+ 'remove-self' => $wgGroupsRemoveFromSelf
+ );
+
+ foreach( $groupArr as $type => $rights ) {
+ if( isset( $rights[$group] ) ) {
+ $arr[$type] = $rights[$group];
+ $this->getResult()->setIndexedTagName( $arr[$type], 'group' );
+ }
+ }
$this->getResult()->setIndexedTagName( $arr['rights'], 'permission' );
$data[] = $arr;
}
-
+
$this->getResult()->setIndexedTagName( $data, 'group' );
return $this->getResult()->addValue( 'query', $property, $data );
}
-
+
protected function appendFileExtensions( $property ) {
global $wgFileExtensions;
-
+
$data = array();
foreach ( $wgFileExtensions as $ext ) {
$data[] = array( 'ext' => $ext );
@@ -355,10 +384,12 @@ class ApiQuerySiteinfo extends ApiQueryBase {
foreach ( $extensions as $ext ) {
$ret = array();
$ret['type'] = $type;
- if ( isset( $ext['name'] ) )
+ if ( isset( $ext['name'] ) ) {
$ret['name'] = $ext['name'];
- if ( isset( $ext['description'] ) )
+ }
+ if ( isset( $ext['description'] ) ) {
$ret['description'] = $ext['description'];
+ }
if ( isset( $ext['descriptionmsg'] ) ) {
// Can be a string or array( key, param1, param2, ... )
if ( is_array( $ext['descriptionmsg'] ) ) {
@@ -428,9 +459,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function getAllowedParams() {
return array(
'prop' => array(
- ApiBase :: PARAM_DFLT => 'general',
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_DFLT => 'general',
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'general',
'namespaces',
'namespacealiases',
@@ -447,7 +478,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
)
),
'filteriw' => array(
- ApiBase :: PARAM_TYPE => array(
+ ApiBase::PARAM_TYPE => array(
'local',
'!local',
)
@@ -461,19 +492,19 @@ class ApiQuerySiteinfo extends ApiQueryBase {
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)',
- ' dbrepllag - Returns database server with the highest replication lag',
- ' usergroups - Returns user groups and the associated permissions',
- ' extensions - Returns extensions installed on the wiki',
- ' fileextensions - Returns list of file extensions allowed to be uploaded',
- ' rightsinfo - Returns wiki rights (license) information if available',
- ' languages - Returns a list of languages MediaWiki supports',
+ ' 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)',
+ ' dbrepllag - Returns database server with the highest replication lag',
+ ' usergroups - Returns user groups and the associated permissions',
+ ' extensions - Returns extensions installed on the wiki',
+ ' fileextensions - Returns list of file extensions allowed to be uploaded',
+ ' rightsinfo - Returns wiki rights (license) information if available',
+ ' languages - Returns a list of languages MediaWiki supports',
),
'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
'showalldb' => 'List all database servers, not just the one lagging the most',
@@ -482,7 +513,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
public function getDescription() {
- return 'Return general information about the site.';
+ return 'Return general information about the site';
}
public function getPossibleErrors() {
@@ -495,11 +526,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
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',
- );
+ 'api.php?action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb=',
+ );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 77192 2010-11-23 22:05:27Z btongminh $';
}
}
diff --git a/includes/api/ApiQueryStashImageInfo.php b/includes/api/ApiQueryStashImageInfo.php
new file mode 100644
index 00000000..769b3e9d
--- /dev/null
+++ b/includes/api/ApiQueryStashImageInfo.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * API for MediaWiki 1.16+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 query action to get image information from temporarily stashed files.
+ *
+ * @ingroup API
+ */
+class ApiQueryStashImageInfo extends ApiQueryImageInfo {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'sii' );
+ }
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $modulePrefix = $this->getModulePrefix();
+
+ $prop = array_flip( $params['prop'] );
+
+ $scale = $this->getScale( $params );
+
+ $result = $this->getResult();
+
+ try {
+ $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
+
+ foreach ( $params['sessionkey'] as $sessionkey ) {
+ $file = $stash->getFile( $sessionkey );
+ $imageInfo = self::getInfo( $file, $prop, $result, $scale );
+ $result->addValue( array( 'query', $this->getModuleName() ), null, $imageInfo );
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $modulePrefix );
+ }
+
+ } 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 ) {
+ $this->dieUsage( "Bad path: " . $e->getMessage(), "invalidsessiondata" );
+ }
+
+ }
+
+ /**
+ * Returns all valid parameters to siiprop
+ */
+ public static function getPropertyNames() {
+ return array(
+ 'timestamp',
+ 'url',
+ 'size',
+ 'dimensions', // For backwards compatibility with Allimages
+ 'sha1',
+ 'mime',
+ 'thumbmime',
+ 'metadata',
+ 'bitdepth',
+ );
+ }
+
+
+ public function getAllowedParams() {
+ return array(
+ 'sessionkey' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_REQUIRED => true,
+ ApiBase::PARAM_DFLT => null
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'timestamp|url',
+ ApiBase::PARAM_TYPE => self::getPropertyNames()
+ ),
+ 'urlwidth' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_DFLT => -1
+ ),
+ 'urlheight' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_DFLT => -1
+ )
+ );
+ }
+
+ /**
+ * Return the API documentation for the parameters.
+ * @return {Array} parameter documentation.
+ */
+ public function getParamDescription() {
+ $p = $this->getModulePrefix();
+ return array(
+ 'prop' => array(
+ 'What image information to get:',
+ ' timestamp - Adds timestamp for the uploaded version',
+ ' url - Gives URL to the image and the description page',
+ ' size - Adds the size of the image in bytes and the height and width',
+ ' dimensions - Alias for size',
+ ' sha1 - Adds sha1 hash for the image',
+ ' mime - Adds MIME of the image',
+ ' thumbmime - Adss MIME of the image thumbnail (requires url)',
+ ' metadata - Lists EXIF metadata for the version of the image',
+ ' bitdepth - Adds the bit depth of the version',
+ ),
+ 'sessionkey' => 'Session key that identifies a previous upload that was stashed temporarily.',
+ '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"
+ );
+ }
+
+ public function getDescription() {
+ return 'Returns image information for stashed images';
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => 'siiurlwidth', 'info' => 'siiurlheight cannot be used without iiurlwidth' ),
+ ) );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=query&prop=stashimageinfo&siisessionkey=124sd34rsdf567',
+ 'api.php?action=query&prop=stashimageinfo&siisessionkey=b34edoe3|bceffd4&siiurlwidth=120&siiprop=url',
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryStashImageInfo.php 81000 2011-01-25 22:49:34Z catrope $';
+ }
+
+}
+
diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php
index a5d152bc..e88ec9b5 100644
--- a/includes/api/ApiQueryTags.php
+++ b/includes/api/ApiQueryTags.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Jul 9, 2009
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2009
+ * Created on Jul 9, 2009
+ *
+ * Copyright © 2009
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -34,148 +35,154 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* @ingroup API
*/
class ApiQueryTags extends ApiQueryBase {
-
+
private $limit, $result;
private $fld_displayname = false, $fld_description = false,
$fld_hitcount = false;
-
+
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'tg' );
+ parent::__construct( $query, $moduleName, 'tg' );
}
public function execute() {
$params = $this->extractRequestParams();
-
+
$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();
-
- $pageSet = $this->getPageSet();
- $titles = $pageSet->getTitles();
- $data = array();
-
+
$this->addTables( 'change_tag' );
$this->addFields( 'ct_tag' );
-
- if ( $this->fld_hitcount )
+
+ if ( $this->fld_hitcount ) {
$this->addFields( 'count(*) AS hitcount' );
-
+ }
+
$this->addOption( 'LIMIT', $this->limit + 1 );
$this->addOption( 'GROUP BY', 'ct_tag' );
$this->addWhereRange( 'ct_tag', 'newer', $params['continue'], null );
-
+
$res = $this->select( __METHOD__ );
-
+
$ok = true;
-
- while ( $row = $res->fetchObject() ) {
- if ( !$ok ) break;
+
+ foreach ( $res as $row ) {
+ if ( !$ok ) {
+ break;
+ }
$ok = $this->doTag( $row->ct_tag, $row->hitcount );
}
-
+
// include tags with no hits yet
foreach ( ChangeTags::listDefinedTags() as $tag ) {
- if ( !$ok ) break;
+ if ( !$ok ) {
+ 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();
-
+
if ( in_array( $tagName, $doneTags ) ) {
return true;
}
-
- if ( ++$count > $this->limit )
- {
+
+ if ( ++$count > $this->limit ) {
$this->setContinueEnumParameter( 'continue', $tagName );
return false;
}
-
+
$tag = array();
$tag['name'] = $tagName;
-
- if ( $this->fld_displayname )
+
+ if ( $this->fld_displayname ) {
$tag['displayname'] = ChangeTags::tagDescription( $tagName );
-
- if ( $this->fld_description )
- {
+ }
+
+ if ( $this->fld_description ) {
$msg = wfMsg( "tag-$tagName-description" );
$msg = wfEmptyMsg( "tag-$tagName-description", $msg ) ? '' : $msg;
$tag['description'] = $msg;
}
-
- if ( $this->fld_hitcount )
+
+ if ( $this->fld_hitcount ) {
$tag['hitcount'] = $hitcount;
-
+ }
+
$doneTags[] = $tagName;
-
+
$fit = $this->result->addValue( array( 'query', $this->getModuleName() ), null, $tag );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $tagName );
return false;
}
-
+
return true;
}
-
+
public function getCacheMode( $params ) {
return 'public';
}
public function getAllowedParams() {
- return array (
+ return array(
'continue' => 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
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'prop' => array(
- ApiBase :: PARAM_DFLT => 'name',
- ApiBase :: PARAM_TYPE => array(
- 'name',
- 'displayname',
- 'description',
- 'hitcount'
- ),
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_DFLT => 'name',
+ ApiBase::PARAM_TYPE => array(
+ 'name',
+ 'displayname',
+ 'description',
+ 'hitcount'
+ ),
+ ApiBase::PARAM_ISMULTI => true
)
);
}
public function getParamDescription() {
- return array (
+ return array(
'continue' => 'When more results are available, use this to continue',
'limit' => 'The maximum number of tags to list',
- 'prop' => 'Which properties to get',
+ 'prop' => array(
+ 'Which properties to get',
+ ' name - Adds name of tag',
+ ' displayname - Adds system messsage 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.';
+ return 'List change tags';
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&list=tags&tgprop=displayname|description|hitcount'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryTags.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryTags.php 73858 2010-09-28 01:21:15Z reedy $';
}
}
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
index b51b9adb..5d63fa60 100644
--- a/includes/api/ApiQueryUserContributions.php
+++ b/includes/api/ApiQueryUserContributions.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 16, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Oct 16, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -36,13 +37,13 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryContributions extends ApiQueryBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'uc' );
+ parent::__construct( $query, $moduleName, 'uc' );
}
- private $params, $username;
+ private $params, $prefixMode, $userprefix, $multiUserMode, $usernames;
private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
$fld_comment = false, $fld_parsedcomment = false, $fld_flags = false,
- $fld_patrolled = false, $fld_tags = false;
+ $fld_patrolled = false, $fld_tags = false, $fld_size = false;
public function execute() {
// Parse some parameters
@@ -61,23 +62,22 @@ class ApiQueryContributions extends ApiQueryBase {
// TODO: if the query is going only against the revision table, should this be done?
$this->selectNamedDB( 'contributions', DB_SLAVE, 'contributions' );
- $db = $this->getDB();
- if ( isset( $this->params['userprefix'] ) )
- {
+ if ( isset( $this->params['userprefix'] ) ) {
$this->prefixMode = true;
$this->multiUserMode = true;
$this->userprefix = $this->params['userprefix'];
- }
- else
- {
+ } else {
$this->usernames = array();
- if ( !is_array( $this->params['user'] ) )
+ if ( !is_array( $this->params['user'] ) ) {
$this->params['user'] = array( $this->params['user'] );
- if ( !count( $this->params['user'] ) )
+ }
+ if ( !count( $this->params['user'] ) ) {
$this->dieUsage( 'User parameter may not be empty.', 'param_user' );
- foreach ( $this->params['user'] as $u )
+ }
+ foreach ( $this->params['user'] as $u ) {
$this->prepareUsername( $u );
+ }
$this->prefixMode = false;
$this->multiUserMode = ( count( $this->params['user'] ) > 1 );
}
@@ -91,31 +91,29 @@ class ApiQueryContributions extends ApiQueryBase {
$limit = $this->params['limit'];
// Fetch each row
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
- if ( $this->multiUserMode )
+ if ( $this->multiUserMode ) {
$this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
- else
+ } else {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rev_timestamp ) );
+ }
break;
}
$vals = $this->extractRowInfo( $row );
$fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals );
- if ( !$fit )
- {
- if ( $this->multiUserMode )
+ if ( !$fit ) {
+ if ( $this->multiUserMode ) {
$this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
- else
+ } else {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rev_timestamp ) );
+ }
break;
}
}
- // Free the database record so the connection can get on with other stuff
- $db->freeResult( $res );
-
$this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
}
@@ -150,32 +148,37 @@ class ApiQueryContributions extends ApiQueryBase {
$this->addWhere( 'page_id=rev_page' );
// Handle continue parameter
- if ( $this->multiUserMode && !is_null( $this->params['continue'] ) )
- {
+ if ( $this->multiUserMode && !is_null( $this->params['continue'] ) ) {
$continue = explode( '|', $this->params['continue'] );
- if ( count( $continue ) != 2 )
- $this->dieUsage( "Invalid continue param. You should pass the original " .
- "value returned by the previous query", "_badcontinue" );
+ if ( count( $continue ) != 2 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the original ' .
+ 'value returned by the previous query', '_badcontinue' );
+ }
$encUser = $this->getDB()->strencode( $continue[0] );
$encTS = wfTimestamp( TS_MW, $continue[1] );
$op = ( $this->params['dir'] == 'older' ? '<' : '>' );
- $this->addWhere( "rev_user_text $op '$encUser' OR " .
- "(rev_user_text = '$encUser' AND " .
- "rev_timestamp $op= '$encTS')" );
+ $this->addWhere(
+ "rev_user_text $op '$encUser' OR " .
+ "(rev_user_text = '$encUser' AND " .
+ "rev_timestamp $op= '$encTS')"
+ );
}
- if ( !$wgUser->isAllowed( 'hideuser' ) )
+ if ( !$wgUser->isAllowed( 'hideuser' ) ) {
$this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
+ }
// We only want pages by the specified users.
- if ( $this->prefixMode )
+ if ( $this->prefixMode ) {
$this->addWhere( 'rev_user_text' . $this->getDB()->buildLike( $this->userprefix, $this->getDB()->anyString() ) );
- else
+ } else {
$this->addWhereFld( 'rev_user_text', $this->usernames );
+ }
// ... and in the specified timeframe.
// Ensure the same sort order for rev_user_text and rev_timestamp
// so our query is indexed
- if ( $this->multiUserMode )
+ if ( $this->multiUserMode ) {
$this->addWhereRange( 'rev_user_text', $this->params['dir'], null, null );
+ }
$this->addWhereRange( 'rev_timestamp',
$this->params['dir'], $this->params['start'], $this->params['end'] );
$this->addWhereFld( 'page_namespace', $this->params['namespace'] );
@@ -184,8 +187,9 @@ class ApiQueryContributions extends ApiQueryBase {
if ( !is_null( $show ) ) {
$show = array_flip( $show );
if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
- || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) )
+ || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) ) {
$this->dieUsageMsg( array( 'show' ) );
+ }
$this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) );
$this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) );
@@ -202,22 +206,22 @@ class ApiQueryContributions extends ApiQueryBase {
'rev_timestamp',
'page_namespace',
'page_title',
+ 'rev_user',
'rev_user_text',
'rev_deleted'
) );
-
+
if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
- $this->fld_patrolled )
- {
- global $wgUser;
- if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() )
- $this->dieUsage( "You need the patrol right to request the patrolled flag", 'permissiondenied' );
+ $this->fld_patrolled ) {
+ if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
+ $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
+ }
+
// Use a redundant join condition on both
// timestamp and ID so we can use the timestamp
// index
$index['recentchanges'] = 'rc_user_text';
- if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) )
- {
+ if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
// Put the tables in the right order for
// STRAIGHT_JOIN
$tables = array( 'revision', 'recentchanges', 'page' );
@@ -225,9 +229,7 @@ class ApiQueryContributions extends ApiQueryBase {
$this->addWhere( 'rc_user_text=rev_user_text' );
$this->addWhere( 'rc_timestamp=rev_timestamp' );
$this->addWhere( 'rc_this_oldid=rev_id' );
- }
- else
- {
+ } else {
$tables[] = 'recentchanges';
$this->addJoinConds( array( 'recentchanges' => array(
'LEFT JOIN', array(
@@ -241,28 +243,27 @@ class ApiQueryContributions extends ApiQueryBase {
$this->addFieldsIf( 'rev_page', $this->fld_ids );
$this->addFieldsIf( 'rev_id', $this->fld_ids || $this->fld_flags );
$this->addFieldsIf( 'page_latest', $this->fld_flags );
- // $this->addFieldsIf('rev_text_id', $this->fld_ids); // Should this field be exposed?
+ // $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed?
$this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'rev_len', $this->fld_size );
$this->addFieldsIf( 'rev_minor_edit', $this->fld_flags );
$this->addFieldsIf( 'rev_parent_id', $this->fld_flags );
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
-
- if ( $this->fld_tags )
- {
+
+ if ( $this->fld_tags ) {
$this->addTables( 'tag_summary' );
$this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) ) );
$this->addFields( 'ts_tags' );
}
-
+
if ( isset( $this->params['tag'] ) ) {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
$this->addWhereFld( 'ct_tag', $this->params['tag'] );
global $wgOldChangeTagsIndex;
- $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
+ $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
}
-
+
$this->addOption( 'USE INDEX', $index );
}
@@ -270,42 +271,49 @@ class ApiQueryContributions extends ApiQueryBase {
* Extract fields from the database row and append them to a result array
*/
private function extractRowInfo( $row ) {
-
$vals = array();
+ $vals['userid'] = $row->rev_user;
$vals['user'] = $row->rev_user_text;
- if ( $row->rev_deleted & Revision::DELETED_USER )
+ if ( $row->rev_deleted & Revision::DELETED_USER ) {
$vals['userhidden'] = '';
+ }
if ( $this->fld_ids ) {
$vals['pageid'] = intval( $row->rev_page );
$vals['revid'] = intval( $row->rev_id );
- // $vals['textid'] = intval($row->rev_text_id); // todo: Should this field be exposed?
+ // $vals['textid'] = intval( $row->rev_text_id ); // todo: Should this field be exposed?
}
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
- if ( $this->fld_title )
+ if ( $this->fld_title ) {
ApiQueryBase::addTitleInfo( $vals, $title );
+ }
- if ( $this->fld_timestamp )
+ if ( $this->fld_timestamp ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
+ }
if ( $this->fld_flags ) {
- if ( $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id ) )
+ if ( $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id ) ) {
$vals['new'] = '';
- if ( $row->rev_minor_edit )
+ }
+ if ( $row->rev_minor_edit ) {
$vals['minor'] = '';
- if ( $row->page_latest == $row->rev_id )
+ }
+ if ( $row->page_latest == $row->rev_id ) {
$vals['top'] = '';
+ }
}
if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->rev_comment ) ) {
- if ( $row->rev_deleted & Revision::DELETED_COMMENT )
+ if ( $row->rev_deleted & Revision::DELETED_COMMENT ) {
$vals['commenthidden'] = '';
- else {
- if ( $this->fld_comment )
+ } else {
+ if ( $this->fld_comment ) {
$vals['comment'] = $row->rev_comment;
-
+ }
+
if ( $this->fld_parsedcomment ) {
global $wgUser;
$vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->rev_comment, $title );
@@ -313,11 +321,13 @@ class ApiQueryContributions extends ApiQueryBase {
}
}
- if ( $this->fld_patrolled && $row->rc_patrolled )
+ if ( $this->fld_patrolled && $row->rc_patrolled ) {
$vals['patrolled'] = '';
-
- if ( $this->fld_size && !is_null( $row->rev_len ) )
+ }
+
+ if ( $this->fld_size && !is_null( $row->rev_len ) ) {
$vals['size'] = intval( $row->rev_len );
+ }
if ( $this->fld_tags ) {
if ( $row->ts_tags ) {
@@ -328,12 +338,11 @@ class ApiQueryContributions extends ApiQueryBase {
$vals['tags'] = array();
}
}
-
+
return $vals;
}
-
- private function continueStr( $row )
- {
+
+ private function continueStr( $row ) {
return $row->rev_user_text . '|' .
wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
}
@@ -345,40 +354,40 @@ class ApiQueryContributions extends ApiQueryBase {
}
public function getAllowedParams() {
- 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
+ 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
),
- 'start' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ 'start' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
- 'end' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ 'end' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'continue' => null,
- 'user' => array (
- ApiBase :: PARAM_ISMULTI => true
+ 'user' => array(
+ ApiBase::PARAM_ISMULTI => true
),
'userprefix' => null,
- 'dir' => array (
- ApiBase :: PARAM_DFLT => 'older',
- ApiBase :: PARAM_TYPE => array (
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_TYPE => array(
'newer',
'older'
)
),
- 'namespace' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => 'namespace'
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace'
),
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_DFLT => 'ids|title|timestamp|comment|size|flags',
- ApiBase :: PARAM_TYPE => array (
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'ids|title|timestamp|comment|size|flags',
+ ApiBase::PARAM_TYPE => array(
'ids',
'title',
'timestamp',
@@ -390,9 +399,9 @@ class ApiQueryContributions extends ApiQueryBase {
'tags'
)
),
- 'show' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ 'show' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'minor',
'!minor',
'patrolled',
@@ -404,18 +413,31 @@ class ApiQueryContributions extends ApiQueryBase {
}
public function getParamDescription() {
- 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 user to retrieve contributions for.',
- 'userprefix' => 'Retrieve contibutions for all users whose names begin with this value. Overrides ucuser.',
- 'dir' => 'The direction to search (older or newer).',
+ global $wgRCMaxAge;
+ $p = $this->getModulePrefix();
+ return array(
+ 'limit' => 'The maximum number of contributions to return',
+ 'start' => 'The start timestamp to return from',
+ 'end' => 'The end timestamp to return to',
+ 'continue' => 'When more results are available, use this to continue',
+ 'user' => 'The users to retrieve contributions for',
+ 'userprefix' => "Retrieve contibutions for all users whose names begin with this value. Overrides {$p}user",
+ 'dir' => 'The direction to search (older or newer)',
'namespace' => 'Only list contributions in these namespaces',
- 'prop' => 'Include additional pieces of information',
- 'show' => array( 'Show only items that meet this criteria, e.g. non minor edits only: show=!minor',
- 'NOTE: if show=patrolled or show=!patrolled is set, revisions older than $wgRCMaxAge won\'t be shown', ),
+ '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 size of the page',
+ ' flags - Adds flags of the edit',
+ ' patrolled - Tags patrolled edits',
+ ' tags - Lists tags for the edit',
+ ),
+ 'show' => array( "Show only items that meet this criteria, e.g. non minor edits only: {$p}show=!minor",
+ "NOTE: if {$p}show=patrolled or {$p}show=!patrolled is set, revisions older than $wgRCMaxAge won\'t be shown", ),
'tag' => 'Only list revisions tagged with this tag',
);
}
@@ -423,7 +445,7 @@ class ApiQueryContributions extends ApiQueryBase {
public function getDescription() {
return 'Get all edits by a user';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'param_user', 'info' => 'User parameter may not be empty.' ),
@@ -434,13 +456,13 @@ class ApiQueryContributions extends ApiQueryBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&list=usercontribs&ucuser=YurikBot',
'api.php?action=query&list=usercontribs&ucuserprefix=217.121.114.',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryUserContributions.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryUserContributions.php 75096 2010-10-20 18:50:33Z reedy $';
}
}
diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php
index 42cb47b9..ec7b74b3 100644
--- a/includes/api/ApiQueryUserInfo.php
+++ b/includes/api/ApiQueryUserInfo.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on July 30, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on July 30, 2007
+ *
+ * Copyright © 2007 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -35,33 +36,34 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiQueryUserInfo extends ApiQueryBase {
+ private $prop = array();
+
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'ui' );
+ parent::__construct( $query, $moduleName, 'ui' );
}
public function execute() {
$params = $this->extractRequestParams();
$result = $this->getResult();
- $r = array();
if ( !is_null( $params['prop'] ) ) {
$this->prop = array_flip( $params['prop'] );
- } else {
- $this->prop = array();
}
+
$r = $this->getCurrentUserInfo();
- $result->addValue( "query", $this->getModuleName(), $r );
+ $result->addValue( 'query', $this->getModuleName(), $r );
}
protected function getCurrentUserInfo() {
- global $wgUser;
+ global $wgUser, $wgRequest;
$result = $this->getResult();
$vals = array();
$vals['id'] = intval( $wgUser->getId() );
$vals['name'] = $wgUser->getName();
- if ( $wgUser->isAnon() )
+ if ( $wgUser->isAnon() ) {
$vals['anon'] = '';
+ }
if ( isset( $this->prop['blockinfo'] ) ) {
if ( $wgUser->isBlocked() ) {
@@ -75,7 +77,9 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
if ( isset( $this->prop['groups'] ) ) {
- $vals['groups'] = $wgUser->getGroups();
+ $autolist = ApiQueryUsers::getAutoGroups( $wgUser );
+
+ $vals['groups'] = array_merge( $autolist, $wgUser->getGroups() );
$result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty
}
@@ -97,7 +101,11 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['options'] = $wgUser->getOptions();
}
- if ( isset( $this->prop['preferencestoken'] ) && is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
+ if (
+ isset( $this->prop['preferencestoken'] ) &&
+ is_null( $this->getMain()->getRequest()->getVal( 'callback' ) )
+ )
+ {
$vals['preferencestoken'] = $wgUser->editToken();
}
@@ -112,26 +120,39 @@ class ApiQueryUserInfo extends ApiQueryBase {
if ( isset( $this->prop['email'] ) ) {
$vals['email'] = $wgUser->getEmail();
$auth = $wgUser->getEmailAuthenticationTimestamp();
- if ( !is_null( $auth ) )
+ if ( !is_null( $auth ) ) {
$vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth );
+ }
+ }
+
+ if ( isset( $this->prop['acceptlang'] ) ) {
+ $langs = $wgRequest->getAcceptLang();
+ $acceptLang = array();
+ foreach ( $langs as $lang => $val ) {
+ $r = array( 'q' => $val );
+ ApiResult::setContent( $r, $lang );
+ $acceptLang[] = $r;
+ }
+ $result->setIndexedTagName( $acceptLang, 'lang' );
+ $vals['acceptlang'] = $acceptLang;
}
return $vals;
}
- protected function getRateLimits()
- {
+ protected function getRateLimits() {
global $wgUser, $wgRateLimits;
- if ( !$wgUser->isPingLimitable() )
+ if ( !$wgUser->isPingLimitable() ) {
return array(); // No limits
+ }
// Find out which categories we belong to
$categories = array();
- if ( $wgUser->isAnon() )
+ if ( $wgUser->isAnon() ) {
$categories[] = 'anon';
- else
+ } else {
$categories[] = 'user';
- if ( $wgUser->isNewBie() )
- {
+ }
+ if ( $wgUser->isNewbie() ) {
$categories[] = 'ip';
$categories[] = 'subnet';
if ( !$wgUser->isAnon() )
@@ -141,22 +162,23 @@ class ApiQueryUserInfo extends ApiQueryBase {
// Now get the actual limits
$retval = array();
- foreach ( $wgRateLimits as $action => $limits )
- foreach ( $categories as $cat )
- if ( isset( $limits[$cat] ) && !is_null( $limits[$cat] ) )
- {
+ foreach ( $wgRateLimits as $action => $limits ) {
+ foreach ( $categories as $cat ) {
+ if ( isset( $limits[$cat] ) && !is_null( $limits[$cat] ) ) {
$retval[$action][$cat]['hits'] = intval( $limits[$cat][0] );
$retval[$action][$cat]['seconds'] = intval( $limits[$cat][1] );
}
+ }
+ }
return $retval;
}
public function getAllowedParams() {
- return array (
- 'prop' => array (
- ApiBase :: PARAM_DFLT => null,
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'blockinfo',
'hasmsg',
'groups',
@@ -167,23 +189,26 @@ class ApiQueryUserInfo extends ApiQueryBase {
'editcount',
'ratelimits',
'email',
+ 'acceptlang',
)
)
);
}
public function getParamDescription() {
- return array (
+ 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',
- ' 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',
- ' editcount - adds the current user\'s edit count',
- ' ratelimits - lists all rate limits applying to the current user'
+ ' 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',
+ ' 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',
+ ' editcount - Adds the current user\'s edit count',
+ ' ratelimits - Lists all rate limits applying to the current user',
+ ' 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',
)
);
}
@@ -193,13 +218,13 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&meta=userinfo',
'api.php?action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryUserInfo.php 69578 2010-07-20 02:46:20Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryUserInfo.php 75937 2010-11-03 17:01:21Z reedy $';
}
}
diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php
index 5dc0e4a6..2619d200 100644
--- a/includes/api/ApiQueryUsers.php
+++ b/includes/api/ApiQueryUsers.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on July 30, 2007
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on July 30, 2007
+ *
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -35,24 +36,28 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiQueryUsers extends ApiQueryBase {
+ private $tokenFunctions, $prop;
+
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'us' );
+ parent::__construct( $query, $moduleName, 'us' );
}
-
+
/**
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($user)
* it should return a token or false (permission denied)
- * @return array(tokenname => function)
+ * @return Array tokenname => function
*/
protected function getTokenFunctions() {
// Don't call the hooks twice
- if ( isset( $this->tokenFunctions ) )
+ if ( isset( $this->tokenFunctions ) ) {
return $this->tokenFunctions;
+ }
// If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) )
+ if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
return array();
+ }
$this->tokenFunctions = array(
'userrights' => array( 'ApiQueryUsers', 'getUserrightsToken' ),
@@ -60,9 +65,8 @@ if ( !defined( 'MEDIAWIKI' ) ) {
wfRunHooks( 'APIQueryUsersTokens', array( &$this->tokenFunctions ) );
return $this->tokenFunctions;
}
-
- public static function getUserrightsToken( $user )
- {
+
+ public static function getUserrightsToken( $user ) {
global $wgUser;
// Since the permissions check for userrights is non-trivial,
// don't bother with it here
@@ -71,8 +75,6 @@ if ( !defined( 'MEDIAWIKI' ) ) {
public function execute() {
$params = $this->extractRequestParams();
- $result = $this->getResult();
- $r = array();
if ( !is_null( $params['prop'] ) ) {
$this->prop = array_flip( $params['prop'] );
@@ -86,27 +88,23 @@ if ( !defined( 'MEDIAWIKI' ) ) {
// Canonicalize user names
foreach ( $users as $u ) {
$n = User::getCanonicalName( $u );
- if ( $n === false || $n === '' )
- {
+ if ( $n === false || $n === '' ) {
$vals = array( 'name' => $u, 'invalid' => '' );
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'users',
implode( '|', array_diff( $users, $done ) ) );
$goodNames = array();
break;
}
$done[] = $u;
- }
- else
+ } else {
$goodNames[] = $n;
+ }
}
- if ( count( $goodNames ) )
- {
- $db = $this->getDb();
+ if ( count( $goodNames ) ) {
$this->addTables( 'user', 'u1' );
$this->addFields( 'u1.*' );
$this->addWhereFld( 'u1.user_name', $goodNames );
@@ -116,35 +114,50 @@ if ( !defined( 'MEDIAWIKI' ) ) {
$this->addJoinConds( array( 'user_groups' => array( 'LEFT JOIN', 'ug_user=u1.user_id' ) ) );
$this->addFields( 'ug_group' );
}
- if ( isset( $this->prop['blockinfo'] ) ) {
- $this->addTables( 'ipblocks' );
- $this->addTables( 'user', 'u2' );
- $u2 = $this->getAliasedName( 'user', 'u2' );
- $this->addJoinConds( array(
- 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=u1.user_id' ),
- $u2 => array( 'LEFT JOIN', 'ipb_by=u2.user_id' ) ) );
- $this->addFields( array( 'ipb_reason', 'u2.user_name AS blocker_name' ) );
- }
+
+ $this->showHiddenUsersAddBlockInfo( isset( $this->prop['blockinfo'] ) );
$data = array();
$res = $this->select( __METHOD__ );
- while ( ( $r = $db->fetchObject( $res ) ) ) {
- $user = User::newFromRow( $r );
+ foreach ( $res as $row ) {
+ $user = User::newFromRow( $row );
$name = $user->getName();
$data[$name]['name'] = $name;
- if ( isset( $this->prop['editcount'] ) )
+
+ if ( isset( $this->prop['editcount'] ) ) {
$data[$name]['editcount'] = intval( $user->getEditCount() );
- if ( isset( $this->prop['registration'] ) )
+ }
+
+ if ( isset( $this->prop['registration'] ) ) {
$data[$name]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() );
- if ( isset( $this->prop['groups'] ) && !is_null( $r->ug_group ) )
+ }
+
+ if ( isset( $this->prop['groups'] ) && !is_null( $row->ug_group ) ) {
// This row contains only one group, others will be added from other rows
- $data[$name]['groups'][] = $r->ug_group;
- if ( isset( $this->prop['blockinfo'] ) && !is_null( $r->blocker_name ) ) {
- $data[$name]['blockedby'] = $r->blocker_name;
- $data[$name]['blockreason'] = $r->ipb_reason;
+ $data[$name]['groups'][] = $row->ug_group;
}
- if ( isset( $this->prop['emailable'] ) && $user->canReceiveEmail() )
+
+ if ( isset( $this->prop['rights'] ) && !is_null( $row->ug_group ) ) {
+ if ( !isset( $data[$name]['rights'] ) ) {
+ $data[$name]['rights'] = User::getGroupPermissions( User::getImplicitGroups() );
+ }
+
+ $data[$name]['rights'] = array_unique( array_merge( $data[$name]['rights'],
+ User::getGroupPermissions( array( $row->ug_group ) ) ) );
+ $result->setIndexedTagName( $data[$name]['rights'], 'r' );
+ }
+ if ( $row->ipb_deleted ) {
+ $data[$name]['hidden'] = '';
+ }
+ if ( isset( $this->prop['blockinfo'] ) && !is_null( $row->ipb_by_text ) ) {
+ $data[$name]['blockedby'] = $row->ipb_by_text;
+ $data[$name]['blockreason'] = $row->ipb_reason;
+ $data[$name]['blockexpiry'] = $row->ipb_expiry;
+ }
+
+ if ( isset( $this->prop['emailable'] ) && $user->canReceiveEmail() ) {
$data[$name]['emailable'] = '';
+ }
if ( isset( $this->prop['gender'] ) ) {
$gender = $user->getOption( 'gender' );
@@ -154,16 +167,15 @@ if ( !defined( 'MEDIAWIKI' ) ) {
$data[$name]['gender'] = $gender;
}
- if ( !is_null( $params['token'] ) )
- {
+ if ( !is_null( $params['token'] ) ) {
$tokenFunctions = $this->getTokenFunctions();
- foreach ( $params['token'] as $t )
- {
+ foreach ( $params['token'] as $t ) {
$val = call_user_func( $tokenFunctions[$t], $user );
- if ( $val === false )
+ if ( $val === false ) {
$this->setWarning( "Action '$t' is not allowed for the current user" );
- else
+ } else {
$data[$name][$t . 'token'] = $val;
+ }
}
}
}
@@ -174,30 +186,37 @@ if ( !defined( 'MEDIAWIKI' ) ) {
$data[$u] = array( 'name' => $u );
$urPage = new UserrightsPage;
$iwUser = $urPage->fetchUser( $u );
+
if ( $iwUser instanceof UserRightsProxy ) {
$data[$u]['interwiki'] = '';
- if ( !is_null( $params['token'] ) )
- {
+
+ if ( !is_null( $params['token'] ) ) {
$tokenFunctions = $this->getTokenFunctions();
- foreach ( $params['token'] as $t )
- {
+
+ foreach ( $params['token'] as $t ) {
$val = call_user_func( $tokenFunctions[$t], $iwUser );
- if ( $val === false )
+ if ( $val === false ) {
$this->setWarning( "Action '$t' is not allowed for the current user" );
- else
+ } else {
$data[$u][$t . 'token'] = $val;
+ }
}
}
- } else
+ } else {
$data[$u]['missing'] = '';
+ }
} else {
- if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) )
+ if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
+ $autolist = ApiQueryUsers::getAutoGroups( User::newFromName( $u ) );
+
+ $data[$u]['groups'] = array_merge( $autolist, $data[$u]['groups'] );
+
$this->getResult()->setIndexedTagName( $data[$u]['groups'], 'g' );
+ }
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $data[$u] );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'users',
implode( '|', array_diff( $users, $done ) ) );
break;
@@ -207,20 +226,34 @@ if ( !defined( 'MEDIAWIKI' ) ) {
return $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
}
+ /**
+ * Gets all the groups that a user is automatically a member of
+ * @return array
+ */
+ public static function getAutoGroups( $user ) {
+ $groups = array( '*' );
+
+ if ( !$user->isAnon() ) {
+ $groups[] = 'user';
+ }
+
+ return array_merge( $groups, Autopromote::getAutopromoteGroups( $user ) );
+ }
+
public function getCacheMode( $params ) {
if ( isset( $params['token'] ) ) {
return 'private';
} else {
- return 'public';
+ return 'anon-public-user-private';
}
}
public function getAllowedParams() {
- return array (
- 'prop' => array (
- ApiBase :: PARAM_DFLT => null,
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'blockinfo',
'groups',
'editcount',
@@ -230,25 +263,26 @@ if ( !defined( 'MEDIAWIKI' ) ) {
)
),
'users' => array(
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true
),
'token' => array(
- ApiBase :: PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
+ ApiBase::PARAM_ISMULTI => true
),
);
}
public function getParamDescription() {
- return array (
+ 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 belongs to',
- ' editcount - adds the user\'s edit count',
- ' registration - adds the user\'s registration timestamp',
- ' emailable - tags if the user can and wants to receive e-mail through [[Special:Emailuser]]',
- ' gender - tags the gender of the user. Returns "male", "female", or "unknown"',
+ ' blockinfo - Tags if the user is blocked, by whom, and for what reason',
+ ' groups - Lists all the groups the user(s) belongs to',
+ ' 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 e-mail 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',
@@ -264,6 +298,6 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryUsers.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryUsers.php 85354 2011-04-04 18:25:31Z demon $';
}
}
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
index caac0706..784f89c0 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 25, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 25, 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -37,7 +38,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryWatchlist extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'wl' );
+ parent::__construct( $query, $moduleName, 'wl' );
}
public function execute() {
@@ -50,38 +51,23 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
private $fld_ids = false, $fld_title = false, $fld_patrol = false, $fld_flags = false,
$fld_timestamp = false, $fld_user = false, $fld_comment = false, $fld_parsedcomment = false, $fld_sizes = false,
- $fld_notificationtimestamp = false;
+ $fld_notificationtimestamp = false, $fld_userid = false;
private function run( $resultPageSet = null ) {
- global $wgUser;
-
$this->selectNamedDB( 'watchlist', DB_SLAVE, 'watchlist' );
$params = $this->extractRequestParams();
- if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
- $user = User::newFromName( $params['owner'], false );
- if ( !$user->getId() ) {
- $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
- }
- $token = $user->getOption( 'watchlisttoken' );
- if ( $token == '' || $token != $params['token'] ) {
- $this->dieUsage( 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', 'bad_wltoken' );
- }
- } elseif ( !$wgUser->isLoggedIn() ) {
- $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
- } else {
- $user = $wgUser;
- }
+ $user = $this->getWatchlistUser( $params );
if ( !is_null( $params['prop'] ) && is_null( $resultPageSet ) ) {
-
$prop = array_flip( $params['prop'] );
$this->fld_ids = isset( $prop['ids'] );
$this->fld_title = isset( $prop['title'] );
$this->fld_flags = isset( $prop['flags'] );
$this->fld_user = isset( $prop['user'] );
+ $this->fld_userid = isset( $prop['userid'] );
$this->fld_comment = isset( $prop['comment'] );
$this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
$this->fld_timestamp = isset( $prop['timestamp'] );
@@ -90,19 +76,20 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] );
if ( $this->fld_patrol ) {
- if ( !$user->useRCPatrol() && !$user->useNPPatrol() )
+ if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
$this->dieUsage( 'patrol property is not available', 'patrol' );
+ }
}
}
-
- $this->addFields( array (
+
+ $this->addFields( array(
'rc_namespace',
'rc_title',
'rc_timestamp'
) );
if ( is_null( $resultPageSet ) ) {
- $this->addFields( array (
+ $this->addFields( array(
'rc_cur_id',
'rc_this_oldid'
) );
@@ -110,7 +97,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->addFieldsIf( 'rc_new', $this->fld_flags );
$this->addFieldsIf( 'rc_minor', $this->fld_flags );
$this->addFieldsIf( 'rc_bot', $this->fld_flags );
- $this->addFieldsIf( 'rc_user', $this->fld_user );
+ $this->addFieldsIf( 'rc_user', $this->fld_user || $this->fld_userid );
$this->addFieldsIf( 'rc_user_text', $this->fld_user );
$this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrol );
@@ -123,22 +110,26 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->addFields( 'rc_cur_id' );
}
- $this->addTables( array (
+ $this->addTables( array(
'watchlist',
'page',
'recentchanges'
) );
$userId = $user->getId();
- $this->addWhere( array (
+ $this->addWhere( array(
'wl_namespace = rc_namespace',
'wl_title = rc_title',
'rc_cur_id = page_id',
'wl_user' => $userId,
'rc_deleted' => 0,
) );
+
+ $db = $this->getDB();
- $this->addWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
+ $this->addWhereRange( 'rc_timestamp', $params['dir'],
+ $db->timestamp( $params['start'] ),
+ $db->timestamp( $params['end'] ) );
$this->addWhereFld( 'wl_namespace', $params['namespace'] );
$this->addWhereIf( 'rc_this_oldid=page_latest', !$params['allrev'] );
@@ -149,45 +140,53 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
if ( ( isset ( $show['minor'] ) && isset ( $show['!minor'] ) )
|| ( isset ( $show['bot'] ) && isset ( $show['!bot'] ) )
|| ( isset ( $show['anon'] ) && isset ( $show['!anon'] ) )
- || ( isset ( $show['patrolled'] ) && isset ( $show['!patrolled'] ) ) ) {
-
+ || ( isset ( $show['patrolled'] ) && isset ( $show['!patrolled'] ) )
+ )
+ {
$this->dieUsageMsg( array( 'show' ) );
}
-
+
// Check permissions.
- if ( ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() )
- $this->dieUsage( "You need the patrol right to request the patrolled flag", 'permissiondenied' );
+ if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
+ global $wgUser;
+ if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
+ $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
+ }
+ }
/* Add additional conditions to query depending upon parameters. */
- $this->addWhereIf( 'rc_minor = 0', isset ( $show['!minor'] ) );
- $this->addWhereIf( 'rc_minor != 0', isset ( $show['minor'] ) );
- $this->addWhereIf( 'rc_bot = 0', isset ( $show['!bot'] ) );
- $this->addWhereIf( 'rc_bot != 0', isset ( $show['bot'] ) );
- $this->addWhereIf( 'rc_user = 0', isset ( $show['anon'] ) );
- $this->addWhereIf( 'rc_user != 0', isset ( $show['!anon'] ) );
+ $this->addWhereIf( 'rc_minor = 0', isset( $show['!minor'] ) );
+ $this->addWhereIf( 'rc_minor != 0', isset( $show['minor'] ) );
+ $this->addWhereIf( 'rc_bot = 0', isset( $show['!bot'] ) );
+ $this->addWhereIf( 'rc_bot != 0', isset( $show['bot'] ) );
+ $this->addWhereIf( 'rc_user = 0', isset( $show['anon'] ) );
+ $this->addWhereIf( 'rc_user != 0', isset( $show['!anon'] ) );
$this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
$this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
}
- if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) )
+ if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
$this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' );
- if ( !is_null( $params['user'] ) )
+ }
+ if ( !is_null( $params['user'] ) ) {
$this->addWhereFld( 'rc_user_text', $params['user'] );
- if ( !is_null( $params['excludeuser'] ) )
+ }
+ if ( !is_null( $params['excludeuser'] ) ) {
$this->addWhere( 'rc_user_text != ' . $this->getDB()->addQuotes( $params['excludeuser'] ) );
+ }
+
+
- $db = $this->getDB();
-
// This is an index optimization for mysql, as done in the Special:Watchlist page
- $this->addWhereIf( "rc_timestamp > ''", !isset ( $params['start'] ) && !isset ( $params['end'] ) && $db->getType() == 'mysql' );
+ $this->addWhereIf( "rc_timestamp > ''", !isset( $params['start'] ) && !isset( $params['end'] ) && $db->getType() == 'mysql' );
$this->addOption( 'LIMIT', $params['limit'] + 1 );
- $ids = array ();
+ $ids = array();
$count = 0;
$res = $this->select( __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ++ $count > $params['limit'] ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
@@ -197,8 +196,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
if ( is_null( $resultPageSet ) ) {
$vals = $this->extractRowInfo( $row );
$fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'start',
wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
break;
@@ -212,12 +210,9 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
}
- $db->freeResult( $res );
-
if ( is_null( $resultPageSet ) ) {
$this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
- }
- elseif ( $params['allrev'] ) {
+ } elseif ( $params['allrev'] ) {
$resultPageSet->populateFromRevisionIDs( $ids );
} else {
$resultPageSet->populateFromPageIDs( $ids );
@@ -225,8 +220,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
private function extractRowInfo( $row ) {
-
- $vals = array ();
+ $vals = array();
if ( $this->fld_ids ) {
$vals['pageid'] = intval( $row->rc_cur_id );
@@ -235,41 +229,60 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
- if ( $this->fld_title )
+ if ( $this->fld_title ) {
ApiQueryBase::addTitleInfo( $vals, $title );
+ }
- if ( $this->fld_user ) {
- $vals['user'] = $row->rc_user_text;
- if ( !$row->rc_user )
+ if ( $this->fld_user || $this->fld_userid ) {
+
+ if ( $this->fld_user ) {
+ $vals['user'] = $row->rc_user_text;
+ }
+
+ if ( $this->fld_userid ) {
+ $vals['user'] = $row->rc_user;
+ }
+
+ if ( !$row->rc_user ) {
$vals['anon'] = '';
+ }
}
if ( $this->fld_flags ) {
- if ( $row->rc_new )
+ if ( $row->rc_new ) {
$vals['new'] = '';
- if ( $row->rc_minor )
+ }
+ if ( $row->rc_minor ) {
$vals['minor'] = '';
- if ( $row->rc_bot )
+ }
+ if ( $row->rc_bot ) {
$vals['bot'] = '';
+ }
}
- if ( $this->fld_patrol && isset( $row->rc_patrolled ) )
+ if ( $this->fld_patrol && isset( $row->rc_patrolled ) ) {
$vals['patrolled'] = '';
+ }
- if ( $this->fld_timestamp )
+ if ( $this->fld_timestamp ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
+ }
if ( $this->fld_sizes ) {
$vals['oldlen'] = intval( $row->rc_old_len );
$vals['newlen'] = intval( $row->rc_new_len );
}
-
- if ( $this->fld_notificationtimestamp )
- $vals['notificationtimestamp'] = ( $row->wl_notificationtimestamp == null ) ? '' : wfTimestamp( TS_ISO_8601, $row->wl_notificationtimestamp );
- if ( $this->fld_comment && isset( $row->rc_comment ) )
+ if ( $this->fld_notificationtimestamp ) {
+ $vals['notificationtimestamp'] = ( $row->wl_notificationtimestamp == null )
+ ? ''
+ : wfTimestamp( TS_ISO_8601, $row->wl_notificationtimestamp );
+ }
+
+ if ( $this->fld_comment && isset( $row->rc_comment ) ) {
$vals['comment'] = $row->rc_comment;
-
+ }
+
if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
global $wgUser;
$vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->rc_comment, $title );
@@ -279,46 +292,47 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'allrev' => false,
- 'start' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ 'start' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
- 'end' => array (
- ApiBase :: PARAM_TYPE => 'timestamp'
+ 'end' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
),
'namespace' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => 'namespace'
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace'
),
'user' => array(
- ApiBase :: PARAM_TYPE => 'user',
+ ApiBase::PARAM_TYPE => 'user',
),
'excludeuser' => array(
- ApiBase :: PARAM_TYPE => 'user',
+ ApiBase::PARAM_TYPE => 'user',
),
- 'dir' => array (
- ApiBase :: PARAM_DFLT => 'older',
- ApiBase :: PARAM_TYPE => array (
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_TYPE => array(
'newer',
'older'
)
),
- '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
+ '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
),
- 'prop' => array (
- APIBase :: PARAM_ISMULTI => true,
- APIBase :: PARAM_DFLT => 'ids|title|flags',
- APIBase :: PARAM_TYPE => array (
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'ids|title|flags',
+ ApiBase::PARAM_TYPE => array(
'ids',
'title',
'flags',
'user',
+ 'userid',
'comment',
'parsedcomment',
'timestamp',
@@ -327,9 +341,9 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'notificationtimestamp'
)
),
- 'show' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ 'show' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'minor',
'!minor',
'bot',
@@ -340,39 +354,52 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'!patrolled',
)
),
- 'owner' => array (
- ApiBase :: PARAM_TYPE => 'user'
+ 'owner' => array(
+ ApiBase::PARAM_TYPE => 'user'
),
- 'token' => array (
- ApiBase :: PARAM_TYPE => 'string'
+ 'token' => array(
+ ApiBase::PARAM_TYPE => 'string'
)
);
}
public function getParamDescription() {
- 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).',
+ 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' => 'In which direction to enumerate pages.',
- 'limit' => 'How many total results to return per request.',
- 'prop' => 'Which additional items to get (non-generator mode only).',
- 'show' => array (
+ 'dir' => 'In which direction to enumerate pages',
+ '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',
+ ' size - Adds the old and new lengths of the page',
+ ' notificationtimestamp - Adds timestamp of when the user was last notified about the edit',
+ ),
+ 'show' => array(
'Show only items that meet this criteria.',
- 'For example, to see only minor edits done by logged-in users, set show=minor|!anon'
+ "For example, to see only minor edits done by logged-in users, set {$this->getModulePrefix()}show=minor|!anon"
),
- '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"
+ 'owner' => 'The name of the user whose watchlist you\'d like to access',
+ 'token' => 'Give a security token (settable in preferences) to allow access to another user\'s watchlist'
);
}
public function getDescription() {
return "Get all recent changes to pages in the logged in user's watchlist";
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'bad_wlowner', 'info' => 'Specified user does not exist' ),
@@ -386,17 +413,17 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array (
+ 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&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&generator=watchlist&gwlallrev=&prop=revisions&rvprop=timestamp|user',
'api.php?action=query&list=watchlist&wlowner=Bob_Smith&wltoken=d8d562e9725ea1512894cdab28e5ceebc7f20237'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryWatchlist.php 69932 2010-07-26 08:03:21Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryWatchlist.php 85435 2011-04-05 14:00:08Z demon $';
}
}
diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php
index 42d4005b..0e5617e3 100644
--- a/includes/api/ApiQueryWatchlistRaw.php
+++ b/includes/api/ApiQueryWatchlistRaw.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Oct 4, 2008
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Oct 4, 2008
+ *
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This 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,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiQueryBase.php' );
+ require_once( 'ApiQueryBase.php' );
}
/**
@@ -37,7 +38,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent :: __construct( $query, $moduleName, 'wr' );
+ parent::__construct( $query, $moduleName, 'wr' );
}
public function execute() {
@@ -49,54 +50,54 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
}
private function run( $resultPageSet = null ) {
- global $wgUser;
-
$this->selectNamedDB( 'watchlist', DB_SLAVE, 'watchlist' );
- if ( !$wgUser->isLoggedIn() )
- $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
$params = $this->extractRequestParams();
+
+ $user = $this->getWatchlistUser( $params );
+
$prop = array_flip( (array)$params['prop'] );
$show = array_flip( (array)$params['show'] );
- if ( isset( $show['changed'] ) && isset( $show['!changed'] ) )
+ if ( isset( $show['changed'] ) && isset( $show['!changed'] ) ) {
$this->dieUsageMsg( array( 'show' ) );
+ }
$this->addTables( 'watchlist' );
$this->addFields( array( 'wl_namespace', 'wl_title' ) );
$this->addFieldsIf( 'wl_notificationtimestamp', isset( $prop['changed'] ) );
- $this->addWhereFld( 'wl_user', $wgUser->getId() );
+ $this->addWhereFld( 'wl_user', $user->getId() );
$this->addWhereFld( 'wl_namespace', $params['namespace'] );
$this->addWhereIf( 'wl_notificationtimestamp IS NOT NULL', isset( $show['changed'] ) );
$this->addWhereIf( 'wl_notificationtimestamp IS NULL', isset( $show['!changed'] ) );
- if ( isset( $params['continue'] ) )
- {
+ if ( isset( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 )
+ if ( count( $cont ) != 2 ) {
$this->dieUsage( "Invalid continue param. You should pass the " .
"original value returned by the previous query", "_badcontinue" );
+ }
$ns = intval( $cont[0] );
$title = $this->getDB()->strencode( $this->titleToKey( $cont[1] ) );
- $this->addWhere( "wl_namespace > '$ns' OR " .
- "(wl_namespace = '$ns' AND " .
- "wl_title >= '$title')" );
+ $this->addWhere(
+ "wl_namespace > '$ns' OR " .
+ "(wl_namespace = '$ns' AND " .
+ "wl_title >= '$title')"
+ );
}
// Don't ORDER BY wl_namespace if it's constant in the WHERE clause
- if ( count( $params['namespace'] ) == 1 )
+ if ( count( $params['namespace'] ) == 1 ) {
$this->addOption( 'ORDER BY', 'wl_title' );
- else
+ } else {
$this->addOption( 'ORDER BY', 'wl_namespace, wl_title' );
+ }
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$res = $this->select( __METHOD__ );
-
- $db = $this->getDB();
+
$titles = array();
$count = 0;
- while ( $row = $db->fetchObject( $res ) )
- {
- if ( ++$count > $params['limit'] )
- {
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'continue', $row->wl_namespace . '|' .
$this->keyToTitle( $row->wl_title ) );
@@ -104,88 +105,102 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
}
$t = Title::makeTitle( $row->wl_namespace, $row->wl_title );
- if ( is_null( $resultPageSet ) )
- {
+ if ( is_null( $resultPageSet ) ) {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $t );
if ( isset( $prop['changed'] ) && !is_null( $row->wl_notificationtimestamp ) )
+ {
$vals['changed'] = wfTimestamp( TS_ISO_8601, $row->wl_notificationtimestamp );
+ }
$fit = $this->getResult()->addValue( $this->getModuleName(), null, $vals );
- if ( !$fit )
- {
+ if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $row->wl_namespace . '|' .
$this->keyToTitle( $row->wl_title ) );
break;
}
- }
- else
+ } else {
$titles[] = $t;
+ }
}
- if ( is_null( $resultPageSet ) )
+ if ( is_null( $resultPageSet ) ) {
$this->getResult()->setIndexedTagName_internal( $this->getModuleName(), 'wr' );
- else
+ } else {
$resultPageSet->populateFromTitles( $titles );
+ }
}
public function getAllowedParams() {
- return array (
+ return array(
'continue' => null,
- 'namespace' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => 'namespace'
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace'
),
- 'limit' => array (
- ApiBase :: PARAM_DFLT => 10,
- ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
- ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ '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
),
- 'prop' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'changed',
)
),
- 'show' => array (
- ApiBase :: PARAM_ISMULTI => true,
- ApiBase :: PARAM_TYPE => array (
+ 'show' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
'changed',
'!changed',
)
+ ),
+ 'owner' => array(
+ ApiBase::PARAM_TYPE => 'user'
+ ),
+ 'token' => array(
+ ApiBase::PARAM_TYPE => 'string'
)
);
}
public function getParamDescription() {
- return array (
+ 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' => 'Which additional properties to get (non-generator mode only).',
- 'show' => 'Only list items that meet these criteria.',
+ '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',
);
}
public function getDescription() {
return "Get all pages on the logged in user's watchlist";
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'notloggedin', 'info' => 'You must be logged-in to have a watchlist' ),
array( 'show' ),
+ array( 'code' => 'bad_wlowner', 'info' => 'Specified user does not exist' ),
+ array( 'code' => 'bad_wltoken', 'info' => 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences' ),
) );
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=query&list=watchlistraw',
'api.php?action=query&generator=watchlistraw&gwrshow=changed&prop=revisions',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 69578 2010-07-20 02:46:20Z tstarling $';
+ return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 70647 2010-08-07 19:59:42Z ialex $';
}
} \ No newline at end of file
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index 64c2c3fb..9d42a58e 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Sep 4, 2006
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
@@ -54,7 +55,7 @@ class ApiResult extends ApiBase {
* @param $main ApiMain object
*/
public function __construct( $main ) {
- parent :: __construct( $main, 'result' );
+ parent::__construct( $main, 'result' );
$this->mIsRawMode = false;
$this->mCheckingSize = true;
$this->reset();
@@ -64,7 +65,7 @@ class ApiResult extends ApiBase {
* Clear the current result data.
*/
public function reset() {
- $this->mData = array ();
+ $this->mData = array();
$this->mSize = 0;
}
@@ -100,12 +101,14 @@ class ApiResult extends ApiBase {
*/
public static function size( $value ) {
$s = 0;
- if ( is_array( $value ) )
- foreach ( $value as $v )
+ if ( is_array( $value ) ) {
+ foreach ( $value as $v ) {
$s += self::size( $v );
- else if ( !is_object( $value ) )
+ }
+ } elseif ( !is_object( $value ) ) {
// Objects can't always be cast to string
$s = strlen( $value );
+ }
return $s;
}
@@ -139,57 +142,65 @@ class ApiResult extends ApiBase {
* @param $arr array to add $value to
* @param $name string Index of $arr to add $value at
* @param $value mixed
+ * @param $overwrite bool Whether overwriting an existing element is allowed
*/
- public static function setElement( & $arr, $name, $value ) {
- if ( $arr === null || $name === null || $value === null || !is_array( $arr ) || is_array( $name ) )
- ApiBase :: dieDebug( __METHOD__, 'Bad parameter' );
+ public static function setElement( &$arr, $name, $value, $overwrite = false ) {
+ if ( $arr === null || $name === null || $value === null || !is_array( $arr ) || is_array( $name ) ) {
+ ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+ }
- if ( !isset ( $arr[$name] ) ) {
+ if ( !isset ( $arr[$name] ) || $overwrite ) {
$arr[$name] = $value;
- }
- elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
+ } elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
$merged = array_intersect_key( $arr[$name], $value );
- if ( !count( $merged ) )
+ if ( !count( $merged ) ) {
$arr[$name] += $value;
- else
- ApiBase :: dieDebug( __METHOD__, "Attempting to merge element $name" );
- } else
- ApiBase :: dieDebug( __METHOD__, "Attempting to add element $name=$value, existing value is {$arr[$name]}" );
+ } else {
+ ApiBase::dieDebug( __METHOD__, "Attempting to merge element $name" );
+ }
+ } else {
+ ApiBase::dieDebug( __METHOD__, "Attempting to add element $name=$value, existing value is {$arr[$name]}" );
+ }
}
/**
* Adds a content element to an array.
* Use this function instead of hardcoding the '*' element.
* @param $arr array to add the content element to
+ * @param $value Mixed
* @param $subElemName string 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 ) {
- if ( is_array( $value ) )
- ApiBase :: dieDebug( __METHOD__, 'Bad parameter' );
+ public static function setContent( &$arr, $value, $subElemName = null ) {
+ if ( is_array( $value ) ) {
+ ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+ }
if ( is_null( $subElemName ) ) {
- ApiResult :: setElement( $arr, '*', $value );
+ ApiResult::setElement( $arr, '*', $value );
} else {
- if ( !isset ( $arr[$subElemName] ) )
- $arr[$subElemName] = array ();
- ApiResult :: setElement( $arr[$subElemName], '*', $value );
+ if ( !isset( $arr[$subElemName] ) ) {
+ $arr[$subElemName] = array();
+ }
+ ApiResult::setElement( $arr[$subElemName], '*', $value );
}
}
/**
* 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 arrray that has numerical indexes.
+ * called on every array that has numerical indexes.
* @param $arr array
* @param $tag string Tag name
*/
- public function setIndexedTagName( & $arr, $tag ) {
+ public function setIndexedTagName( &$arr, $tag ) {
// In raw mode, add the '_element', otherwise just ignore
- if ( !$this->getIsRawMode() )
+ if ( !$this->getIsRawMode() ) {
return;
- if ( $arr === null || $tag === null || !is_array( $arr ) || is_array( $tag ) )
- ApiBase :: dieDebug( __METHOD__, 'Bad parameter' );
+ }
+ if ( $arr === null || $tag === null || !is_array( $arr ) || is_array( $tag ) ) {
+ ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+ }
// Do not use setElement() as it is ok to call this more than once
$arr['_element'] = $tag;
}
@@ -200,14 +211,15 @@ class ApiResult extends ApiBase {
* @param $tag string Tag name
*/
public function setIndexedTagName_recursive( &$arr, $tag ) {
- if ( !is_array( $arr ) )
- return;
- foreach ( $arr as &$a )
- {
- if ( !is_array( $a ) )
- continue;
- $this->setIndexedTagName( $a, $tag );
- $this->setIndexedTagName_recursive( $a, $tag );
+ if ( !is_array( $arr ) ) {
+ return;
+ }
+ foreach ( $arr as &$a ) {
+ if ( !is_array( $a ) ) {
+ continue;
+ }
+ $this->setIndexedTagName( $a, $tag );
+ $this->setIndexedTagName_recursive( $a, $tag );
}
}
@@ -219,57 +231,73 @@ class ApiResult extends ApiBase {
* @param $tag string
*/
public function setIndexedTagName_internal( $path, $tag ) {
- $data = & $this->mData;
+ $data = &$this->mData;
foreach ( (array)$path as $p ) {
if ( !isset( $data[$p] ) ) {
$data[$p] = array();
}
- $data = & $data[$p];
+ $data = &$data[$p];
}
- if ( is_null( $data ) )
+ if ( is_null( $data ) ) {
return;
+ }
$this->setIndexedTagName( $data, $tag );
}
/**
* Add value to the output data at the given path.
- * Path is an indexed array, each element specifing the branch at which to add the new value
+ * Path is 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 $name is empty, the $value is added as a next list element data[] = $value
* @return bool True if $value fits in the result, false if not
*/
- public function addValue( $path, $name, $value ) {
+ public function addValue( $path, $name, $value, $overwrite = false ) {
global $wgAPIMaxResultSize;
- $data = & $this->mData;
+ $data = &$this->mData;
if ( $this->mCheckingSize ) {
$newsize = $this->mSize + self::size( $value );
- if ( $newsize > $wgAPIMaxResultSize )
+ if ( $newsize > $wgAPIMaxResultSize ) {
return false;
+ }
$this->mSize = $newsize;
}
if ( !is_null( $path ) ) {
if ( is_array( $path ) ) {
foreach ( $path as $p ) {
- if ( !isset ( $data[$p] ) )
- $data[$p] = array ();
- $data = & $data[$p];
+ if ( !isset( $data[$p] ) ) {
+ $data[$p] = array();
+ }
+ $data = &$data[$p];
}
} else {
- if ( !isset ( $data[$path] ) )
- $data[$path] = array ();
- $data = & $data[$path];
+ if ( !isset( $data[$path] ) ) {
+ $data[$path] = array();
+ }
+ $data = &$data[$path];
}
}
- if ( !$name )
- $data[] = $value; // Add list element
- else
- ApiResult :: setElement( $data, $name, $value ); // Add named element
+ if ( !$name ) {
+ $data[] = $value; // Add list element
+ } else {
+ self::setElement( $data, $name, $value, $overwrite ); // Add named element
+ }
return true;
}
/**
+ * Add a parsed limit=max to the result.
+ *
+ * @param $moduleName string
+ * @param $limit int
+ */
+ public function setParsedLimit( $moduleName, $limit ) {
+ // Add value, allowing overwriting
+ $this->addValue( 'limits', $moduleName, $limit, true );
+ }
+
+ /**
* Unset a value previously added to the result set.
* Fails silently if the value isn't found.
* For parameters, see addValue()
@@ -277,13 +305,15 @@ class ApiResult extends ApiBase {
* @param $name string
*/
public function unsetValue( $path, $name ) {
- $data = & $this->mData;
- if ( !is_null( $path ) )
+ $data = &$this->mData;
+ if ( !is_null( $path ) ) {
foreach ( (array)$path as $p ) {
- if ( !isset( $data[$p] ) )
+ if ( !isset( $data[$p] ) ) {
return;
- $data = & $data[$p];
+ }
+ $data = &$data[$p];
}
+ }
$this->mSize -= self::size( $data[$name] );
unset( $data[$name] );
}
@@ -291,27 +321,26 @@ class ApiResult extends ApiBase {
/**
* Ensure all values in this result are valid UTF-8.
*/
- public function cleanUpUTF8()
- {
+ public function cleanUpUTF8() {
array_walk_recursive( $this->mData, array( 'ApiResult', 'cleanUp_helper' ) );
}
/**
* Callback function for cleanUpUTF8()
*/
- private static function cleanUp_helper( &$s )
- {
- if ( !is_string( $s ) )
+ private static function cleanUp_helper( &$s ) {
+ if ( !is_string( $s ) ) {
return;
+ }
global $wgContLang;
$s = $wgContLang->normalize( $s );
}
public function execute() {
- ApiBase :: dieDebug( __METHOD__, 'execute() is not supported on Result object' );
+ ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiResult.php 62354 2010-02-12 06:44:16Z mah $';
+ return __CLASS__ . ': $Id: ApiResult.php 74230 2010-10-03 19:07:11Z reedy $';
}
}
diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php
index 5c259f4e..e31bfed8 100644
--- a/includes/api/ApiRollback.php
+++ b/includes/api/ApiRollback.php
@@ -1,10 +1,10 @@
<?php
-
-/*
- * Created on Jun 20, 2007
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Jun 20, 2007
+ *
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -33,39 +35,27 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiRollback extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
+ private $mTitleObj = null, $mUser = null;
+
public function execute() {
$params = $this->extractRequestParams();
- $titleObj = null;
- if ( !isset( $params['title'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'title' ) );
- if ( !isset( $params['user'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'user' ) );
-
- $titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj )
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
- if ( !$titleObj->exists() )
- $this->dieUsageMsg( array( 'notanarticle' ) );
-
- // We need to be able to revert IPs, but getCanonicalName rejects them
- $username = User::isIP( $params['user'] )
- ? $params['user']
- : User::getCanonicalName( $params['user'] );
- if ( !$username )
- $this->dieUsageMsg( array( 'invaliduser', $params['user'] ) );
-
+ // User and title already validated in call to getTokenSalt from Main
+ $titleObj = $this->getTitle();
$articleObj = new Article( $titleObj );
- $summary = ( isset( $params['summary'] ) ? $params['summary'] : "" );
+ $summary = ( isset( $params['summary'] ) ? $params['summary'] : '' );
$details = null;
- $retval = $articleObj->doRollback( $username, $summary, $params['token'], $params['markbot'], $details );
+ $retval = $articleObj->doRollback( $this->getUser(), $summary, $params['token'], $params['markbot'], $details );
- if ( $retval )
+ if ( $retval ) {
// We don't care about multiple errors, just report one of them
$this->dieUsageMsg( reset( $retval ) );
+ }
+
+ $this->setWatch( $params['watchlist'], $titleObj );
$info = array(
'title' => $titleObj->getPrefixedText(),
@@ -79,57 +69,121 @@ class ApiRollback extends ApiBase {
$this->getResult()->addValue( null, $this->getModuleName(), $info );
}
- public function mustBePosted() { return true; }
+ public function mustBePosted() {
+ return true;
+ }
public function isWriteMode() {
return true;
}
public function getAllowedParams() {
- return array (
- 'title' => null,
- 'user' => null,
+ return array(
+ 'title' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'user' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'token' => null,
'summary' => null,
- 'markbot' => false
+ 'markbot' => false,
+ 'watchlist' => array(
+ ApiBase::PARAM_DFLT => 'preferences',
+ ApiBase::PARAM_TYPE => array(
+ 'watch',
+ 'unwatch',
+ 'preferences',
+ 'nochange'
+ ),
+ ),
);
}
public function getParamDescription() {
- return array (
+ return array(
'title' => 'Title of the page you want to rollback.',
'user' => 'Name of the user whose edits are to be rolled back. If set incorrectly, you\'ll get a badtoken error.',
- 'token' => 'A rollback token previously retrieved through prop=revisions',
- 'summary' => 'Custom edit summary. If not set, default summary will be used.',
- 'markbot' => 'Mark the reverted edits and the revert as bot edits'
+ 'token' => "A rollback token previously retrieved through {$this->getModulePrefix()}prop=revisions",
+ 'summary' => 'Custom edit summary. If not set, 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.'
- );
+ 'Undo the last edit to the page. If the last user who edited the page made multiple edits in a row,',
+ 'they will all be rolled back'
+ );
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'title' ),
- array( 'missingparam', 'user' ),
array( 'invalidtitle', 'title' ),
array( 'notanarticle' ),
array( 'invaliduser', 'user' ),
) );
}
+ public function needsToken() {
+ return true;
+ }
+
+ public function getTokenSalt() {
+ return array( $this->getTitle()->getPrefixedText(), $this->getUser() );
+ }
+
+ private function getUser() {
+ if ( $this->mUser !== null ) {
+ return $this->mUser;
+ }
+
+ $params = $this->extractRequestParams();
+
+ // We need to be able to revert IPs, but getCanonicalName rejects them
+ $this->mUser = User::isIP( $params['user'] )
+ ? $params['user']
+ : User::getCanonicalName( $params['user'] );
+ if ( !$this->mUser ) {
+ $this->dieUsageMsg( array( 'invaliduser', $params['user'] ) );
+ }
+
+ return $this->mUser;
+ }
+
+ /**
+ * @return Title
+ */
+ private function getTitle() {
+ if ( $this->mTitleObj !== null ) {
+ return $this->mTitleObj;
+ }
+
+ $params = $this->extractRequestParams();
+
+ $this->mTitleObj = Title::newFromText( $params['title'] );
+
+ if ( !$this->mTitleObj ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
+ if ( !$this->mTitleObj->exists() ) {
+ $this->dieUsageMsg( array( 'notanarticle' ) );
+ }
+
+ return $this->mTitleObj;
+ }
+
protected function getExamples() {
- return array (
+ return array(
'api.php?action=rollback&title=Main%20Page&user=Catrope&token=123ABC',
'api.php?action=rollback&title=Main%20Page&user=217.121.114.116&token=123ABC&summary=Reverting%20vandalism&markbot=1'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiRollback.php 65371 2010-04-21 10:41:25Z tstarling $';
+ return __CLASS__ . ': $Id: ApiRollback.php 75602 2010-10-28 00:04:48Z reedy $';
}
}
diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php
new file mode 100644
index 00000000..7bc4722c
--- /dev/null
+++ b/includes/api/ApiRsd.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * API for MediaWiki 1.17+
+ *
+ * Created on October 26, 2010
+ *
+ * Copyright © 2010 Bryan Tong Minh and Brion Vibber
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ require_once( 'ApiBase.php' );
+}
+
+/**
+ * API module for sending out RSD information
+ * @ingroup API
+ */
+class ApiRsd extends ApiBase {
+
+ public function __construct( $main, $action ) {
+ parent::__construct( $main, $action );
+ }
+
+ public function execute() {
+ $result = $this->getResult();
+
+ $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, 'http://www.mediawiki.org/', 'engineLink' );
+
+ $result->setIndexedTagName( $service['apis'], 'api' );
+
+ $result->addValue( null, 'service', $service );
+ }
+
+ public function getCustomPrinter() {
+ return new ApiFormatXmlRsd( $this->getMain(), 'xml' );
+ }
+
+ public function getAllowedParams() {
+ return array();
+ }
+
+ public function getParamDescription() {
+ return array();
+ }
+
+ public function getDescription() {
+ return 'Export an RSD schema';
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=rsd'
+ );
+ }
+
+ /**
+ * Builds an internal list of APIs to expose information about.
+ * Normally this only lists the MediaWiki API, with its base URL,
+ * link to documentation, and a marker as to available authentication
+ * (to aid in OAuth client apps switching to support in the future).
+ *
+ * Extensions can expose other APIs, such as WordPress or Twitter-
+ * compatible APIs, by hooking 'ApiRsdServiceApis' and adding more
+ * elements to the array.
+ *
+ * See http://cyber.law.harvard.edu/blogs/gems/tech/rsd.html for
+ * the base RSD spec, and check WordPress and StatusNet sites for
+ * in-production examples listing several blogging and micrblogging
+ * APIs.
+ *
+ * @return array
+ */
+ protected function getRsdApiList() {
+ $apis = array(
+ 'MediaWiki' => array(
+ // The API link is required for all RSD API entries.
+ 'apiLink' => wfExpandUrl( wfScript( 'api' ) ),
+
+ // Docs link is optional, but recommended.
+ 'docs' => 'http://mediawiki.org/wiki/API',
+
+ // Some APIs may need a blog ID, but it may be left blank.
+ 'blogID' => '',
+
+ // Additional settings are optional.
+ 'settings' => array(
+ // Change this to true in the future as an aid to
+ // machine discovery of OAuth for API access.
+ 'OAuth' => false,
+ )
+ ),
+ );
+ wfRunHooks( 'ApiRsdServiceApis', array( &$apis ) );
+ return $apis;
+ }
+
+ /**
+ * Formats the internal list of exposed APIs into an array suitable
+ * to pass to the API's XML formatter.
+ *
+ * @return array
+ */
+ protected function formatRsdApiList() {
+ $apis = $this->getRsdApiList();
+
+ $outputData = array();
+ foreach ( $apis as $name => $info ) {
+ $data = array(
+ 'name' => $name,
+ 'preferred' => wfBoolToStr( $name == 'MediaWiki' ),
+ 'apiLink' => $info['apiLink'],
+ 'blogID' => isset( $info['blogID'] ) ? $info['blogID'] : '',
+ );
+ $settings = array();
+ if ( isset( $info['docs'] ) ) {
+ ApiResult::setContent( $settings, $info['docs'], 'docs' );
+ }
+ if ( isset( $info['settings'] ) ) {
+ foreach ( $info['settings'] as $setting => $val ) {
+ if ( is_bool( $val ) ) {
+ $xmlVal = wfBoolToStr( $val );
+ } else {
+ $xmlVal = $val;
+ }
+ $setting = array( 'name' => $setting );
+ ApiResult::setContent( $setting, $xmlVal );
+ $settings[] = $setting;
+ }
+ }
+ if ( count( $settings ) ) {
+ $this->getResult()->setIndexedTagName( $settings, 'setting' );
+ $data['settings'] = $settings;
+ }
+ $outputData[] = $data;
+ }
+ return $outputData;
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiRsd.php 76195 2010-11-06 15:57:15Z btongminh $';
+ }
+}
+
+class ApiFormatXmlRsd extends ApiFormatXml {
+ public function __construct( $main, $format ) {
+ parent::__construct( $main, $format );
+ $this->setRootElement( 'rsd' );
+ }
+
+ public function getMimeType() {
+ return 'application/rsd+xml';
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiRsd.php 76195 2010-11-06 15:57:15Z btongminh $';
+ }
+}
diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php
index 1c4a4ade..4f6e4fb7 100644
--- a/includes/api/ApiUnblock.php
+++ b/includes/api/ApiUnblock.php
@@ -1,10 +1,10 @@
<?php
-
-/*
- * Created on Sep 7, 2007
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Sep 7, 2007
+ *
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -36,7 +38,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiUnblock extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
/**
@@ -46,27 +48,37 @@ class ApiUnblock extends ApiBase {
global $wgUser;
$params = $this->extractRequestParams();
- if ( $params['gettoken'] )
- {
+ if ( $params['gettoken'] ) {
$res['unblocktoken'] = $wgUser->editToken();
$this->getResult()->addValue( null, $this->getModuleName(), $res );
return;
}
- if ( is_null( $params['id'] ) && is_null( $params['user'] ) )
+ if ( is_null( $params['id'] ) && is_null( $params['user'] ) ) {
$this->dieUsageMsg( array( 'unblock-notarget' ) );
- if ( !is_null( $params['id'] ) && !is_null( $params['user'] ) )
+ }
+ if ( !is_null( $params['id'] ) && !is_null( $params['user'] ) ) {
$this->dieUsageMsg( array( 'unblock-idanduser' ) );
+ }
- if ( !$wgUser->isAllowed( 'block' ) )
+ if ( !$wgUser->isAllowed( 'block' ) ) {
$this->dieUsageMsg( array( 'cantunblock' ) );
+ }
+ # bug 15810: blocked admins should have limited access here
+ if ( $wgUser->isBlocked() ) {
+ $status = IPBlockForm::checkUnblockSelf( $params['user'] );
+ if ( $status !== true ) {
+ $this->dieUsageMsg( array( $status ) );
+ }
+ }
$id = $params['id'];
$user = $params['user'];
$reason = ( is_null( $params['reason'] ) ? '' : $params['reason'] );
$retval = IPUnblockForm::doUnblock( $id, $user, $reason, $range );
- if ( $retval )
+ if ( $retval ) {
$this->dieUsageMsg( $retval );
+ }
$res['id'] = intval( $id );
$res['user'] = $user;
@@ -83,7 +95,7 @@ class ApiUnblock extends ApiBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'id' => null,
'user' => null,
'token' => null,
@@ -93,29 +105,30 @@ class ApiUnblock extends ApiBase {
}
public function getParamDescription() {
- return array (
- 'id' => 'ID of the block you want to unblock (obtained through list=blocks). Cannot be used together with user',
- 'user' => 'Username, IP address or IP range you want to unblock. Cannot be used together with id',
- 'token' => 'An unblock token previously obtained through the gettoken parameter or prop=info',
+ $p = $this->getModulePrefix();
+ return array(
+ 'id' => "ID of the block you want to unblock (obtained through list=blocks). Cannot be used together with {$p}user",
+ 'user' => "Username, IP address or IP range you want to unblock. Cannot be used together with {$p}id",
+ 'token' => "An unblock token previously obtained through the gettoken parameter or {$p}prop=info",
'gettoken' => 'If set, an unblock token will be returned, and no other action will be taken',
'reason' => 'Reason for unblock (optional)',
);
}
public function getDescription() {
- return array(
- 'Unblock a user.'
- );
+ return 'Unblock a user';
}
-
- public function getPossibleErrors() {
+
+ public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'unblock-notarget' ),
array( 'unblock-idanduser' ),
array( 'cantunblock' ),
- ) );
+ array( 'ipbblocked' ),
+ array( 'ipbnounblockself' ),
+ ) );
}
-
+
public function needsToken() {
return true;
}
@@ -125,13 +138,13 @@ class ApiUnblock extends ApiBase {
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=unblock&id=105',
'api.php?action=unblock&user=Bob&reason=Sorry%20Bob'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiUnblock.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiUnblock.php 74098 2010-10-01 20:12:50Z reedy $';
}
}
diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php
index ae705b69..3c7d91a5 100644
--- a/includes/api/ApiUndelete.php
+++ b/includes/api/ApiUndelete.php
@@ -1,10 +1,10 @@
<?php
-
-/*
- * Created on Jul 3, 2007
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Jul 3, 2007
+ *
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -33,45 +35,49 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiUndelete extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
global $wgUser;
$params = $this->extractRequestParams();
- $titleObj = null;
- if ( !isset( $params['title'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'title' ) );
-
- if ( !$wgUser->isAllowed( 'undelete' ) )
+ if ( !$wgUser->isAllowed( 'undelete' ) ) {
$this->dieUsageMsg( array( 'permdenied-undelete' ) );
+ }
- if ( $wgUser->isBlocked() )
+ if ( $wgUser->isBlocked() ) {
$this->dieUsageMsg( array( 'blockedtext' ) );
+ }
$titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj )
+ if ( !$titleObj ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
// Convert timestamps
- if ( !isset( $params['timestamps'] ) )
+ if ( !isset( $params['timestamps'] ) ) {
$params['timestamps'] = array();
- if ( !is_array( $params['timestamps'] ) )
+ }
+ if ( !is_array( $params['timestamps'] ) ) {
$params['timestamps'] = array( $params['timestamps'] );
- foreach ( $params['timestamps'] as $i => $ts )
+ }
+ foreach ( $params['timestamps'] as $i => $ts ) {
$params['timestamps'][$i] = wfTimestamp( TS_MW, $ts );
+ }
$pa = new PageArchive( $titleObj );
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
$retval = $pa->undelete( ( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ), $params['reason'] );
- if ( !is_array( $retval ) )
+ if ( !is_array( $retval ) ) {
$this->dieUsageMsg( array( 'cannotundelete' ) );
+ }
- if ( $retval[1] )
+ if ( $retval[1] ) {
wfRunHooks( 'FileUndeleteComplete',
array( $titleObj, array(), $wgUser, $params['reason'] ) );
+ }
+
+ $this->setWatch( $params['watchlist'], $titleObj );
$info['title'] = $titleObj->getPrefixedText();
$info['revisions'] = intval( $retval[0] );
@@ -89,22 +95,35 @@ class ApiUndelete extends ApiBase {
}
public function getAllowedParams() {
- return array (
- 'title' => null,
+ return array(
+ 'title' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'token' => null,
- 'reason' => "",
+ 'reason' => '',
'timestamps' => array(
- ApiBase :: PARAM_ISMULTI => true
- )
+ ApiBase::PARAM_ISMULTI => true
+ ),
+ 'watchlist' => array(
+ ApiBase::PARAM_DFLT => 'preferences',
+ ApiBase::PARAM_TYPE => array(
+ 'watch',
+ 'unwatch',
+ 'preferences',
+ 'nochange'
+ ),
+ ),
);
}
public function getParamDescription() {
- return array (
- 'title' => 'Title of the page you want to restore.',
+ return array(
+ 'title' => 'Title of the page you want to restore',
'token' => 'An undelete token previously retrieved through list=deletedrevs',
'reason' => 'Reason for restoring (optional)',
- 'timestamps' => 'Timestamps of the revisions to restore. If not set, all revisions will be restored.'
+ 'timestamps' => 'Timestamps of the revisions to restore. If not set, all revisions will be restored.',
+ 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
);
}
@@ -114,17 +133,16 @@ class ApiUndelete extends ApiBase {
'retrieved through list=deletedrevs'
);
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'title' ),
array( 'permdenied-undelete' ),
array( 'blockedtext' ),
array( 'invalidtitle', 'title' ),
array( 'cannotundelete' ),
) );
}
-
+
public function needsToken() {
return true;
}
@@ -134,13 +152,13 @@ class ApiUndelete extends ApiBase {
}
protected function getExamples() {
- return array (
+ 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'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiUndelete.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiUndelete.php 74098 2010-10-01 20:12:50Z reedy $';
}
}
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index 06688997..e7d7b939 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -1,9 +1,10 @@
<?php
-/*
- * Created on Aug 21, 2008
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 - 2010 Bryan Tong Minh <Bryan.TongMinh@Gmail.com>
+ * Created on Aug 21, 2008
+ *
+ * Copyright © 2008 - 2010 Bryan Tong Minh <Bryan.TongMinh@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
@@ -17,8 +18,10 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
@@ -38,141 +41,269 @@ class ApiUpload extends ApiBase {
}
public function execute() {
- global $wgUser, $wgAllowCopyUploads;
+ global $wgUser;
// Check whether upload is enabled
- if ( !UploadBase::isEnabled() )
+ if ( !UploadBase::isEnabled() ) {
$this->dieUsageMsg( array( 'uploaddisabled' ) );
+ }
+ // Parameter handling
$this->mParams = $this->extractRequestParams();
$request = $this->getMain()->getRequest();
-
// Add the uploaded file to the params array
$this->mParams['file'] = $request->getFileName( 'file' );
+ // Select an upload module
+ if ( !$this->selectUploadModule() ) {
+ // This is not a true upload, but a status request or similar
+ return;
+ }
+ if ( !isset( $this->mUpload ) ) {
+ $this->dieUsage( 'No upload module set', 'nomodule' );
+ }
+
+ // First check permission to upload
+ $this->checkPermissions( $wgUser );
+
+ // Fetch the file
+ $status = $this->mUpload->fetchFile();
+ if ( !$status->isGood() ) {
+ $errors = $status->getErrorsArray();
+ $error = array_shift( $errors[0] );
+ $this->dieUsage( 'Error fetching file from remote source', $error, 0, $errors[0] );
+ }
+
+ // Check if the uploaded file is sane
+ $this->verifyUpload();
+
+ // Check permission to upload this file
+ $permErrors = $this->mUpload->verifyPermissions( $wgUser );
+ if ( $permErrors !== true ) {
+ // TODO: stash the upload and allow choosing a new name
+ $this->dieUsageMsg( array( 'badaccess-groups' ) );
+ }
+
+ // Prepare the API result
+ $result = array();
+
+ $warnings = $this->getApiWarnings();
+ if ( $warnings ) {
+ $result['result'] = 'Warning';
+ $result['warnings'] = $warnings;
+ // in case the warnings can be fixed with some further user action, let's stash this upload
+ // and return a key they can use to restart it
+ try {
+ $result['sessionkey'] = $this->performStash();
+ } catch ( MWException $e ) {
+ $result['warnings']['stashfailed'] = $e->getMessage();
+ }
+ } elseif ( $this->mParams['stash'] ) {
+ // Some uploads can request they be stashed, so as not to publish them immediately.
+ // In this case, a failure to stash ought to be fatal
+ try {
+ $result['result'] = 'Success';
+ $result['sessionkey'] = $this->performStash();
+ } catch ( MWException $e ) {
+ $this->dieUsage( $e->getMessage(), 'stashfailed' );
+ }
+ } else {
+ // This is the most common case -- a normal upload with no warnings
+ // $result will be formatted properly for the API already, with a status
+ $result = $this->performUpload();
+ }
+
+ if ( $result['result'] === 'Success' ) {
+ $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
+ }
+
+ $this->getResult()->addValue( null, $this->getModuleName(), $result );
+
+ // Cleanup any temporary mess
+ $this->mUpload->cleanupTempFile();
+ }
+
+ /**
+ * Stash the file and return the session key
+ * Also re-raises exceptions with slightly more informative message strings (useful for API)
+ * @throws MWException
+ * @return {String} session key
+ */
+ function performStash() {
+ try {
+ $sessionKey = $this->mUpload->stashSessionFile()->getSessionKey();
+ } catch ( MWException $e ) {
+ throw new MWException( 'Stashing temporary file failed: ' . get_class($e) . ' ' . $e->getMessage() );
+ }
+ return $sessionKey;
+ }
+
+
+ /**
+ * Select an upload module and set it to mUpload. Dies on failure. If the
+ * request was a status request and not a true upload, returns false;
+ * otherwise true
+ *
+ * @return bool
+ */
+ protected function selectUploadModule() {
+ global $wgAllowAsyncCopyUploads;
+ $request = $this->getMain()->getRequest();
+
// One and only one of the following parameters is needed
$this->requireOnlyOneParameter( $this->mParams,
- 'sessionkey', 'file', 'url' );
+ 'sessionkey', 'file', 'url', 'statuskey' );
+
+ if ( $wgAllowAsyncCopyUploads && $this->mParams['statuskey'] ) {
+ // Status request for an async upload
+ $sessionData = UploadFromUrlJob::getSessionData( $this->mParams['statuskey'] );
+ if ( !isset( $sessionData['result'] ) ) {
+ $this->dieUsage( 'No result in session data', 'missingresult');
+ }
+ if ( $sessionData['result'] == 'Warning' ) {
+ $sessionData['warnings'] = $this->transformWarnings( $sessionData['warnings'] );
+ $sessionData['sessionkey'] = $this->mParams['statuskey'];
+ }
+ $this->getResult()->addValue( null, $this->getModuleName(), $sessionData );
+ return false;
+
+ }
+
+
+ // The following modules all require the filename parameter to be set
+ if ( is_null( $this->mParams['filename'] ) ) {
+ $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
+ }
+
if ( $this->mParams['sessionkey'] ) {
- /**
- * Upload stashed in a previous request
- */
- // Check the session key
- if ( !isset( $_SESSION['wsUploadData'][$this->mParams['sessionkey']] ) )
+ // Upload stashed in a previous request
+ $sessionData = $request->getSessionData( UploadBase::getSessionKeyName() );
+ if ( !UploadFromStash::isValidSessionKey( $this->mParams['sessionkey'], $sessionData ) ) {
$this->dieUsageMsg( array( 'invalid-session-key' ) );
+ }
$this->mUpload = new UploadFromStash();
$this->mUpload->initialize( $this->mParams['filename'],
$this->mParams['sessionkey'],
- $_SESSION['wsUploadData'][$this->mParams['sessionkey']] );
- } elseif ( isset( $this->mParams['filename'] ) ) {
- /**
- * Upload from url, etc
- * Parameter filename is required
- */
-
- if ( isset( $this->mParams['file'] ) ) {
- $this->mUpload = new UploadFromFile();
- $this->mUpload->initialize(
- $this->mParams['filename'],
- $request->getFileTempName( 'file' ),
- $request->getFileSize( 'file' )
- );
- } elseif ( isset( $this->mParams['url'] ) ) {
- // make sure upload by url is enabled:
- if ( !$wgAllowCopyUploads )
- $this->dieUsageMsg( array( 'uploaddisabled' ) );
-
- // make sure the current user can upload
- if ( ! $wgUser->isAllowed( 'upload_by_url' ) )
- $this->dieUsageMsg( array( 'badaccess-groups' ) );
-
- $this->mUpload = new UploadFromUrl();
- $this->mUpload->initialize( $this->mParams['filename'],
- $this->mParams['url'] );
-
- $status = $this->mUpload->fetchFile();
- if ( !$status->isOK() ) {
- $this->dieUsage( $status->getWikiText(), 'fetchfileerror' );
+ $sessionData[$this->mParams['sessionkey']] );
+
+
+ } elseif ( isset( $this->mParams['file'] ) ) {
+ $this->mUpload = new UploadFromFile();
+ $this->mUpload->initialize(
+ $this->mParams['filename'],
+ $request->getUpload( 'file' )
+ );
+ } elseif ( isset( $this->mParams['url'] ) ) {
+ // Make sure upload by URL is enabled:
+ if ( !UploadFromUrl::isEnabled() ) {
+ $this->dieUsageMsg( array( 'copyuploaddisabled' ) );
+ }
+
+ $async = false;
+ if ( $this->mParams['asyncdownload'] ) {
+ if ( $this->mParams['leavemessage'] && !$this->mParams['ignorewarnings'] ) {
+ $this->dieUsage( 'Using leavemessage without ignorewarnings is not supported',
+ 'missing-ignorewarnings' );
+ }
+
+ if ( $this->mParams['leavemessage'] ) {
+ $async = 'async-leavemessage';
+ } else {
+ $async = 'async';
}
}
- } else $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
+ $this->mUpload = new UploadFromUrl;
+ $this->mUpload->initialize( $this->mParams['filename'],
+ $this->mParams['url'], $async );
- if ( !isset( $this->mUpload ) )
- $this->dieUsage( 'No upload module set', 'nomodule' );
+ }
+
+ return true;
+ }
+ /**
+ * Checks that the user has permissions to perform this upload.
+ * Dies with usage message on inadequate permissions.
+ * @param $user User The user to check.
+ */
+ protected function checkPermissions( $user ) {
// Check whether the user has the appropriate permissions to upload anyway
- $permission = $this->mUpload->isAllowed( $wgUser );
+ $permission = $this->mUpload->isAllowed( $user );
if ( $permission !== true ) {
- if ( !$wgUser->isLoggedIn() )
+ if ( !$user->isLoggedIn() ) {
$this->dieUsageMsg( array( 'mustbeloggedin', 'upload' ) );
- else
+ } else {
$this->dieUsageMsg( array( 'badaccess-groups' ) );
+ }
}
- // Perform the upload
- $result = $this->performUpload();
-
- // Cleanup any temporary mess
- $this->mUpload->cleanupTempFile();
-
- $this->getResult()->addValue( null, $this->getModuleName(), $result );
}
- protected function performUpload() {
- global $wgUser;
- $result = array();
- $permErrors = $this->mUpload->verifyPermissions( $wgUser );
- if ( $permErrors !== true ) {
- $this->dieUsageMsg( array( 'badaccess-groups' ) );
+ /**
+ * Performs file verification, dies on error.
+ */
+ protected function verifyUpload( ) {
+ global $wgFileExtensions;
+
+ $verification = $this->mUpload->verifyUpload( );
+ if ( $verification['status'] === UploadBase::OK ) {
+ return;
}
// TODO: Move them to ApiBase's message map
- $verification = $this->mUpload->verifyUpload();
- if ( $verification['status'] !== UploadBase::OK ) {
- $result['result'] = 'Failure';
- switch( $verification['status'] ) {
- case UploadBase::EMPTY_FILE:
- $this->dieUsage( 'The file you submitted was empty', 'empty-file' );
- break;
- case UploadBase::FILETYPE_MISSING:
- $this->dieUsage( 'The file is missing an extension', 'filetype-missing' );
- break;
- case UploadBase::FILETYPE_BADTYPE:
- global $wgFileExtensions;
- $this->dieUsage( 'This type of file is banned', 'filetype-banned',
- 0, array(
- 'filetype' => $verification['finalExt'],
- 'allowed' => $wgFileExtensions
- ) );
- break;
- case UploadBase::MIN_LENGTH_PARTNAME:
- $this->dieUsage( 'The filename is too short', 'filename-tooshort' );
- break;
- case UploadBase::ILLEGAL_FILENAME:
- $this->dieUsage( 'The filename is not allowed', 'illegal-filename',
- 0, array( 'filename' => $verification['filtered'] ) );
- break;
- case UploadBase::OVERWRITE_EXISTING_FILE:
- $this->dieUsage( 'Overwriting an existing file is not allowed', 'overwrite' );
- break;
- case UploadBase::VERIFICATION_ERROR:
- $this->getResult()->setIndexedTagName( $verification['details'], 'detail' );
- $this->dieUsage( 'This file did not pass file verification', 'verification-error',
- 0, array( 'details' => $verification['details'] ) );
- break;
- case UploadBase::HOOK_ABORTED:
- $this->dieUsage( "The modification you tried to make was aborted by an extension hook",
- 'hookaborted', 0, array( 'error' => $verification['error'] ) );
- break;
- default:
- $this->dieUsage( 'An unknown error occurred', 'unknown-error',
- 0, array( 'code' => $verification['status'] ) );
- break;
- }
- return $result;
+ switch( $verification['status'] ) {
+ case UploadBase::EMPTY_FILE:
+ $this->dieUsage( 'The file you submitted was empty', 'empty-file' );
+ break;
+ case UploadBase::FILE_TOO_LARGE:
+ $this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
+ break;
+ case UploadBase::FILETYPE_MISSING:
+ $this->dieUsage( 'The file is missing an extension', 'filetype-missing' );
+ break;
+ case UploadBase::FILETYPE_BADTYPE:
+ $this->dieUsage( 'This type of file is banned', 'filetype-banned',
+ 0, array(
+ 'filetype' => $verification['finalExt'],
+ 'allowed' => $wgFileExtensions
+ ) );
+ break;
+ case UploadBase::MIN_LENGTH_PARTNAME:
+ $this->dieUsage( 'The filename is too short', 'filename-tooshort' );
+ break;
+ case UploadBase::ILLEGAL_FILENAME:
+ $this->dieUsage( 'The filename is not allowed', 'illegal-filename',
+ 0, array( 'filename' => $verification['filtered'] ) );
+ break;
+ case UploadBase::VERIFICATION_ERROR:
+ $this->getResult()->setIndexedTagName( $verification['details'], 'detail' );
+ $this->dieUsage( 'This file did not pass file verification', 'verification-error',
+ 0, array( 'details' => $verification['details'] ) );
+ break;
+ case UploadBase::HOOK_ABORTED:
+ $this->dieUsage( "The modification you tried to make was aborted by an extension hook",
+ 'hookaborted', 0, array( 'error' => $verification['error'] ) );
+ break;
+ default:
+ $this->dieUsage( 'An unknown error occurred', 'unknown-error',
+ 0, array( 'code' => $verification['status'] ) );
+ break;
}
+ }
+
+
+ /**
+ * Check warnings if ignorewarnings is not set.
+ * Returns a suitable array for inclusion into API results if there were warnings
+ * Returns the empty array if there were no warnings
+ *
+ * @return array
+ */
+ protected function getApiWarnings() {
+ $warnings = array();
+
if ( !$this->mParams['ignorewarnings'] ) {
$warnings = $this->mUpload->checkWarnings();
if ( $warnings ) {
@@ -181,51 +312,70 @@ class ApiUpload extends ApiBase {
if ( isset( $warnings['duplicate'] ) ) {
$dupes = array();
- foreach ( $warnings['duplicate'] as $key => $dupe )
+ foreach ( $warnings['duplicate'] as $dupe ) {
$dupes[] = $dupe->getName();
+ }
$this->getResult()->setIndexedTagName( $dupes, 'duplicate' );
$warnings['duplicate'] = $dupes;
}
-
if ( isset( $warnings['exists'] ) ) {
$warning = $warnings['exists'];
unset( $warnings['exists'] );
$warnings[$warning['warning']] = $warning['file']->getName();
}
-
- $result['result'] = 'Warning';
- $result['warnings'] = $warnings;
-
- $sessionKey = $this->mUpload->stashSession();
- if ( !$sessionKey )
- $this->dieUsage( 'Stashing temporary file failed', 'stashfailed' );
-
- $result['sessionkey'] = $sessionKey;
-
- return $result;
}
}
+ return $warnings;
+ }
+
+ /**
+ * Perform the actual upload. Returns a suitable result array on success;
+ * dies on failure.
+ */
+ protected function performUpload() {
+ global $wgUser;
+
// Use comment as initial page text by default
- if ( is_null( $this->mParams['text'] ) )
+ if ( is_null( $this->mParams['text'] ) ) {
$this->mParams['text'] = $this->mParams['comment'];
+ }
+
+ $file = $this->mUpload->getLocalFile();
+ $watch = $this->getWatchlistValue( $this->mParams['watchlist'], $file->getTitle() );
+
+ // Deprecated parameters
+ if ( $this->mParams['watch'] ) {
+ $watch = true;
+ }
// No errors, no warnings: do the upload
$status = $this->mUpload->performUpload( $this->mParams['comment'],
- $this->mParams['text'], $this->mParams['watch'], $wgUser );
+ $this->mParams['text'], $watch, $wgUser );
if ( !$status->isGood() ) {
$error = $status->getErrorsArray();
- $this->getResult()->setIndexedTagName( $result['details'], 'error' );
- $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
+ if ( count( $error ) == 1 && $error[0][0] == 'async' ) {
+ // The upload can not be performed right now, because the user
+ // requested so
+ return array(
+ 'result' => 'Queued',
+ 'statuskey' => $error[0][1],
+ );
+ } else {
+ $this->getResult()->setIndexedTagName( $error, 'error' );
+
+ $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
+ }
}
$file = $this->mUpload->getLocalFile();
+
$result['result'] = 'Success';
$result['filename'] = $file->getName();
- $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
+
return $result;
}
@@ -240,36 +390,70 @@ class ApiUpload extends ApiBase {
public function getAllowedParams() {
$params = array(
- 'filename' => null,
+ 'filename' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ),
'comment' => array(
ApiBase::PARAM_DFLT => ''
),
'text' => null,
'token' => null,
- 'watch' => false,
+ 'watch' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'watchlist' => array(
+ ApiBase::PARAM_DFLT => 'preferences',
+ ApiBase::PARAM_TYPE => array(
+ 'watch',
+ 'preferences',
+ 'nochange'
+ ),
+ ),
'ignorewarnings' => false,
'file' => null,
'url' => null,
'sessionkey' => null,
+ 'stash' => false,
);
- return $params;
+ global $wgAllowAsyncCopyUploads;
+ if ( $wgAllowAsyncCopyUploads ) {
+ $params += array(
+ 'asyncdownload' => false,
+ 'leavemessage' => false,
+ 'statuskey' => null,
+ );
+ }
+ return $params;
}
public function getParamDescription() {
- return array(
+ $params = array(
'filename' => 'Target filename',
'token' => 'Edit token. You can get one of these through prop=info',
'comment' => 'Upload comment. Also used as the initial page text for new files if "text" is not specified',
'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',
- 'sessionkey' => array(
- 'Session key returned by a previous upload that failed due to warnings',
- ),
+ 'sessionkey' => 'Session key that identifies a previous upload that was stashed temporarily.',
+ 'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.'
);
+
+ global $wgAllowAsyncCopyUploads;
+ if ( $wgAllowAsyncCopyUploads ) {
+ $params += array(
+ '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 session key',
+ );
+ }
+
+ return $params;
+
}
public function getDescription() {
@@ -281,17 +465,16 @@ class ApiUpload extends ApiBase {
'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
'sending the "file". Note also that queries using session keys must be',
'done in the same login session as the query that originally returned the key (i.e. do not',
- 'log out and then log back in). Also you must get and send an edit token before doing any upload stuff.'
+ 'log out and then log back in). Also you must get and send an edit token before doing any upload stuff'
);
}
-
- public function getPossibleErrors() {
+
+ public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'uploaddisabled' ),
array( 'invalid-session-key' ),
array( 'uploaddisabled' ),
array( 'badaccess-groups' ),
- array( 'missingparam', 'filename' ),
array( 'mustbeloggedin', 'upload' ),
array( 'badaccess-groups' ),
array( 'badaccess-groups' ),
@@ -303,9 +486,9 @@ class ApiUpload extends ApiBase {
array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ),
array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ),
array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ),
- ) );
+ ) );
}
-
+
public function needsToken() {
return true;
}
diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php
index be0750d6..f9fe9ad2 100644
--- a/includes/api/ApiUserrights.php
+++ b/includes/api/ApiUserrights.php
@@ -1,10 +1,11 @@
<?php
-/*
- * Created on Mar 24, 2009
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Created on Mar 24, 2009
+ *
+ * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,13 +19,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( "ApiBase.php" );
+ require_once( "ApiBase.php" );
}
/**
@@ -33,16 +36,17 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiUserrights extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
+ private $mUser = null;
+
public function execute() {
$params = $this->extractRequestParams();
-
- // User already validated in call to getTokenSalt from Main
+
+ $user = $this->getUser();
+
$form = new UserrightsPage;
- $user = $form->fetchUser( $params['user'] );
-
$r['user'] = $user->getName();
list( $r['added'], $r['removed'] ) =
$form->doSaveUserGroups(
@@ -54,6 +58,29 @@ class ApiUserrights extends ApiBase {
$this->getResult()->addValue( null, $this->getModuleName(), $r );
}
+ /**
+ * @return User
+ */
+ private function getUser() {
+ if ( $this->mUser !== null ) {
+ return $this->mUser;
+ }
+
+ $params = $this->extractRequestParams();
+
+ $form = new UserrightsPage;
+ $status = $form->fetchUser( $params['user'] );
+ if ( !$status->isOK() ) {
+ $errors = $status->getErrorsArray();
+ $this->dieUsageMsg( $errors[0] );
+ } else {
+ $user = $status->value;
+ }
+
+ $this->mUser = $user;
+ return $user;
+ }
+
public function mustBePosted() {
return true;
}
@@ -64,24 +91,27 @@ class ApiUserrights extends ApiBase {
public function getAllowedParams() {
return array (
- 'user' => null,
+ 'user' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
'add' => array(
- ApiBase :: PARAM_TYPE => User::getAllGroups(),
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_TYPE => User::getAllGroups(),
+ ApiBase::PARAM_ISMULTI => true
),
'remove' => array(
- ApiBase :: PARAM_TYPE => User::getAllGroups(),
- ApiBase :: PARAM_ISMULTI => true
+ ApiBase::PARAM_TYPE => User::getAllGroups(),
+ ApiBase::PARAM_ISMULTI => true
),
'token' => null,
'reason' => array(
- ApiBase :: PARAM_DFLT => ''
+ ApiBase::PARAM_DFLT => ''
)
);
}
public function getParamDescription() {
- return array (
+ return array(
'user' => 'User name',
'add' => 'Add the user to these groups',
'remove' => 'Remove the user from these groups',
@@ -91,42 +121,24 @@ class ApiUserrights extends ApiBase {
}
public function getDescription() {
- return array(
- 'Add/remove a user to/from groups',
- );
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'user' ),
- ) );
+ return 'Add/remove a user to/from groups';
}
public function needsToken() {
return true;
}
-
- public function getTokenSalt() {
- $params = $this->extractRequestParams();
- if ( is_null( $params['user'] ) )
- $this->dieUsageMsg( array( 'missingparam', 'user' ) );
-
- $form = new UserrightsPage;
- $user = $form->fetchUser( $params['user'] );
- if ( $user instanceof WikiErrorMsg )
- $this->dieUsageMsg( array_merge(
- (array)$user->getMessageKey(), $user->getMessageArgs() ) );
- return $user->getName();
+ public function getTokenSalt() {
+ return $this->getUser()->getName();
}
protected function getExamples() {
- return array (
+ return array(
'api.php?action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiUserrights.php 74217 2010-10-03 15:53:07Z reedy $';
+ return __CLASS__ . ': $Id: ApiUserrights.php 75602 2010-10-28 00:04:48Z reedy $';
}
}
diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php
index 391d91e2..e9560a4d 100644
--- a/includes/api/ApiWatch.php
+++ b/includes/api/ApiWatch.php
@@ -1,11 +1,10 @@
<?php
-
-/*
- * Created on Jan 4, 2008
- *
+/**
* API for MediaWiki 1.8+
*
- * Copyright (C) 2008 Yuri Astrakhan <Firstname><Lastname>@gmail.com,
+ * Created on Jan 4, 2008
+ *
+ * Copyright © 2008 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
@@ -19,13 +18,15 @@
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// Eclipse helper - will be ignored in production
- require_once ( 'ApiBase.php' );
+ require_once( 'ApiBase.php' );
}
/**
@@ -36,35 +37,37 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiWatch extends ApiBase {
public function __construct( $main, $action ) {
- parent :: __construct( $main, $action );
+ parent::__construct( $main, $action );
}
public function execute() {
global $wgUser;
- if ( !$wgUser->isLoggedIn() )
+ if ( !$wgUser->isLoggedIn() ) {
$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
+ }
$params = $this->extractRequestParams();
$title = Title::newFromText( $params['title'] );
- if ( !$title )
+ if ( !$title ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
$article = new Article( $title );
$res = array( 'title' => $title->getPrefixedText() );
- if ( $params['unwatch'] )
- {
+ if ( $params['unwatch'] ) {
$res['unwatched'] = '';
+ $res['message'] = wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() );
$success = $article->doUnwatch();
- }
- else
- {
+ } else {
$res['watched'] = '';
+ $res['message'] = wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() );
$success = $article->doWatch();
}
- if ( !$success )
+ if ( !$success ) {
$this->dieUsageMsg( array( 'hookaborted' ) );
+ }
$this->getResult()->addValue( null, $this->getModuleName(), $res );
}
@@ -73,25 +76,27 @@ class ApiWatch extends ApiBase {
}
public function getAllowedParams() {
- return array (
- 'title' => null,
+ return array(
+ 'title' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+
'unwatch' => false,
);
}
public function getParamDescription() {
- return array (
+ return array(
'title' => 'The page to (un)watch',
'unwatch' => 'If set the page will be unwatched rather than watched',
);
}
public function getDescription() {
- return array (
- 'Add or remove a page from/to the current user\'s watchlist'
- );
+ return 'Add or remove a page from/to the current user\'s watchlist';
}
-
+
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'notloggedin', 'info' => 'You must be logged-in to have a watchlist' ),
@@ -103,11 +108,11 @@ class ApiWatch extends ApiBase {
protected function getExamples() {
return array(
'api.php?action=watch&title=Main_Page',
- 'api.php?action=watch&title=Main_Page&unwatch',
+ 'api.php?action=watch&title=Main_Page&unwatch=',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiWatch.php 69578 2010-07-20 02:46:20Z tstarling $';
+ return __CLASS__ . ': $Id: ApiWatch.php 77192 2010-11-23 22:05:27Z btongminh $';
}
}
diff --git a/includes/db/Database.php b/includes/db/Database.php
index ea5d77da..5acb67fa 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -4,7 +4,7 @@
*
* @file
* @ingroup Database
- * This file deals with MySQL interface functions
+ * This file deals with database interface functions
* and query specifics/optimisations
*/
@@ -16,14 +16,200 @@ define( 'DEADLOCK_DELAY_MIN', 500000 );
define( 'DEADLOCK_DELAY_MAX', 1500000 );
/**
+ * 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
+ */
+ public function getType();
+
+ /**
+ * Open a connection to the database. Usually aborts on failure
+ *
+ * @param $server String: database server host
+ * @param $user String: database user name
+ * @param $password String: database user password
+ * @param $dbName String: database name
+ * @return bool
+ * @throws DBConnectionError
+ */
+ public function open( $server, $user, $password, $dbName );
+
+ /**
+ * The DBMS-dependent part of query()
+ * @todo Fixme: Make this private someday
+ *
+ * @param $sql String: SQL query.
+ * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
+ * @private
+ */
+ /*private*/ function doQuery( $sql );
+
+ /**
+ * 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.
+ *
+ * @param $res SQL result object as returned from DatabaseBase::query(), etc.
+ * @return Row object
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ public function fetchObject( $res );
+
+ /**
+ * Fetch the next row from the given result object, in associative array
+ * form. Fields are retrieved with $row['fieldname'].
+ *
+ * @param $res SQL result object as returned from DatabaseBase::query(), etc.
+ * @return Row object
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ public function fetchRow( $res );
+
+ /**
+ * Get the number of rows in a result object
+ *
+ * @param $res Mixed: A SQL result
+ * @return int
+ */
+ public function numRows( $res );
+
+ /**
+ * Get the number of fields in a result object
+ * @see http://www.php.net/mysql_num_fields
+ *
+ * @param $res Mixed: A SQL result
+ * @return int
+ */
+ public function numFields( $res );
+
+ /**
+ * Get a field name in a result object
+ * @see http://www.php.net/mysql_field_name
+ *
+ * @param $res Mixed: A SQL result
+ * @param $n Integer
+ * @return string
+ */
+ public 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
+ */
+ public function insertId();
+
+ /**
+ * Change the position of the cursor in a result object
+ * @see http://www.php.net/mysql_data_seek
+ *
+ * @param $res Mixed: A SQL result
+ * @param $row Mixed: Either MySQL row or ResultWrapper
+ */
+ public function dataSeek( $res, $row );
+
+ /**
+ * Get the last error number
+ * @see http://www.php.net/mysql_errno
+ *
+ * @return int
+ */
+ public function lastErrno();
+
+ /**
+ * Get a description of the last error
+ * @see http://www.php.net/mysql_error
+ *
+ * @return string
+ */
+ public function lastError();
+
+ /**
+ * mysql_fetch_field() wrapper
+ * Returns false if the field doesn't exist
+ *
+ * @param $table string: table name
+ * @param $field string: field name
+ */
+ public function fieldInfo( $table, $field );
+
+ /**
+ * Get information about an index into an object
+ * @param $table string: Table name
+ * @param $index string: Index name
+ * @param $fname string: Calling function name
+ * @return Mixed: Database-specific index description class or false if the index does not exist
+ */
+ function indexInfo( $table, $index, $fname = 'Database::indexInfo' );
+
+ /**
+ * Get the number of rows affected by the last write query
+ * @see http://www.php.net/mysql_affected_rows
+ *
+ * @return int
+ */
+ public function affectedRows();
+
+ /**
+ * Wrapper for addslashes()
+ *
+ * @param $s string: to be slashed.
+ * @return string: slashed string.
+ */
+ public 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
+ */
+ public static function getSoftwareLink();
+
+ /**
+ * A string describing the current software version, like from
+ * mysql_get_server_info().
+ *
+ * @return string: Version information from the database server.
+ */
+ public 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
+ */
+ public function getServerInfo();
+}
+
+/**
* Database abstraction object
* @ingroup Database
*/
-abstract class DatabaseBase {
+abstract class DatabaseBase implements DatabaseType {
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Variables
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
protected $mLastQuery = '';
protected $mDoneWrites = false;
@@ -32,7 +218,6 @@ abstract class DatabaseBase {
protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
protected $mOpened = false;
- protected $mFailFunction;
protected $mTablePrefix;
protected $mFlags;
protected $mTrxLevel = 0;
@@ -40,26 +225,22 @@ abstract class DatabaseBase {
protected $mLBInfo = array();
protected $mFakeSlaveLag = null, $mFakeMaster = false;
protected $mDefaultBigSelects = null;
+ protected $mSchemaVars = false;
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Accessors
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# These optionally set a variable and return the previous state
/**
- * Fail function, takes a Database as a parameter
- * Set to false for default, 1 for ignore errors
- */
- function failFunction( $function = null ) {
- return wfSetVar( $this->mFailFunction, $function );
- }
-
- /**
- * Output page, used for reporting errors
- * FALSE means discard output
+ * 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 setOutputPage( $out ) {
- wfDeprecated( __METHOD__ );
+ public function getServerInfo() {
+ return $this->getServerVersion();
}
/**
@@ -193,8 +374,8 @@ abstract class DatabaseBase {
}
/**
- * Returns true if this database requires that SELECT DISTINCT queries require that all
- ORDER BY expressions occur in the SELECT list per the SQL92 standard
+ * Returns true if this database requires that SELECT DISTINCT queries require that all
+ ORDER BY expressions occur in the SELECT list per the SQL92 standard
*/
function standardSelectDistinct() {
return true;
@@ -216,7 +397,7 @@ abstract class DatabaseBase {
}
/**
- * Return the last query that went through Database::query()
+ * Return the last query that went through DatabaseBase::query()
* @return String
*/
function lastQuery() { return $this->mLastQuery; }
@@ -244,7 +425,7 @@ abstract class DatabaseBase {
* - DBO_TRX: automatically start transactions
* - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
* and removes it in command line mode
- * - DBO_PERSISTENT: use persistant database connection
+ * - DBO_PERSISTENT: use persistant database connection
*/
function setFlag( $flag ) {
$this->mFlags |= $flag;
@@ -266,7 +447,7 @@ abstract class DatabaseBase {
* @return Boolean
*/
function getFlag( $flag ) {
- return !!($this->mFlags & $flag);
+ return !!( $this->mFlags & $flag );
}
/**
@@ -277,7 +458,7 @@ abstract class DatabaseBase {
}
function getWikiID() {
- if( $this->mTablePrefix ) {
+ if ( $this->mTablePrefix ) {
return "{$this->mDBname}-{$this->mTablePrefix}";
} else {
return $this->mDBname;
@@ -285,13 +466,20 @@ abstract class DatabaseBase {
}
/**
- * Get the type of the DBMS, as it appears in $wgDBtype.
+ * Return a path to the DBMS-specific schema, otherwise default to tables.sql
*/
- abstract function getType();
+ public function getSchema() {
+ global $IP;
+ if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
+ return "$IP/maintenance/" . $this->getType() . "/tables.sql";
+ } else {
+ return "$IP/maintenance/tables.sql";
+ }
+ }
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Other functions
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
/**
* Constructor.
@@ -299,20 +487,18 @@ abstract class DatabaseBase {
* @param $user String: database user name
* @param $password String: database user password
* @param $dbName String: database name
- * @param $failFunction
* @param $flags
* @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php
*/
function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) {
-
+ $flags = 0, $tablePrefix = 'get from global'
+ ) {
global $wgOut, $wgDBprefix, $wgCommandLineMode;
+
# Can't get a reference if it hasn't been set yet
if ( !isset( $wgOut ) ) {
$wgOut = null;
}
-
- $this->mFailFunction = $failFunction;
$this->mFlags = $flags;
if ( $this->mFlags & DBO_DEFAULT ) {
@@ -344,27 +530,53 @@ abstract class DatabaseBase {
/**
* Same as new DatabaseMysql( ... ), kept for backward compatibility
- * @param $server String: database server host
- * @param $user String: database user name
- * @param $password String: database user password
- * @param $dbName String: database name
- * @param failFunction
- * @param $flags
+ * @deprecated
*/
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
- {
- return new DatabaseMysql( $server, $user, $password, $dbName, $failFunction, $flags );
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
+ wfDeprecated( __METHOD__ );
+ return new DatabaseMysql( $server, $user, $password, $dbName, $flags );
}
/**
- * Usually aborts on failure
- * If the failFunction is set to a non-zero integer, returns success
- * @param $server String: database server host
- * @param $user String: database user name
- * @param $password String: database user password
- * @param $dbName String: database name
- */
- abstract function open( $server, $user, $password, $dbName );
+ * Given a DB type, construct the name of the appropriate child class of
+ * DatabaseBase. This is designed to replace all of the manual stuff like:
+ * $class = 'Database' . ucfirst( strtolower( $type ) );
+ * as well as validate against the canonical list of DB types we have
+ *
+ * This factory function is mostly useful for when you need to connect to a
+ * database other than the MediaWiki default (such as for external auth,
+ * an extension, et cetera). Do not use this to connect to the MediaWiki
+ * database. Example uses in core:
+ * @see LoadBalancer::reallyOpenConnection()
+ * @see ExternalUser_MediaWiki::initFromCond()
+ * @see ForeignDBRepo::getMasterDB()
+ * @see WebInstaller_DBConnect::execute()
+ *
+ * @param $dbType String A possible DB type
+ * @param $p Array An array of options to pass to the constructor.
+ * Valid options are: host, user, password, dbname, flags, tableprefix
+ * @return DatabaseBase subclass or null
+ */
+ public final static function newFromType( $dbType, $p = array() ) {
+ $canonicalDBTypes = array(
+ 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
+ );
+ $dbType = strtolower( $dbType );
+
+ if( in_array( $dbType, $canonicalDBTypes ) ) {
+ $class = 'Database' . ucfirst( $dbType );
+ return new $class(
+ isset( $p['host'] ) ? $p['host'] : false,
+ isset( $p['user'] ) ? $p['user'] : false,
+ isset( $p['password'] ) ? $p['password'] : false,
+ isset( $p['dbname'] ) ? $p['dbname'] : false,
+ isset( $p['flags'] ) ? $p['flags'] : 0,
+ isset( $p['tableprefix'] ) ? $p['tableprefix'] : 'get from global'
+ );
+ } else {
+ return null;
+ }
+ }
protected function installErrorHandler() {
$this->mPHPError = false;
@@ -402,7 +614,7 @@ abstract class DatabaseBase {
}
/**
- * @param $error String: fallback error message, used if none is given by MySQL
+ * @param $error String: fallback error message, used if none is given by DB
*/
function reportConnectionError( $error = 'Unknown error' ) {
$myError = $this->lastError();
@@ -410,16 +622,8 @@ abstract class DatabaseBase {
$error = $myError;
}
- if ( $this->mFailFunction ) {
- # Legacy error handling method
- if ( !is_int( $this->mFailFunction ) ) {
- $ff = $this->mFailFunction;
- $ff( $this, $error );
- }
- } else {
- # New method
- throw new DBConnectionError( $this, $error );
- }
+ # New method
+ throw new DBConnectionError( $this, $error );
}
/**
@@ -434,11 +638,11 @@ abstract class DatabaseBase {
* Usually aborts on failure. If errors are explicitly ignored, returns success.
*
* @param $sql String: SQL query
- * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST
+ * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST
* comment (you can use __METHOD__ or add some extra info)
- * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
+ * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
* maybe best to catch the exception instead?
- * @return true for a successful write query, ResultWrapper object for a successful read query,
+ * @return boolean or ResultWrapper. true for a successful write query, ResultWrapper object for a successful read query,
* or false on failure if $tempIgnore set
* @throws DBQueryError Thrown when the database returns an error of any kind
*/
@@ -451,15 +655,16 @@ abstract class DatabaseBase {
# logging size most of the time. The substr is really just a sanity check.
# Who's been wasting my precious column space? -- TS
- #$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
+ # $profName = 'query: ' . $fname . ' ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
if ( $isMaster ) {
- $queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'Database::query-master';
+ $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query-master';
} else {
- $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'Database::query';
+ $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query';
}
+
wfProfileIn( $totalProf );
wfProfileIn( $queryProf );
}
@@ -467,14 +672,14 @@ abstract class DatabaseBase {
$this->mLastQuery = $sql;
if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
// Set a flag indicating that writes have been done
- wfDebug( __METHOD__.": Writes done: $sql\n" );
+ wfDebug( __METHOD__ . ": Writes done: $sql\n" );
$this->mDoneWrites = true;
}
# Add a comment for easy SHOW PROCESSLIST interpretation
- #if ( $fname ) {
+ # if ( $fname ) {
global $wgUser;
- if ( is_object( $wgUser ) && !($wgUser instanceof StubObject) ) {
+ if ( is_object( $wgUser ) && $wgUser->mDataLoaded ) {
$userName = $wgUser->getName();
if ( mb_strlen( $userName ) > 15 ) {
$userName = mb_substr( $userName, 0, 15 ) . '...';
@@ -483,29 +688,33 @@ abstract class DatabaseBase {
} else {
$userName = '';
}
- $commentedSql = preg_replace('/\s/', " /* $fname $userName */ ", $sql, 1);
- #} else {
+ $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 );
+ # } else {
# $commentedSql = $sql;
- #}
+ # }
# If DBO_TRX is set, start a transaction
- if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') {
+ if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
+ $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) {
// avoid establishing transactions for SHOW and SET statements too -
- // that would delay transaction initializations to once connection
+ // 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 and strpos($sqlstart,"SET ")!==0)
- $this->begin();
+ $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
+ if ( strpos( $sqlstart, "SHOW " ) !== 0 and strpos( $sqlstart, "SET " ) !== 0 )
+ $this->begin();
}
if ( $this->debug() ) {
+ static $cnt = 0;
+
+ $cnt++;
$sqlx = substr( $commentedSql, 0, 500 );
$sqlx = strtr( $sqlx, "\t\n", ' ' );
+
if ( $isMaster ) {
- wfDebug( "SQL-master: $sqlx\n" );
+ wfDebug( "Query $cnt (master): $sqlx\n" );
} else {
- wfDebug( "SQL: $sqlx\n" );
+ wfDebug( "Query $cnt (slave): $sqlx\n" );
}
}
@@ -521,13 +730,17 @@ abstract class DatabaseBase {
# Transaction is gone, like it or not
$this->mTrxLevel = 0;
wfDebug( "Connection lost, reconnecting...\n" );
+
if ( $this->ping() ) {
wfDebug( "Reconnected\n" );
$sqlx = substr( $commentedSql, 0, 500 );
$sqlx = strtr( $sqlx, "\t\n", ' ' );
global $wgRequestTime;
- $elapsed = round( microtime(true) - $wgRequestTime, 3 );
- wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\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\n" );
+ }
$ret = $this->doQuery( $commentedSql );
} else {
wfDebug( "Failed\n" );
@@ -542,18 +755,11 @@ abstract class DatabaseBase {
wfProfileOut( $queryProf );
wfProfileOut( $totalProf );
}
+
return $this->resultObject( $ret );
}
/**
- * The DBMS-dependent part of query()
- * @param $sql String: SQL query.
- * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
- * @private
- */
- /*private*/ abstract function doQuery( $sql );
-
- /**
* @param $error String
* @param $errno Integer
* @param $sql String
@@ -561,18 +767,17 @@ abstract class DatabaseBase {
* @param $tempIgnore Boolean
*/
function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- global $wgCommandLineMode;
# Ignore errors during error handling to avoid infinite recursion
$ignore = $this->ignoreErrors( true );
++$this->mErrorCount;
- if( $ignore || $tempIgnore ) {
- wfDebug("SQL ERROR (ignored): $error\n");
+ if ( $ignore || $tempIgnore ) {
+ wfDebug( "SQL ERROR (ignored): $error\n" );
$this->ignoreErrors( $ignore );
} else {
$sql1line = str_replace( "\n", "\\n", $sql );
- wfLogDBError("$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n");
- wfDebug("SQL ERROR: " . $error . "\n");
+ wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" );
+ wfDebug( "SQL ERROR: " . $error . "\n" );
throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
}
@@ -587,7 +792,7 @@ abstract class DatabaseBase {
* & = filename; reads the file and inserts as a blob
* (we don't use this though...)
*/
- function prepare( $sql, $func = 'Database::prepare' ) {
+ function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
/* MySQL doesn't support prepared statements (yet), so just
pack up the query for reference. We'll manually replace
the bits later. */
@@ -595,7 +800,7 @@ abstract class DatabaseBase {
}
function freePrepared( $prepared ) {
- /* No-op for MySQL */
+ /* No-op by default */
}
/**
@@ -604,12 +809,14 @@ abstract class DatabaseBase {
* @param $args Mixed: Either an array here, or put scalars as varargs
*/
function execute( $prepared, $args = null ) {
- if( !is_array( $args ) ) {
+ if ( !is_array( $args ) ) {
# Pull the var args
$args = func_get_args();
array_shift( $args );
}
+
$sql = $this->fillPrepared( $prepared['query'], $args );
+
return $this->query( $sql, $prepared['func'] );
}
@@ -620,14 +827,17 @@ abstract class DatabaseBase {
* @param $args ...
*/
function safeQuery( $query, $args = null ) {
- $prepared = $this->prepare( $query, 'Database::safeQuery' );
- if( !is_array( $args ) ) {
+ $prepared = $this->prepare( $query, 'DatabaseBase::safeQuery' );
+
+ if ( !is_array( $args ) ) {
# Pull the var args
$args = func_get_args();
array_shift( $args );
}
+
$retval = $this->execute( $prepared, $args );
$this->freePrepared( $prepared );
+
return $retval;
}
@@ -641,6 +851,7 @@ abstract class DatabaseBase {
function fillPrepared( $preparedQuery, $args ) {
reset( $args );
$this->preparedArgs =& $args;
+
return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
array( &$this, 'fillPreparedArg' ), $preparedQuery );
}
@@ -660,7 +871,9 @@ abstract class DatabaseBase {
case '\\!': return '!';
case '\\&': return '&';
}
+
list( /* $n */ , $arg ) = each( $this->preparedArgs );
+
switch( $matches[1] ) {
case '?': return $this->addQuotes( $arg );
case '!': return $arg;
@@ -682,98 +895,18 @@ abstract class DatabaseBase {
}
/**
- * 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.
- *
- * @param $res SQL result object as returned from Database::query(), etc.
- * @return MySQL row object
- * @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'].
- *
- * @param $res SQL result object as returned from Database::query(), etc.
- * @return MySQL row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- abstract function fetchRow( $res );
-
- /**
- * Get the number of rows in a result object
- * @param $res Mixed: A SQL result
- */
- abstract function numRows( $res );
-
- /**
- * Get the number of fields in a result object
- * See documentation for mysql_num_fields()
- * @param $res Mixed: A SQL result
- */
- abstract function numFields( $res );
-
- /**
- * Get a field name in a result object
- * See documentation for mysql_field_name():
- * http://www.php.net/mysql_field_name
- * @param $res Mixed: A SQL result
- * @param $n Integer
- */
- 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();
- */
- abstract function insertId();
-
- /**
- * Change the position of the cursor in a result object
- * See mysql_data_seek()
- * @param $res Mixed: A SQL result
- * @param $row Mixed: Either MySQL row or ResultWrapper
- */
- abstract function dataSeek( $res, $row );
-
- /**
- * Get the last error number
- * See mysql_errno()
- */
- abstract function lastErrno();
-
- /**
- * Get a description of the last error
- * See mysql_error() for more details
- */
- abstract function lastError();
-
- /**
- * Get the number of rows affected by the last write query
- * See mysql_affected_rows() for more details
- */
- abstract function affectedRows();
-
- /**
* Simple UPDATE wrapper
* Usually aborts on failure
* If errors are explicitly ignored, returns success
*
- * This function exists for historical reasons, Database::update() has a more standard
+ * This function exists for historical reasons, DatabaseBase::update() has a more standard
* calling convention and feature set
*/
- function set( $table, $var, $value, $cond, $fname = 'Database::set' ) {
+ function set( $table, $var, $value, $cond, $fname = 'DatabaseBase::set' ) {
$table = $this->tableName( $table );
$sql = "UPDATE $table SET $var = '" .
$this->strencode( $value ) . "' WHERE ($cond)";
+
return (bool)$this->query( $sql, $fname );
}
@@ -782,19 +915,22 @@ abstract class DatabaseBase {
* Usually aborts on failure
* If errors are explicitly ignored, returns FALSE on failure
*/
- function selectField( $table, $var, $cond='', $fname = 'Database::selectField', $options = array() ) {
+ function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField', $options = array() ) {
if ( !is_array( $options ) ) {
$options = array( $options );
}
+
$options['LIMIT'] = 1;
$res = $this->select( $table, $var, $cond, $fname, $options );
+
if ( $res === false || !$this->numRows( $res ) ) {
return false;
}
+
$row = $this->fetchRow( $res );
+
if ( $row !== false ) {
- $this->freeResult( $res );
return reset( $row );
} else {
return false;
@@ -816,42 +952,82 @@ abstract class DatabaseBase {
$startOpts = '';
$noKeyOptions = array();
+
foreach ( $options as $key => $option ) {
if ( is_numeric( $key ) ) {
$noKeyOptions[$option] = true;
}
}
- if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
- if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
-
- //if (isset($options['LIMIT'])) {
+ if ( isset( $options['GROUP BY'] ) ) {
+ $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
+ }
+
+ if ( isset( $options['HAVING'] ) ) {
+ $preLimitTail .= " HAVING {$options['HAVING']}";
+ }
+
+ if ( isset( $options['ORDER BY'] ) ) {
+ $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
+ }
+
+ // if (isset($options['LIMIT'])) {
// $tailOpts .= $this->limitResult('', $options['LIMIT'],
- // isset($options['OFFSET']) ? $options['OFFSET']
+ // isset($options['OFFSET']) ? $options['OFFSET']
// : false);
- //}
+ // }
+
+ if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+ $postLimitTail .= ' FOR UPDATE';
+ }
+
+ if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
+ $postLimitTail .= ' LOCK IN SHARE MODE';
+ }
- if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
- if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
+ if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
+ $startOpts .= 'DISTINCT';
+ }
# Various MySQL extensions
- if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */';
- if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY';
- if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT';
- if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT';
- if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT';
- if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS';
- if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE';
- if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE';
+ if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
+ $startOpts .= ' /*! STRAIGHT_JOIN */';
+ }
+
+ if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
+ $startOpts .= ' HIGH_PRIORITY';
+ }
+
+ if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
+ $startOpts .= ' SQL_BIG_RESULT';
+ }
+
+ if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
+ $startOpts .= ' SQL_BUFFER_RESULT';
+ }
+
+ if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
+ $startOpts .= ' SQL_SMALL_RESULT';
+ }
+
+ if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
+ $startOpts .= ' SQL_CALC_FOUND_ROWS';
+ }
+
+ if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
+ $startOpts .= ' SQL_CACHE';
+ }
+
+ if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
+ $startOpts .= ' SQL_NO_CACHE';
+ }
if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
$useIndex = $this->useIndexClause( $options['USE INDEX'] );
} else {
$useIndex = '';
}
-
+
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
@@ -863,44 +1039,47 @@ abstract class DatabaseBase {
* @param $conds Mixed: Array or string, condition(s) for WHERE
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
* @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
+ * see DatabaseBase::makeSelectOptions code for list of supported stuff
* @param $join_conds Array: Associative array of table join conditions (optional)
* (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
+ * @return mixed Database result resource (feed to DatabaseBase::fetchObject or whatever), or false on failure
*/
- function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() )
- {
+ function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', $options = array(), $join_conds = array() ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+
return $this->query( $sql, $fname );
}
-
+
/**
* SELECT wrapper
*
- * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
+ * @param $table Mixed: Array or string, table name(s) (prefix auto-added). Array keys are table aliases (optional)
* @param $vars Mixed: Array or string, field name(s) to be retrieved
* @param $conds Mixed: Array or string, condition(s) for WHERE
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
* @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
+ * see DatabaseBase::makeSelectOptions code for list of supported stuff
* @param $join_conds Array: Associative array of table join conditions (optional)
* (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
* @return string, the SQL text
*/
- function selectSQLText( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() ) {
- if( is_array( $vars ) ) {
+ function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', $options = array(), $join_conds = array() ) {
+ if ( is_array( $vars ) ) {
$vars = implode( ',', $vars );
}
- if( !is_array( $options ) ) {
+
+ if ( !is_array( $options ) ) {
$options = array( $options );
}
- if( is_array( $table ) ) {
- if ( !empty($join_conds) || ( isset( $options['USE INDEX'] ) && is_array( @$options['USE INDEX'] ) ) )
+
+ if ( is_array( $table ) ) {
+ if ( !empty( $join_conds ) || ( isset( $options['USE INDEX'] ) && is_array( @$options['USE INDEX'] ) ) ) {
$from = ' FROM ' . $this->tableNamesWithUseIndexOrJOIN( $table, @$options['USE INDEX'], $join_conds );
- else
- $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
- } elseif ($table!='') {
- if ($table{0}==' ') {
+ } else {
+ $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
+ }
+ } elseif ( $table != '' ) {
+ if ( $table { 0 } == ' ' ) {
$from = ' FROM ' . $table;
} else {
$from = ' FROM ' . $this->tableName( $table );
@@ -911,7 +1090,7 @@ abstract class DatabaseBase {
list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
- if( !empty( $conds ) ) {
+ if ( !empty( $conds ) ) {
if ( is_array( $conds ) ) {
$conds = $this->makeList( $conds, LIST_AND );
}
@@ -920,14 +1099,15 @@ abstract class DatabaseBase {
$sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
}
- if (isset($options['LIMIT']))
- $sql = $this->limitResult($sql, $options['LIMIT'],
- isset($options['OFFSET']) ? $options['OFFSET'] : false);
+ if ( isset( $options['LIMIT'] ) )
+ $sql = $this->limitResult( $sql, $options['LIMIT'],
+ isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
$sql = "$sql $postLimitTail";
-
- if (isset($options['EXPLAIN'])) {
+
+ if ( isset( $options['EXPLAIN'] ) ) {
$sql = 'EXPLAIN ' . $sql;
}
+
return $sql;
}
@@ -949,42 +1129,45 @@ abstract class DatabaseBase {
*
* @todo migrate documentation to phpdocumentor format
*/
- function selectRow( $table, $vars, $conds, $fname = 'Database::selectRow', $options = array(), $join_conds = array() ) {
+ function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow', $options = array(), $join_conds = array() ) {
$options['LIMIT'] = 1;
$res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
- if ( $res === false )
+
+ if ( $res === false ) {
return false;
- if ( !$this->numRows($res) ) {
- $this->freeResult($res);
+ }
+
+ if ( !$this->numRows( $res ) ) {
return false;
}
+
$obj = $this->fetchObject( $res );
- $this->freeResult( $res );
- return $obj;
+ return $obj;
}
-
+
/**
* Estimate rows in dataset
* Returns estimated count - not necessarily an accurate estimate across different databases,
* so use sparingly
- * Takes same arguments as Database::select()
+ * Takes same arguments as DatabaseBase::select()
*
- * @param string $table table name
- * @param array $vars unused
- * @param array $conds filters on the table
- * @param string $fname function name for profiling
- * @param array $options options for select
- * @return int row count
+ * @param $table String: table name
+ * @param $vars Array: unused
+ * @param $conds Array: filters on the table
+ * @param $fname String: function name for profiling
+ * @param $options Array: options for select
+ * @return Integer: row count
*/
- public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
+ public function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseBase::estimateRowCount', $options = array() ) {
$rows = 0;
$res = $this->select ( $table, 'COUNT(*) AS rowcount', $conds, $fname, $options );
+
if ( $res ) {
$row = $this->fetchRow( $res );
$rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
}
- $this->freeResult( $res );
+
return $rows;
}
@@ -999,42 +1182,33 @@ abstract class DatabaseBase {
# as to avoid crashing php on some large strings.
# $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
- $sql = str_replace ( "\\\\", '', $sql);
- $sql = str_replace ( "\\'", '', $sql);
- $sql = str_replace ( "\\\"", '', $sql);
- $sql = preg_replace ("/'.*'/s", "'X'", $sql);
- $sql = preg_replace ('/".*"/s', "'X'", $sql);
+ $sql = str_replace ( "\\\\", '', $sql );
+ $sql = str_replace ( "\\'", '', $sql );
+ $sql = str_replace ( "\\\"", '', $sql );
+ $sql = preg_replace ( "/'.*'/s", "'X'", $sql );
+ $sql = preg_replace ( '/".*"/s', "'X'", $sql );
# All newlines, tabs, etc replaced by single space
- $sql = preg_replace ( '/\s+/', ' ', $sql);
+ $sql = preg_replace ( '/\s+/', ' ', $sql );
# All numbers => N
- $sql = preg_replace ('/-?[0-9]+/s', 'N', $sql);
+ $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql );
return $sql;
}
/**
* Determines whether a field exists in a table
- * Usually aborts on failure
- * If errors are explicitly ignored, returns NULL on failure
+ *
+ * @param $table String: table name
+ * @param $field String: filed to check on that table
+ * @param $fname String: calling function name (optional)
+ * @return Boolean: whether $table has filed $field
*/
- function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) {
- $table = $this->tableName( $table );
- $res = $this->query( 'DESCRIBE '.$table, $fname );
- if ( !$res ) {
- return null;
- }
-
- $found = false;
+ function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
+ $info = $this->fieldInfo( $table, $field );
- while ( $row = $this->fetchObject( $res ) ) {
- if ( $row->Field == $field ) {
- $found = true;
- break;
- }
- }
- return $found;
+ return (bool)$info;
}
/**
@@ -1042,7 +1216,7 @@ abstract class DatabaseBase {
* Usually aborts on failure
* If errors are explicitly ignored, returns NULL on failure
*/
- function indexExists( $table, $index, $fname = 'Database::indexExists' ) {
+ function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
$info = $this->indexInfo( $table, $index, $fname );
if ( is_null( $info ) ) {
return null;
@@ -1051,58 +1225,17 @@ abstract class DatabaseBase {
}
}
-
- /**
- * Get information about an index into an object
- * Returns false if the index does not exist
- */
- function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
- # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
- # SHOW INDEX should work for 3.x and up:
- # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
- $table = $this->tableName( $table );
- $index = $this->indexName( $index );
- $sql = 'SHOW INDEX FROM '.$table;
- $res = $this->query( $sql, $fname );
- if ( !$res ) {
- return null;
- }
-
- $result = array();
- while ( $row = $this->fetchObject( $res ) ) {
- if ( $row->Key_name == $index ) {
- $result[] = $row;
- }
- }
- $this->freeResult($res);
-
- return empty($result) ? false : $result;
- }
-
/**
* Query whether a given table exists
*/
function tableExists( $table ) {
$table = $this->tableName( $table );
$old = $this->ignoreErrors( true );
- $res = $this->query( "SELECT 1 FROM $table LIMIT 1" );
+ $res = $this->query( "SELECT 1 FROM $table LIMIT 1", __METHOD__ );
$this->ignoreErrors( $old );
- if( $res ) {
- $this->freeResult( $res );
- return true;
- } else {
- return false;
- }
- }
- /**
- * mysql_fetch_field() wrapper
- * Returns false if the field doesn't exist
- *
- * @param $table
- * @param $field
- */
- abstract function fieldInfo( $table, $field );
+ return (bool)$res;
+ }
/**
* mysql_field_type() wrapper
@@ -1111,6 +1244,7 @@ abstract class DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return mysql_field_type( $res, $index );
}
@@ -1119,9 +1253,11 @@ abstract class DatabaseBase {
*/
function indexUnique( $table, $index ) {
$indexInfo = $this->indexInfo( $table, $index );
+
if ( !$indexInfo ) {
return null;
}
+
return !$indexInfo[0]->Non_unique;
}
@@ -1133,17 +1269,26 @@ abstract class DatabaseBase {
*
* Usually aborts on failure
* If errors are explicitly ignored, returns success
+ *
+ * @param $table String: table name (prefix auto-added)
+ * @param $a Array: Array of rows to insert
+ * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
+ * @param $options Mixed: Associative array of options
+ *
+ * @return bool
*/
- function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
+ function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
# No rows to insert, easy just return now
if ( !count( $a ) ) {
return true;
}
$table = $this->tableName( $table );
+
if ( !is_array( $options ) ) {
$options = array( $options );
}
+
if ( isset( $a[0] ) && is_array( $a[0] ) ) {
$multi = true;
$keys = array_keys( $a[0] );
@@ -1168,26 +1313,33 @@ abstract class DatabaseBase {
} else {
$sql .= '(' . $this->makeList( $a ) . ')';
}
+
return (bool)$this->query( $sql, $fname );
}
/**
- * Make UPDATE options for the Database::update function
+ * Make UPDATE options for the DatabaseBase::update function
*
* @private
- * @param $options Array: The options passed to Database::update
+ * @param $options Array: The options passed to DatabaseBase::update
* @return string
*/
function makeUpdateOptions( $options ) {
- if( !is_array( $options ) ) {
+ if ( !is_array( $options ) ) {
$options = array( $options );
}
+
$opts = array();
- if ( in_array( 'LOW_PRIORITY', $options ) )
+
+ if ( in_array( 'LOW_PRIORITY', $options ) ) {
$opts[] = $this->lowPriorityOption();
- if ( in_array( 'IGNORE', $options ) )
+ }
+
+ if ( in_array( 'IGNORE', $options ) ) {
$opts[] = 'IGNORE';
- return implode(' ', $opts);
+ }
+
+ return implode( ' ', $opts );
}
/**
@@ -1202,13 +1354,15 @@ abstract class DatabaseBase {
* more of IGNORE, LOW_PRIORITY
* @return Boolean
*/
- function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
+ function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) {
$table = $this->tableName( $table );
$opts = $this->makeUpdateOptions( $options );
$sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
+
if ( $conds != '*' ) {
$sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
}
+
return $this->query( $sql, $fname );
}
@@ -1223,16 +1377,17 @@ abstract class DatabaseBase {
*/
function makeList( $a, $mode = LIST_COMMA ) {
if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
+ throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
}
$first = true;
$list = '';
+
foreach ( $a as $field => $value ) {
if ( !$first ) {
if ( $mode == LIST_AND ) {
$list .= ' AND ';
- } elseif($mode == LIST_OR) {
+ } elseif ( $mode == LIST_OR ) {
$list .= ' OR ';
} else {
$list .= ',';
@@ -1240,23 +1395,24 @@ abstract class DatabaseBase {
} else {
$first = false;
}
- if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
+
+ if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
$list .= "($value)";
- } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
+ } 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' );
- } elseif( count( $value ) == 1 ) {
+ } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
+ if ( count( $value ) == 0 ) {
+ throw new MWException( __METHOD__ . ': empty input' );
+ } 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] );
+ $list .= $field . " = " . $this->addQuotes( $value[0] );
} else {
- $list .= $field." IN (".$this->makeList($value).") ";
+ $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
}
- } elseif( $value === null ) {
+ } elseif ( $value === null ) {
if ( $mode == LIST_AND || $mode == LIST_OR ) {
$list .= "$field IS ";
} elseif ( $mode == LIST_SET ) {
@@ -1270,35 +1426,64 @@ abstract class DatabaseBase {
$list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
}
}
+
return $list;
}
/**
+ * Build a partial where clause from a 2-d array such as used for LinkBatch.
+ * The keys on each level may be either integers or strings.
+ *
+ * @param $data Array: organized as 2-d array(baseKeyVal => array(subKeyVal => <ignored>, ...), ...)
+ * @param $baseKey String: field name to match the base-level keys to (eg 'pl_namespace')
+ * @param $subKey String: field name to match the sub-level keys to (eg 'pl_title')
+ * @return Mixed: string SQL fragment, or false if no items in array.
+ */
+ function makeWhereFrom2d( $data, $baseKey, $subKey ) {
+ $conds = array();
+
+ foreach ( $data as $base => $sub ) {
+ if ( count( $sub ) ) {
+ $conds[] = $this->makeList(
+ array( $baseKey => $base, $subKey => array_keys( $sub ) ),
+ LIST_AND );
+ }
+ }
+
+ if ( $conds ) {
+ return $this->makeList( $conds, LIST_OR );
+ } else {
+ // Nothing to search for...
+ return false;
+ }
+ }
+
+ /**
* Bitwise operations
*/
- function bitNot($field) {
- return "(~$bitField)";
+ function bitNot( $field ) {
+ return "(~$field)";
}
- function bitAnd($fieldLeft, $fieldRight) {
+ function bitAnd( $fieldLeft, $fieldRight ) {
return "($fieldLeft & $fieldRight)";
}
- function bitOr($fieldLeft, $fieldRight) {
+ function bitOr( $fieldLeft, $fieldRight ) {
return "($fieldLeft | $fieldRight)";
}
/**
* Change the current database
*
+ * @todo Explain what exactly will fail if this is not overridden.
* @return bool Success or failure
*/
function selectDB( $db ) {
# Stub. Shouldn't cause serious problems if it's not overridden, but
# if your database engine supports a concept similar to MySQL's
- # databases you may as well. TODO: explain what exactly will fail if
- # this is not overridden.
+ # databases you may as well.
return true;
}
@@ -1335,8 +1520,10 @@ abstract class DatabaseBase {
# Note that we check the end so that we will still quote any use of
# use of `database`.table. But won't break things if someone wants
# to query a database table with a dot in the name.
- if ( $name[0] == '`' && substr( $name, -1, 1 ) == '`' ) return $name;
-
+ if ( $name[0] == '`' && substr( $name, -1, 1 ) == '`' ) {
+ return $name;
+ }
+
# Lets test for any bits of text that should never show up in a table
# name. Basically anything like JOIN or ON which are actually part of
# SQL queries, but may end up inside of the table value to combine
@@ -1344,23 +1531,30 @@ abstract class DatabaseBase {
# Note that we use a whitespace test rather than a \b test to avoid
# any remote case where a word like on may be inside of a table name
# surrounded by symbols which may be considered word breaks.
- if( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) return $name;
-
+ if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
+ return $name;
+ }
+
# Split database and table into proper variables.
# We reverse the explode so that database.table and table both output
# the correct table.
$dbDetails = array_reverse( explode( '.', $name, 2 ) );
- if( isset( $dbDetails[1] ) ) @list( $table, $database ) = $dbDetails;
- else @list( $table ) = $dbDetails;
+ if ( isset( $dbDetails[1] ) ) {
+ @list( $table, $database ) = $dbDetails;
+ } else {
+ @list( $table ) = $dbDetails;
+ }
$prefix = $this->mTablePrefix; # Default prefix
-
+
# A database name has been specified in input. Quote the table name
# because we don't want any prefixes added.
- if( isset($database) ) $table = ( $table[0] == '`' ? $table : "`{$table}`" );
-
+ if ( isset( $database ) ) {
+ $table = ( $table[0] == '`' ? $table : "`{$table}`" );
+ }
+
# Note that we use the long format because php will complain in in_array if
# the input is not an array, and will complain in is_array if it is not set.
- if( !isset( $database ) # Don't use shared database if pre selected.
+ if ( !isset( $database ) # Don't use shared database if pre selected.
&& isset( $wgSharedDB ) # We have a shared database
&& $table[0] != '`' # Paranoia check to prevent shared tables listing '`table`'
&& isset( $wgSharedTables )
@@ -1369,15 +1563,16 @@ abstract class DatabaseBase {
$database = $wgSharedDB;
$prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
}
-
+
# Quote the $database and $table and apply the prefix if not quoted.
- if( isset($database) ) $database = ( $database[0] == '`' ? $database : "`{$database}`" );
+ if ( isset( $database ) ) {
+ $database = ( $database[0] == '`' ? $database : "`{$database}`" );
+ }
$table = ( $table[0] == '`' ? $table : "`{$prefix}{$table}`" );
-
+
# Merge our database and table into our final table name.
- $tableName = ( isset($database) ? "{$database}.{$table}" : "{$table}" );
-
- # We're finished, return.
+ $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
+
return $tableName;
}
@@ -1393,12 +1588,14 @@ abstract class DatabaseBase {
public function tableNames() {
$inArray = func_get_args();
$retVal = array();
+
foreach ( $inArray as $name ) {
$retVal[$name] = $this->tableName( $name );
}
+
return $retVal;
}
-
+
/**
* Fetch a number of table names into an zero-indexed numerical array
* This is handy when you need to construct SQL for joins
@@ -1411,47 +1608,97 @@ abstract class DatabaseBase {
public function tableNamesN() {
$inArray = func_get_args();
$retVal = array();
+
foreach ( $inArray as $name ) {
$retVal[] = $this->tableName( $name );
}
+
return $retVal;
}
/**
+ * Get an aliased table name
+ * e.g. tableName AS newTableName
+ *
+ * @param $name string Table name, see tableName()
+ * @param $alias string Alias (optional)
+ * @return string SQL name for aliased table. Will not alias a table to its own name
+ */
+ public function tableNameWithAlias( $name, $alias = false ) {
+ if ( !$alias || $alias == $name ) {
+ return $this->tableName( $name );
+ } else {
+ return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
+ }
+ }
+
+ /**
+ * Gets an array of aliased table names
+ *
+ * @param $tables array( [alias] => table )
+ * @return array of strings, see tableNameWithAlias()
+ */
+ public function tableNamesWithAlias( $tables ) {
+ $retval = array();
+ foreach ( $tables as $alias => $table ) {
+ if ( is_numeric( $alias ) ) {
+ $alias = $table;
+ }
+ $retval[] = $this->tableNameWithAlias( $table, $alias );
+ }
+ return $retval;
+ }
+
+ /**
* @private
*/
function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
$ret = array();
$retJOIN = array();
- $use_index_safe = is_array($use_index) ? $use_index : array();
- $join_conds_safe = is_array($join_conds) ? $join_conds : array();
- foreach ( $tables as $table ) {
+ $use_index_safe = is_array( $use_index ) ? $use_index : array();
+ $join_conds_safe = is_array( $join_conds ) ? $join_conds : array();
+
+ foreach ( $tables as $alias => $table ) {
+ if ( !is_string( $alias ) ) {
+ // No alias? Set it equal to the table name
+ $alias = $table;
+ }
// Is there a JOIN and INDEX clause for this table?
- if ( isset($join_conds_safe[$table]) && isset($use_index_safe[$table]) ) {
- $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
- $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
- $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
+ if ( isset( $join_conds_safe[$alias] ) && isset( $use_index_safe[$alias] ) ) {
+ $tableClause = $join_conds_safe[$alias][0] . ' ' . $this->tableNameWithAlias( $table, $alias );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$alias] ) );
+ $on = $this->makeList( (array)$join_conds_safe[$alias][1], LIST_AND );
+ if ( $on != '' ) {
+ $tableClause .= ' ON (' . $on . ')';
+ }
+
$retJOIN[] = $tableClause;
// Is there an INDEX clause?
- } else if ( isset($use_index_safe[$table]) ) {
- $tableClause = $this->tableName( $table );
- $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+ } else if ( isset( $use_index_safe[$alias] ) ) {
+ $tableClause = $this->tableNameWithAlias( $table, $alias );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$alias] ) );
$ret[] = $tableClause;
// Is there a JOIN clause?
- } else if ( isset($join_conds_safe[$table]) ) {
- $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
- $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
+ } else if ( isset( $join_conds_safe[$alias] ) ) {
+ $tableClause = $join_conds_safe[$alias][0] . ' ' . $this->tableNameWithAlias( $table, $alias );
+ $on = $this->makeList( (array)$join_conds_safe[$alias][1], LIST_AND );
+ if ( $on != '' ) {
+ $tableClause .= ' ON (' . $on . ')';
+ }
+
$retJOIN[] = $tableClause;
} else {
- $tableClause = $this->tableName( $table );
+ $tableClause = $this->tableNameWithAlias( $table, $alias );
$ret[] = $tableClause;
}
}
+
// We can't separate explicit JOIN clauses with ',', use ' ' for those
- $straightJoins = !empty($ret) ? implode( ',', $ret ) : "";
- $otherJoins = !empty($retJOIN) ? implode( ' ', $retJOIN ) : "";
+ $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
+ $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
+
// Compile our final table clause
- return implode(' ',array($straightJoins,$otherJoins) );
+ return implode( ' ', array( $straightJoins, $otherJoins ) );
}
/**
@@ -1464,7 +1711,8 @@ abstract class DatabaseBase {
'un_user_id' => 'user_id',
'un_user_ip' => 'user_ip',
);
- if( isset( $renamed[$index] ) ) {
+
+ if ( isset( $renamed[$index] ) ) {
return $renamed[$index];
} else {
return $index;
@@ -1472,13 +1720,6 @@ abstract class DatabaseBase {
}
/**
- * Wrapper for addslashes()
- * @param $s String: to be slashed.
- * @return String: slashed string.
- */
- abstract function strencode( $s );
-
- /**
* If it's a string, adds quotes and backslashes
* Otherwise returns as-is
*/
@@ -1495,14 +1736,42 @@ abstract class DatabaseBase {
}
/**
+ * Quotes an identifier using `backticks` or "double quotes" depending on the database type.
+ * MySQL uses `backticks` while basically everything else uses double quotes.
+ * Since MySQL is the odd one out here the double quotes are our generic
+ * and we implement backticks in DatabaseMysql.
+ */
+ public function addIdentifierQuotes( $s ) {
+ return '"' . str_replace( '"', '""', $s ) . '"';
+ }
+
+ /**
+ * Backwards compatibility, identifier quoting originated in DatabasePostgres
+ * which used quote_ident which does not follow our naming conventions
+ * was renamed to addIdentifierQuotes.
+ * @deprecated use addIdentifierQuotes
+ */
+ function quote_ident( $s ) {
+ wfDeprecated( __METHOD__ );
+ return $this->addIdentifierQuotes( $s );
+ }
+
+ /**
* Escape string for safe LIKE usage.
* WARNING: you should almost never use this function directly,
* instead use buildLike() that escapes everything automatically
+ * Deprecated in 1.17, warnings in 1.17, removed in ???
*/
- function escapeLike( $s ) {
+ public function escapeLike( $s ) {
+ wfDeprecated( __METHOD__ );
+ return $this->escapeLikeInternal( $s );
+ }
+
+ protected function escapeLikeInternal( $s ) {
$s = str_replace( '\\', '\\\\', $s );
$s = $this->strencode( $s );
$s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
+
return $s;
}
@@ -1510,27 +1779,31 @@ abstract class DatabaseBase {
* LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match
* containing either string literals that will be escaped or tokens returned by anyChar() or anyString().
* Alternatively, the function could be provided with an array of aforementioned parameters.
- *
+ *
* Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns a LIKE clause that searches
* for subpages of 'My page title'.
* Alternatively: $pattern = array( 'My_page_title/', $dbr->anyString() ); $query .= $dbr->buildLike( $pattern );
*
- * @ return String: fully built LIKE statement
+ * @since 1.16
+ * @return String: fully built LIKE statement
*/
function buildLike() {
$params = func_get_args();
- if (count($params) > 0 && is_array($params[0])) {
+
+ if ( count( $params ) > 0 && is_array( $params[0] ) ) {
$params = $params[0];
}
$s = '';
- foreach( $params as $value) {
- if( $value instanceof LikeMatch ) {
+
+ foreach ( $params as $value ) {
+ if ( $value instanceof LikeMatch ) {
$s .= $value->toString();
} else {
- $s .= $this->escapeLike( $value );
+ $s .= $this->escapeLikeInternal( $value );
}
}
+
return " LIKE '" . $s . "' ";
}
@@ -1580,9 +1853,12 @@ abstract class DatabaseBase {
* However if you do this, you run the risk of encountering errors which wouldn't have
* occurred in MySQL
*
- * @todo migrate comment to phodocumentor format
+ * @param $table String: The table to replace the row(s) in.
+ * @param $uniqueIndexes Array: An associative array of indexes
+ * @param $rows Array: Array of rows to replace
+ * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
*/
- function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
+ function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
$table = $this->tableName( $table );
# Single row case
@@ -1590,16 +1866,19 @@ abstract class DatabaseBase {
$rows = array( $rows );
}
- $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) .') VALUES ';
+ $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
$first = true;
+
foreach ( $rows as $row ) {
if ( $first ) {
$first = false;
} else {
$sql .= ',';
}
+
$sql .= '(' . $this->makeList( $row ) . ')';
}
+
return $this->query( $sql, $fname );
}
@@ -1619,14 +1898,15 @@ abstract class DatabaseBase {
* @param $conds Array: Condition array of field names mapped to variables, ANDed together in the WHERE clause
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
*/
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) {
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
$joinTable = $this->tableName( $joinTable );
$sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
+
if ( $conds != '*' ) {
$sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
}
@@ -1640,16 +1920,17 @@ abstract class DatabaseBase {
function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
- $res = $this->query( $sql, 'Database::textFieldSize' );
+ $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
$row = $this->fetchObject( $res );
- $this->freeResult( $res );
$m = array();
+
if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
$size = $m[1];
} else {
$size = -1;
}
+
return $size;
}
@@ -1669,48 +1950,59 @@ abstract class DatabaseBase {
*
* Use $conds == "*" to delete all rows
*/
- function delete( $table, $conds, $fname = 'Database::delete' ) {
+ function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
+ throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
}
+
$table = $this->tableName( $table );
$sql = "DELETE FROM $table";
+
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
+
return $this->query( $sql, $fname );
}
/**
* INSERT SELECT wrapper
* $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
- * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
+ * Source items may be literals rather than field names, but strings should be quoted with DatabaseBase::addQuotes()
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect',
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseBase::insertSelect',
$insertOptions = array(), $selectOptions = array() )
{
$destTable = $this->tableName( $destTable );
+
if ( is_array( $insertOptions ) ) {
$insertOptions = implode( ' ', $insertOptions );
}
- if( !is_array( $selectOptions ) ) {
+
+ if ( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
}
+
list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
- if( is_array( $srcTable ) ) {
+
+ if ( is_array( $srcTable ) ) {
$srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
} else {
$srcTable = $this->tableName( $srcTable );
}
+
$sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
" SELECT $startOpts " . implode( ',', $varMap ) .
" FROM $srcTable $useIndex ";
+
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
+
$sql .= " $tailOpts";
+
return $this->query( $sql, $fname );
}
@@ -1732,20 +2024,22 @@ abstract class DatabaseBase {
* @param $limit Integer: the SQL limit
* @param $offset Integer the SQL offset (default false)
*/
- function limitResult( $sql, $limit, $offset=false ) {
- if( !is_numeric( $limit ) ) {
+ function limitResult( $sql, $limit, $offset = false ) {
+ if ( !is_numeric( $limit ) ) {
throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
}
+
return "$sql LIMIT "
- . ( (is_numeric($offset) && $offset != 0) ? "{$offset}," : "" )
+ . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
. "{$limit} ";
}
+
function limitResultForUpdate( $sql, $num ) {
return $this->limitResult( $sql, $num, 0 );
}
/**
- * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
+ * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
* within the UNION construct.
* @return Boolean
*/
@@ -1761,9 +2055,9 @@ abstract class DatabaseBase {
* @param $all Boolean: use UNION ALL
* @return String: SQL fragment
*/
- function unionQueries($sqls, $all) {
+ function unionQueries( $sqls, $all ) {
$glue = $all ? ') UNION ALL (' : ') UNION (';
- return '('.implode( $glue, $sqls ) . ')';
+ return '(' . implode( $glue, $sqls ) . ')';
}
/**
@@ -1792,6 +2086,16 @@ abstract class DatabaseBase {
}
/**
+ * Convert a field to an unix timestamp
+ *
+ * @param $field String: field name
+ * @return String: SQL statement
+ */
+ public function unixTimestamp( $field ) {
+ return "EXTRACT(epoch FROM $field)";
+ }
+
+ /**
* Determines if the last failure was due to a deadlock
* STUB
*/
@@ -1800,7 +2104,7 @@ abstract class DatabaseBase {
}
/**
- * Determines if the last query error was something that should be dealt
+ * Determines if the last query error was something that should be dealt
* with by pinging the connection and reissuing the query.
* STUB
*/
@@ -1833,18 +2137,20 @@ abstract class DatabaseBase {
* reached.
*/
function deadlockLoop() {
- $myFname = 'Database::deadlockLoop';
+ $myFname = 'DatabaseBase::deadlockLoop';
$this->begin();
$args = func_get_args();
$function = array_shift( $args );
$oldIgnore = $this->ignoreErrors( true );
$tries = DEADLOCK_TRIES;
+
if ( is_array( $function ) ) {
$fname = $function[0];
} else {
$fname = $function;
}
+
do {
$retVal = call_user_func_array( $function, $args );
$error = $this->lastError();
@@ -1859,14 +2165,16 @@ abstract class DatabaseBase {
$this->reportQueryError( $error, $errno, $sql, $fname );
}
}
- } while( $this->wasDeadlock() && --$tries > 0 );
+ } while ( $this->wasDeadlock() && --$tries > 0 );
+
$this->ignoreErrors( $oldIgnore );
+
if ( $tries <= 0 ) {
- $this->query( 'ROLLBACK', $myFname );
+ $this->rollback( $myFname );
$this->reportQueryError( $error, $errno, $sql, $fname );
return false;
} else {
- $this->query( 'COMMIT', $myFname );
+ $this->commit( $myFname );
return $retVal;
}
}
@@ -1878,7 +2186,7 @@ abstract class DatabaseBase {
* @param $timeout Integer: the maximum number of seconds to wait for synchronisation
*/
function masterPosWait( MySQLMasterPos $pos, $timeout ) {
- $fname = 'Database::masterPosWait';
+ $fname = 'DatabaseBase::masterPosWait';
wfProfileIn( $fname );
# Commit any open transactions
@@ -1887,7 +2195,8 @@ abstract class DatabaseBase {
}
if ( !is_null( $this->mFakeSlaveLag ) ) {
- $wait = intval( ( $pos->pos - microtime(true) + $this->mFakeSlaveLag ) * 1e6 );
+ $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
+
if ( $wait > $timeout * 1e6 ) {
wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
wfProfileOut( $fname );
@@ -1909,8 +2218,8 @@ abstract class DatabaseBase {
$encPos = intval( $pos->pos );
$sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
$res = $this->doQuery( $sql );
+
if ( $res && $row = $this->fetchRow( $res ) ) {
- $this->freeResult( $res );
wfProfileOut( $fname );
return $row[0];
} else {
@@ -1924,14 +2233,16 @@ abstract class DatabaseBase {
*/
function getSlavePos() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
- $pos = new MySQLMasterPos( 'fake', microtime(true) - $this->mFakeSlaveLag );
- wfDebug( __METHOD__.": fake slave pos = $pos\n" );
+ $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
+ wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
return $pos;
}
- $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' );
+
+ $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
$row = $this->fetchObject( $res );
+
if ( $row ) {
- $pos = isset($row->Exec_master_log_pos) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
+ $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
} else {
return false;
@@ -1945,8 +2256,10 @@ abstract class DatabaseBase {
if ( $this->mFakeMaster ) {
return new MySQLMasterPos( 'fake', microtime( true ) );
}
- $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' );
+
+ $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
$row = $this->fetchObject( $res );
+
if ( $row ) {
return new MySQLMasterPos( $row->File, $row->Position );
} else {
@@ -1957,7 +2270,7 @@ abstract class DatabaseBase {
/**
* Begin a transaction, committing any previously open transaction
*/
- function begin( $fname = 'Database::begin' ) {
+ function begin( $fname = 'DatabaseBase::begin' ) {
$this->query( 'BEGIN', $fname );
$this->mTrxLevel = 1;
}
@@ -1965,25 +2278,30 @@ abstract class DatabaseBase {
/**
* End a transaction
*/
- function commit( $fname = 'Database::commit' ) {
- $this->query( 'COMMIT', $fname );
- $this->mTrxLevel = 0;
+ function commit( $fname = 'DatabaseBase::commit' ) {
+ if ( $this->mTrxLevel ) {
+ $this->query( 'COMMIT', $fname );
+ $this->mTrxLevel = 0;
+ }
}
/**
* Rollback a transaction.
* No-op on non-transactional databases.
*/
- function rollback( $fname = 'Database::rollback' ) {
- $this->query( 'ROLLBACK', $fname, true );
- $this->mTrxLevel = 0;
+ function rollback( $fname = 'DatabaseBase::rollback' ) {
+ if ( $this->mTrxLevel ) {
+ $this->query( 'ROLLBACK', $fname, true );
+ $this->mTrxLevel = 0;
+ }
}
/**
* Begin a transaction, committing any previously open transaction
* @deprecated use begin()
*/
- function immediateBegin( $fname = 'Database::immediateBegin' ) {
+ function immediateBegin( $fname = 'DatabaseBase::immediateBegin' ) {
+ wfDeprecated( __METHOD__ );
$this->begin();
}
@@ -1991,7 +2309,8 @@ abstract class DatabaseBase {
* Commit transaction, if one is open
* @deprecated use commit()
*/
- function immediateCommit( $fname = 'Database::immediateCommit' ) {
+ function immediateCommit( $fname = 'DatabaseBase::immediateCommit' ) {
+ wfDeprecated( __METHOD__ );
$this->commit();
}
@@ -2004,24 +2323,25 @@ abstract class DatabaseBase {
* @param $oldName String: name of table whose structure should be copied
* @param $newName String: name of table to be created
* @param $temporary Boolean: whether the new table should be temporary
+ * @param $fname String: calling function name
* @return Boolean: true if operation was successful
*/
- function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'Database::duplicateTableStructure' ) {
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseBase::duplicateTableStructure' ) {
throw new MWException( 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
}
/**
* Return MW-style timestamp used for MySQL schema
*/
- function timestamp( $ts=0 ) {
- return wfTimestamp(TS_MW,$ts);
+ function timestamp( $ts = 0 ) {
+ return wfTimestamp( TS_MW, $ts );
}
/**
* Local database timestamp format or null
*/
function timestampOrNull( $ts = null ) {
- if( is_null( $ts ) ) {
+ if ( is_null( $ts ) ) {
return null;
} else {
return $this->timestamp( $ts );
@@ -2032,7 +2352,7 @@ abstract class DatabaseBase {
* @todo document
*/
function resultObject( $result ) {
- if( empty( $result ) ) {
+ if ( empty( $result ) ) {
return false;
} elseif ( $result instanceof ResultWrapper ) {
return $result;
@@ -2047,29 +2367,11 @@ abstract class DatabaseBase {
/**
* Return aggregated value alias
*/
- function aggregateValue ($valuedata,$valuename='value') {
+ function aggregateValue ( $valuedata, $valuename = 'value' ) {
return $valuename;
}
/**
- * 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(). Will be listed on Special:Version, etc.
- *
- * @return String: Version information from the database
- */
- abstract function getServerVersion();
-
- /**
* Ping the server and try to reconnect if it there is no connection
*
* @return bool Success or failure
@@ -2081,52 +2383,24 @@ abstract class DatabaseBase {
/**
* Get slave lag.
- * At the moment, this will only work if the DB user has the PROCESS privilege
+ * Currently supported only by MySQL
+ * @return Database replication lag in seconds
*/
function getLag() {
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
- return $this->mFakeSlaveLag;
- }
- $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
- # Find slave SQL thread
- while ( $row = $this->fetchObject( $res ) ) {
- /* This should work for most situations - when default db
- * for thread is not specified, it had no events executed,
- * and therefore it doesn't know yet how lagged it is.
- *
- * Relay log I/O thread does not select databases.
- */
- if ( $row->User == 'system user' &&
- $row->State != 'Waiting for master to send event' &&
- $row->State != 'Connecting to master' &&
- $row->State != 'Queueing master event to the relay log' &&
- $row->State != 'Waiting for master update' &&
- $row->State != 'Requesting binlog dump' &&
- $row->State != 'Waiting to reconnect after a failed master event read' &&
- $row->State != 'Reconnecting after a failed master event read' &&
- $row->State != 'Registering slave on master'
- ) {
- # This is it, return the time (except -ve)
- if ( $row->Time > 0x7fffffff ) {
- return false;
- } else {
- return $row->Time;
- }
- }
- }
- return false;
+ return intval( $this->mFakeSlaveLag );
}
/**
* Get status information from SHOW STATUS in an associative array
*/
- function getStatus($which="%") {
+ function getStatus( $which = "%" ) {
$res = $this->query( "SHOW STATUS LIKE '{$which}'" );
$status = array();
- while ( $row = $this->fetchObject( $res ) ) {
+
+ foreach ( $res as $row ) {
$status[$row->Variable_name] = $row->Value;
}
+
return $status;
}
@@ -2137,11 +2411,11 @@ abstract class DatabaseBase {
return 0;
}
- function encodeBlob($b) {
+ function encodeBlob( $b ) {
return $b;
}
- function decodeBlob($b) {
+ function decodeBlob( $b ) {
return $b;
}
@@ -2161,56 +2435,79 @@ abstract class DatabaseBase {
* @param $filename String: File name to open
* @param $lineCallback Callback: Optional function called before reading each line
* @param $resultCallback Callback: Optional function called for each MySQL result
+ * @param $fname String: Calling function name or false if name should be generated dynamically
+ * using $filename
*/
- function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) {
+ function sourceFile( $filename, $lineCallback = false, $resultCallback = false, $fname = false ) {
$fp = fopen( $filename, 'r' );
+
if ( false === $fp ) {
- if (!defined("MEDIAWIKI_INSTALL"))
+ if ( !defined( "MEDIAWIKI_INSTALL" ) )
throw new MWException( "Could not open \"{$filename}\".\n" );
else
return "Could not open \"{$filename}\".\n";
}
+
+ if ( !$fname ) {
+ $fname = __METHOD__ . "( $filename )";
+ }
+
try {
- $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
+ $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname );
}
- catch( MWException $e ) {
- if ( defined("MEDIAWIKI_INSTALL") ) {
+ catch ( MWException $e ) {
+ if ( defined( "MEDIAWIKI_INSTALL" ) ) {
$error = $e->getMessage();
} else {
fclose( $fp );
throw $e;
}
}
-
+
fclose( $fp );
+
return $error;
}
/**
* Get the full path of a patch file. Originally based on archive()
- * from updaters.inc. Keep in mind this always returns a patch, as
+ * from updaters.inc. Keep in mind this always returns a patch, as
* it fails back to MySQL if no DB-specific patch can be found
*
* @param $patch String The name of the patch, like patch-something.sql
* @return String Full path to patch file
*/
- public static function patchPath( $patch ) {
- global $wgDBtype, $IP;
- if ( file_exists( "$IP/maintenance/$wgDBtype/archives/$patch" ) ) {
- return "$IP/maintenance/$wgDBtype/archives/$patch";
+ public function patchPath( $patch ) {
+ global $IP;
+
+ $dbType = $this->getType();
+ if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
+ return "$IP/maintenance/$dbType/archives/$patch";
} else {
return "$IP/maintenance/archives/$patch";
}
}
/**
+ * Set variables to be used in sourceFile/sourceStream, in preference to the
+ * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
+ * all. If it's set to false, $GLOBALS will be used.
+ *
+ * @param $vars False, or array mapping variable name to value.
+ */
+ function setSchemaVars( $vars ) {
+ $this->mSchemaVars = $vars;
+ }
+
+ /**
* Read and execute commands from an open file handle
* Returns true on success, error string or exception on failure (depending on object's error ignore settings)
* @param $fp String: File handle
* @param $lineCallback Callback: Optional function called before reading each line
* @param $resultCallback Callback: Optional function called for each MySQL result
+ * @param $fname String: Calling function name
*/
- function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
+ function sourceStream( $fp, $lineCallback = false, $resultCallback = false, $fname = 'DatabaseBase::sourceStream' ) {
$cmd = "";
$done = false;
$dollarquote = false;
@@ -2219,15 +2516,21 @@ abstract class DatabaseBase {
if ( $lineCallback ) {
call_user_func( $lineCallback );
}
+
$line = trim( fgets( $fp, 1024 ) );
$sl = strlen( $line ) - 1;
- if ( $sl < 0 ) { continue; }
- if ( '-' == $line{0} && '-' == $line{1} ) { continue; }
+ if ( $sl < 0 ) {
+ continue;
+ }
+
+ if ( '-' == $line { 0 } && '-' == $line { 1 } ) {
+ continue;
+ }
- ## Allow dollar quoting for function declarations
- if (substr($line,0,4) == '$mw$') {
- if ($dollarquote) {
+ # # Allow dollar quoting for function declarations
+ if ( substr( $line, 0, 4 ) == '$mw$' ) {
+ if ( $dollarquote ) {
$dollarquote = false;
$done = true;
}
@@ -2235,20 +2538,24 @@ abstract class DatabaseBase {
$dollarquote = true;
}
}
- else if (!$dollarquote) {
- if ( ';' == $line{$sl} && ($sl < 2 || ';' != $line{$sl - 1})) {
+ else if ( !$dollarquote ) {
+ if ( ';' == $line { $sl } && ( $sl < 2 || ';' != $line { $sl - 1 } ) ) {
$done = true;
$line = substr( $line, 0, $sl );
}
}
- if ( $cmd != '' ) { $cmd .= ' '; }
+ if ( $cmd != '' ) {
+ $cmd .= ' ';
+ }
+
$cmd .= "$line\n";
if ( $done ) {
- $cmd = str_replace(';;', ";", $cmd);
+ $cmd = str_replace( ';;', ";", $cmd );
$cmd = $this->replaceVars( $cmd );
- $res = $this->query( $cmd, __METHOD__ );
+ $res = $this->query( $cmd, $fname );
+
if ( $resultCallback ) {
call_user_func( $resultCallback, $res, $this );
}
@@ -2262,41 +2569,76 @@ abstract class DatabaseBase {
$done = false;
}
}
+
return true;
}
+ /**
+ * Database independent variable replacement, replaces a set of variables
+ * in a sql statement with their contents as given by $this->getSchemaVars().
+ * Supports '{$var}' `{$var}` and / *$var* / (without the spaces) style variables
+ *
+ * '{$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 can be overridden if the database
+ * uses something other than backticks.
+ * / *$var* / is just encoded, besides traditional dbprefix and tableoptions it's use should be avoided
+ *
+ * @param $ins String: 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
*/
protected function replaceVars( $ins ) {
- $varnames = array(
- 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser',
- 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword',
- 'wgDBadminuser', 'wgDBadminpassword', 'wgDBTableOptions',
- );
-
- // Ordinary variables
- foreach ( $varnames as $var ) {
- if( isset( $GLOBALS[$var] ) ) {
- $val = addslashes( $GLOBALS[$var] ); // FIXME: safety check?
- $ins = str_replace( '{$' . $var . '}', $val, $ins );
- $ins = str_replace( '/*$' . $var . '*/`', '`' . $val, $ins );
- $ins = str_replace( '/*$' . $var . '*/', $val, $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]*)!',
+ $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
array( $this, 'indexNameCallback' ), $ins );
+
return $ins;
}
/**
+ * Get schema variables. If none have been set via setSchemaVars(), then
+ * use some defaults from the current object.
+ */
+ protected function getSchemaVars() {
+ if ( $this->mSchemaVars ) {
+ return $this->mSchemaVars;
+ } else {
+ return $this->getDefaultSchemaVars();
+ }
+ }
+
+ /**
+ * Get schema variables to use if none have been set via setSchemaVars().
+ * Override this in derived classes to provide variables for tables.sql
+ * and SQL patch files.
+ */
+ protected function getDefaultSchemaVars() {
+ return array();
+ }
+
+ /**
* Table name callback
* @private
*/
@@ -2319,16 +2661,17 @@ abstract class DatabaseBase {
function buildConcat( $stringList ) {
return 'CONCAT(' . implode( ',', $stringList ) . ')';
}
-
+
/**
* Acquire a named lock
- *
+ *
* Abstracted from Filestore::lock() so child classes can implement for
* their own needs.
- *
- * @param $lockName String: Name of lock to aquire
- * @param $method String: Name of method calling us
- * @return bool
+ *
+ * @param $lockName String: name of lock to aquire
+ * @param $method String: name of method calling us
+ * @param $timeout Integer: timeout
+ * @return Boolean
*/
public function lock( $lockName, $method, $timeout = 5 ) {
return true;
@@ -2336,13 +2679,12 @@ abstract class DatabaseBase {
/**
* Release a lock.
- *
+ *
* @param $lockName String: Name of lock to release
* @param $method String: Name of method calling us
*
- * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
* @return Returns 1 if the lock was released, 0 if the lock was not established
- * by this thread (in which case the lock is not released), and NULL if the named
+ * by this thread (in which case the lock is not released), and NULL if the named
* lock did not exist
*/
public function unlock( $lockName, $method ) {
@@ -2360,7 +2702,7 @@ abstract class DatabaseBase {
public function lockTables( $read, $write, $method, $lowPriority = true ) {
return true;
}
-
+
/**
* Unlock specific tables
*
@@ -2369,31 +2711,30 @@ abstract class DatabaseBase {
public function unlockTables( $method ) {
return true;
}
-
+
/**
- * Get search engine class. All subclasses of this
- * need to implement this if they wish to use searching.
- *
+ * Get search engine class. All subclasses of this need to implement this
+ * if they wish to use searching.
+ *
* @return String
*/
public function getSearchEngine() {
- return "SearchMySQL";
+ return 'SearchEngineDummy';
}
/**
- * Allow or deny "big selects" for this session only. This is done by setting
+ * Allow or deny "big selects" for this session only. This is done by setting
* the sql_big_selects session variable.
*
- * This is a MySQL-specific feature.
+ * This is a MySQL-specific feature.
*
- * @param mixed $value true for allow, false for deny, or "default" to restore the initial value
+ * @param $value Mixed: true for allow, false for deny, or "default" to restore the initial value
*/
public function setBigSelects( $value = true ) {
// no-op
}
}
-
/******************************************************************************
* Utility classes
*****************************************************************************/
@@ -2405,7 +2746,7 @@ abstract class DatabaseBase {
class DBObject {
public $mData;
- function DBObject($data) {
+ function __construct( $data ) {
$this->mData = $data;
}
@@ -2426,65 +2767,44 @@ class DBObject {
*/
class Blob {
private $mData;
- function __construct($data) {
+
+ function __construct( $data ) {
$this->mData = $data;
}
+
function fetch() {
return $this->mData;
}
}
/**
- * Utility class.
+ * Base for all database-specific classes representing information about database fields
* @ingroup Database
*/
-class MySQLField {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type;
- function __construct ($info) {
- $this->name = $info->name;
- $this->tablename = $info->table;
- $this->default = $info->def;
- $this->max_length = $info->max_length;
- $this->nullable = !$info->not_null;
- $this->is_pk = $info->primary_key;
- $this->is_unique = $info->unique_key;
- $this->is_multiple = $info->multiple_key;
- $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple);
- $this->type = $info->type;
- }
-
- function name() {
- return $this->name;
- }
-
- function tableName() {
- return $this->tableName;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- function maxLength() {
- return $this->max_length;
- }
-
- function nullable() {
- return $this->nullable;
- }
+interface Field {
+ /**
+ * Field name
+ * @return string
+ */
+ function name();
- function isKey() {
- return $this->is_key;
- }
+ /**
+ * Name of table this field belongs to
+ * @return string
+ */
+ function tableName();
- function isMultipleKey() {
- return $this->is_multiple;
- }
+ /**
+ * Database type
+ * @return string
+ */
+ function type();
- function type() {
- return $this->type;
- }
+ /**
+ * Whether this field can store NULL values
+ * @return bool
+ */
+ function isNullable();
}
/******************************************************************************
@@ -2510,10 +2830,13 @@ class DBError extends MWException {
function getText() {
global $wgShowDBErrorBacktrace;
+
$s = $this->getMessage() . "\n";
+
if ( $wgShowDBErrorBacktrace ) {
$s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
}
+
return $s;
}
}
@@ -2523,13 +2846,16 @@ class DBError extends MWException {
*/
class DBConnectionError extends DBError {
public $error;
-
+
function __construct( DatabaseBase &$db, $error = 'unknown error' ) {
$msg = 'DB connection error';
+
if ( trim( $error ) != '' ) {
$msg .= ": $error";
}
+
$this->error = $error;
+
parent::__construct( $db, $msg );
}
@@ -2542,7 +2868,7 @@ class DBConnectionError extends DBError {
// Not likely to work
return false;
}
-
+
function getLogMessage() {
# Don't send to the exception log
return false;
@@ -2550,11 +2876,13 @@ class DBConnectionError extends DBError {
function getPageTitle() {
global $wgSitename, $wgLang;
+
$header = "$wgSitename has a problem";
+
if ( $wgLang instanceof Language ) {
$header = htmlspecialchars( $wgLang->getMessage( 'dberr-header' ) );
}
-
+
return $header;
}
@@ -2577,7 +2905,7 @@ class DBConnectionError extends DBError {
}
if ( trim( $this->error ) == '' ) {
- $this->error = $this->db->getProperty('mServer');
+ $this->error = $this->db->getProperty( 'mServer' );
}
$noconnect = "<p><strong>$sorry</strong><br />$again</p><p><small>$info</small></p>";
@@ -2589,33 +2917,38 @@ class DBConnectionError extends DBError {
$extra = $this->searchForm();
- if( $wgUseFileCache ) {
+ if ( $wgUseFileCache ) {
try {
$cache = $this->fileCachedPage();
# Cached version on file system?
- if( $cache !== null ) {
+ if ( $cache !== null ) {
# Hack: extend the body for error messages
- $cache = str_replace( array('</html>','</body>'), '', $cache );
+ $cache = str_replace( array( '</html>', '</body>' ), '', $cache );
# Add cache notice...
$cachederror = "This is a cached copy of the requested page, and may not be up to date. ";
+
# Localize it if possible...
- if( $wgLang instanceof Language ) {
+ if ( $wgLang instanceof Language ) {
$cachederror = htmlspecialchars( $wgLang->getMessage( 'dberr-cachederror' ) );
}
+
$warning = "<div style='color:red;font-size:150%;font-weight:bold;'>$cachederror</div>";
+
# Output cached page with notices on bottom and re-close body
return "{$cache}{$warning}<hr />$text<hr />$extra</body></html>";
}
- } catch( MWException $e ) {
+ } catch ( MWException $e ) {
// Do nothing, just use the default page
}
}
+
# Headers needed here - output is just the error message
- return $this->htmlHeader()."$text<hr />$extra".$this->htmlFooter();
+ return $this->htmlHeader() . "$text<hr />$extra" . $this->htmlFooter();
}
function searchForm() {
- global $wgSitename, $wgServer, $wgLang, $wgInputEncoding;
+ global $wgSitename, $wgServer, $wgLang;
+
$usegoogle = "You can try searching via Google in the meantime.";
$outofdate = "Note that their indexes of our content may be out of date.";
$googlesearch = "Search";
@@ -2626,23 +2959,26 @@ class DBConnectionError extends DBError {
$googlesearch = htmlspecialchars( $wgLang->getMessage( 'searchbutton' ) );
}
- $search = htmlspecialchars(@$_REQUEST['search']);
+ $search = htmlspecialchars( @$_REQUEST['search'] );
+
+ $server = htmlspecialchars( $wgServer );
+ $sitename = htmlspecialchars( $wgSitename );
$trygoogle = <<<EOT
<div style="margin: 1.5em">$usegoogle<br />
<small>$outofdate</small></div>
<!-- SiteSearch Google -->
<form method="get" action="http://www.google.com/search" id="googlesearch">
- <input type="hidden" name="domains" value="$wgServer" />
- <input type="hidden" name="num" value="50" />
- <input type="hidden" name="ie" value="$wgInputEncoding" />
- <input type="hidden" name="oe" value="$wgInputEncoding" />
+ <input type="hidden" name="domains" value="$server" />
+ <input type="hidden" name="num" value="50" />
+ <input type="hidden" name="ie" value="UTF-8" />
+ <input type="hidden" name="oe" value="UTF-8" />
- <input type="text" name="q" size="31" maxlength="255" value="$search" />
- <input type="submit" name="btnG" value="$googlesearch" />
+ <input type="text" name="q" size="31" maxlength="255" value="$search" />
+ <input type="submit" name="btnG" value="$googlesearch" />
<div>
- <input type="radio" name="sitesearch" id="gwiki" value="$wgServer" checked="checked" /><label for="gwiki">$wgSitename</label>
- <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
+ <input type="radio" name="sitesearch" id="gwiki" value="$server" checked="checked" /><label for="gwiki">$sitename</label>
+ <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
</div>
</form>
<!-- SiteSearch Google -->
@@ -2651,33 +2987,35 @@ EOT;
}
function fileCachedPage() {
- global $wgTitle, $title, $wgLang, $wgOut;
- if( $wgOut->isDisabled() ) return; // Done already?
+ global $wgTitle, $wgLang, $wgOut;
+
+ if ( $wgOut->isDisabled() ) {
+ return; // Done already?
+ }
+
$mainpage = 'Main Page';
+
if ( $wgLang instanceof Language ) {
$mainpage = htmlspecialchars( $wgLang->getMessage( 'mainpage' ) );
}
- if( $wgTitle ) {
+ if ( $wgTitle ) {
$t =& $wgTitle;
- } elseif( $title ) {
- $t = Title::newFromURL( $title );
} else {
$t = Title::newFromText( $mainpage );
}
$cache = new HTMLFileCache( $t );
- if( $cache->isFileCached() ) {
+ if ( $cache->isFileCached() ) {
return $cache->fetchPageText();
} else {
return '';
}
}
-
+
function htmlBodyOnly() {
return true;
}
-
}
/**
@@ -2685,14 +3023,15 @@ EOT;
*/
class DBQueryError extends DBError {
public $error, $errno, $sql, $fname;
-
+
function __construct( DatabaseBase &$db, $error, $errno, $sql, $fname ) {
- $message = "A database error has occurred\n" .
+ $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
"Query: $sql\n" .
"Function: $fname\n" .
"Error: $errno $error\n";
parent::__construct( $db, $message );
+
$this->error = $error;
$this->errno = $errno;
$this->sql = $sql;
@@ -2701,27 +3040,31 @@ class DBQueryError extends DBError {
function getText() {
global $wgShowDBErrorBacktrace;
+
if ( $this->useMessageCache() ) {
$s = wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
+
if ( $wgShowDBErrorBacktrace ) {
$s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
}
+
return $s;
} else {
return parent::getText();
}
}
-
+
function getSQL() {
global $wgShowSQLErrors;
- if( !$wgShowSQLErrors ) {
+
+ if ( !$wgShowSQLErrors ) {
return $this->msg( 'sqlhidden', 'SQL hidden' );
} else {
return $this->sql;
}
}
-
+
function getLogMessage() {
# Don't send to the exception log
return false;
@@ -2733,15 +3076,18 @@ class DBQueryError extends DBError {
function getHTML() {
global $wgShowDBErrorBacktrace;
+
if ( $this->useMessageCache() ) {
$s = wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) );
} else {
$s = nl2br( htmlspecialchars( $this->getMessage() ) );
}
+
if ( $wgShowDBErrorBacktrace ) {
$s .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
}
+
return $s;
}
}
@@ -2762,8 +3108,9 @@ class ResultWrapper implements Iterator {
/**
* Create a new result object from a result resource and a Database object
*/
- function ResultWrapper( $database, $result ) {
+ function __construct( $database, $result ) {
$this->db = $database;
+
if ( $result instanceof ResultWrapper ) {
$this->result = $result->result;
} else {
@@ -2783,7 +3130,6 @@ class ResultWrapper implements Iterator {
* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
*
- * @param $res SQL result object as returned from Database::query(), etc.
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
@@ -2795,7 +3141,6 @@ class ResultWrapper implements Iterator {
* Fetch the next row from the given result object, in associative array
* form. Fields are retrieved with $row['fieldname'].
*
- * @param $res SQL result object as returned from Database::query(), etc.
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
@@ -2827,8 +3172,8 @@ class ResultWrapper implements Iterator {
*/
function rewind() {
- if ($this->numRows()) {
- $this->db->dataSeek($this, 0);
+ if ( $this->numRows() ) {
+ $this->db->dataSeek( $this, 0 );
}
$this->pos = 0;
$this->currentRow = null;
@@ -2857,8 +3202,49 @@ class ResultWrapper implements Iterator {
}
/**
+ * Overloads the relevant methods of the real ResultsWrapper so it
+ * doesn't go anywhere near an actual database.
+ */
+class FakeResultWrapper extends ResultWrapper {
+ var $result = array();
+ var $db = null; // And it's going to stay that way :D
+ var $pos = 0;
+ var $currentRow = null;
+
+ function __construct( $array ) {
+ $this->result = $array;
+ }
+
+ function numRows() {
+ return count( $this->result );
+ }
+
+ function fetchRow() {
+ $this->currentRow = $this->result[$this->pos++];
+ return $this->currentRow;
+ }
+
+ function seek( $row ) {
+ $this->pos = $row;
+ }
+
+ function free() {}
+
+ // Callers want to be able to access fields with $this->fieldName
+ function fetchObject() {
+ $this->currentRow = $this->result[$this->pos++];
+ return (object)$this->currentRow;
+ }
+
+ function rewind() {
+ $this->pos = 0;
+ $this->currentRow = null;
+ }
+}
+
+/**
* Used by DatabaseBase::buildLike() to represent characters that have special meaning in SQL LIKE clauses
- * and thus need no escaping. Don't instantiate it manually, use Database::anyChar() and anyString() instead.
+ * and thus need no escaping. Don't instantiate it manually, use DatabaseBase::anyChar() and anyString() instead.
*/
class LikeMatch {
private $str;
diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php
index 9b62af82..becca11e 100644
--- a/includes/db/DatabaseIbm_db2.php
+++ b/includes/db/DatabaseIbm_db2.php
@@ -1,10 +1,11 @@
<?php
/**
- * This script is the IBM DB2 database abstraction layer
+ * This is the IBM DB2 database abstraction layer.
+ * See maintenance/ibm_db2/README for development notes
+ * and other specific information
*
- * See maintenance/ibm_db2/README for development notes and other specific information
- * @ingroup Database
* @file
+ * @ingroup Database
* @author leo.petr+mediawiki@gmail.com
*/
@@ -12,7 +13,7 @@
* This represents a column in a DB2 database
* @ingroup Database
*/
-class IBM_DB2Field {
+class IBM_DB2Field implements Field {
private $name = '';
private $tablename = '';
private $type = '';
@@ -20,32 +21,36 @@ class IBM_DB2Field {
private $max_length = 0;
/**
- * Builder method for the class
+ * Builder method for the class
* @param $db DatabaseIbm_db2: Database interface
* @param $table String: table name
* @param $field String: column name
* @return IBM_DB2Field
*/
- static function fromText($db, $table, $field) {
+ static function fromText( $db, $table, $field ) {
global $wgDBmwschema;
$q = <<<SQL
SELECT
-lcase(coltype) AS typname,
+lcase( coltype ) AS typname,
nulls AS attnotnull, length AS attlen
FROM sysibm.syscolumns
WHERE tbcreator=%s AND tbname=%s AND name=%s;
SQL;
- $res = $db->query(sprintf($q,
- $db->addQuotes($wgDBmwschema),
- $db->addQuotes($table),
- $db->addQuotes($field)));
- $row = $db->fetchObject($res);
- if (!$row)
+ $res = $db->query(
+ sprintf( $q,
+ $db->addQuotes( $wgDBmwschema ),
+ $db->addQuotes( $table ),
+ $db->addQuotes( $field )
+ )
+ );
+ $row = $db->fetchObject( $res );
+ if ( !$row ) {
return null;
+ }
$n = new IBM_DB2Field;
$n->type = $row->typname;
- $n->nullable = ($row->attnotnull == 'N');
+ $n->nullable = ( $row->attnotnull == 'N' );
$n->name = $field;
$n->tablename = $table;
$n->max_length = $row->attlen;
@@ -70,7 +75,7 @@ SQL;
* Can column be null?
* @return bool true or false
*/
- function nullable() { return $this->nullable; }
+ function isNullable() { return $this->nullable; }
/**
* How much can you fit in the column per row?
* @return int length
@@ -85,18 +90,17 @@ SQL;
class IBM_DB2Blob {
private $mData;
- public function __construct($data) {
+ public function __construct( $data ) {
$this->mData = $data;
}
public function getData() {
return $this->mData;
}
-
- public function __toString()
- {
- return $this->mData;
- }
+
+ public function __toString() {
+ return $this->mData;
+ }
}
/**
@@ -112,7 +116,6 @@ class DatabaseIbm_db2 extends DatabaseBase {
protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
protected $mOut, $mOpened = false;
- protected $mFailFunction;
protected $mTablePrefix;
protected $mFlags;
protected $mTrxLevel = 0;
@@ -121,207 +124,50 @@ class DatabaseIbm_db2 extends DatabaseBase {
protected $mFakeSlaveLag = null, $mFakeMaster = false;
*
*/
-
- /// Server port for uncataloged connections
+
+ /** Database server port */
protected $mPort = null;
- /// Whether connection is cataloged
- protected $mCataloged = null;
- /// Schema for tables, stored procedures, triggers
+ /** Schema for tables, stored procedures, triggers */
protected $mSchema = null;
- /// Whether the schema has been applied in this session
+ /** Whether the schema has been applied in this session */
protected $mSchemaSet = false;
- /// Result of last query
+ /** Result of last query */
protected $mLastResult = null;
- /// Number of rows affected by last INSERT/UPDATE/DELETE
+ /** Number of rows affected by last INSERT/UPDATE/DELETE */
protected $mAffectedRows = null;
- /// Number of rows returned by last SELECT
+ /** Number of rows returned by last SELECT */
protected $mNumRows = null;
-
- /// Connection config options - see constructor
+
+ /** Connection config options - see constructor */
public $mConnOptions = array();
- /// Statement config options -- see constructor
+ /** Statement config options -- see constructor */
public $mStmtOptions = array();
-
-
- const CATALOGED = "cataloged";
- const UNCATALOGED = "uncataloged";
- const USE_GLOBAL = "get from global";
-
+
+ /** Default schema */
+ const USE_GLOBAL = 'mediawiki';
+
+ /** Option that applies to nothing */
const NONE_OPTION = 0x00;
+ /** Option that applies to connection objects */
const CONN_OPTION = 0x01;
+ /** Option that applies to statement objects */
const STMT_OPTION = 0x02;
-
+
+ /** Regular operation mode -- minimal debug messages */
const REGULAR_MODE = 'regular';
+ /** Installation mode -- lots of debug messages */
const INSTALL_MODE = 'install';
-
- // Whether this is regular operation or the initial installation
+
+ /** Controls the level of debug message output */
protected $mMode = self::REGULAR_MODE;
-
- /// Last sequence value used for a primary key
+
+ /** Last sequence value used for a primary key */
protected $mInsertId = null;
-
- /*
- * These can be safely inherited
- *
- * Getter/Setter: (18)
- * failFunction
- * setOutputPage
- * bufferResults
- * ignoreErrors
- * trxLevel
- * errorCount
- * getLBInfo
- * setLBInfo
- * lastQuery
- * isOpen
- * setFlag
- * clearFlag
- * getFlag
- * getProperty
- * getDBname
- * getServer
- * tableNameCallback
- * tablePrefix
- *
- * Administrative: (8)
- * debug
- * installErrorHandler
- * restoreErrorHandler
- * connectionErrorHandler
- * reportConnectionError
- * sourceFile
- * sourceStream
- * replaceVars
- *
- * Database: (5)
- * query
- * set
- * selectField
- * generalizeSQL
- * update
- * strreplace
- * deadlockLoop
- *
- * Prepared Statement: 6
- * prepare
- * freePrepared
- * execute
- * safeQuery
- * fillPrepared
- * fillPreparedArg
- *
- * Slave/Master: (4)
- * masterPosWait
- * getSlavePos
- * getMasterPos
- * getLag
- *
- * Generation: (9)
- * tableNames
- * tableNamesN
- * tableNamesWithUseIndexOrJOIN
- * escapeLike
- * delete
- * insertSelect
- * timestampOrNull
- * resultObject
- * aggregateValue
- * selectSQLText
- * selectRow
- * makeUpdateOptions
- *
- * Reflection: (1)
- * indexExists
- */
-
- /*
- * These have been implemented
- *
- * Administrative: 7 / 7
- * constructor [Done]
- * open [Done]
- * openCataloged [Done]
- * close [Done]
- * newFromParams [Done]
- * openUncataloged [Done]
- * setup_database [Done]
- *
- * Getter/Setter: 13 / 13
- * cascadingDeletes [Done]
- * cleanupTriggers [Done]
- * strictIPs [Done]
- * realTimestamps [Done]
- * impliciGroupby [Done]
- * implicitOrderby [Done]
- * searchableIPs [Done]
- * functionalIndexes [Done]
- * getWikiID [Done]
- * isOpen [Done]
- * getServerVersion [Done]
- * getSoftwareLink [Done]
- * getSearchEngine [Done]
- *
- * Database driver wrapper: 23 / 23
- * lastError [Done]
- * lastErrno [Done]
- * doQuery [Done]
- * tableExists [Done]
- * fetchObject [Done]
- * fetchRow [Done]
- * freeResult [Done]
- * numRows [Done]
- * numFields [Done]
- * fieldName [Done]
- * insertId [Done]
- * dataSeek [Done]
- * affectedRows [Done]
- * selectDB [Done]
- * strencode [Done]
- * conditional [Done]
- * wasDeadlock [Done]
- * ping [Done]
- * getStatus [Done]
- * setTimeout [Done]
- * lock [Done]
- * unlock [Done]
- * insert [Done]
- * select [Done]
- *
- * Slave/master: 2 / 2
- * setFakeSlaveLag [Done]
- * setFakeMaster [Done]
- *
- * Reflection: 6 / 6
- * fieldExists [Done]
- * indexInfo [Done]
- * fieldInfo [Done]
- * fieldType [Done]
- * indexUnique [Done]
- * textFieldSize [Done]
- *
- * Generation: 16 / 16
- * tableName [Done]
- * addQuotes [Done]
- * makeList [Done]
- * makeSelectOptions [Done]
- * estimateRowCount [Done]
- * nextSequenceValue [Done]
- * useIndexClause [Done]
- * replace [Done]
- * deleteJoin [Done]
- * lowPriorityOption [Done]
- * limitResult [Done]
- * limitResultForUpdate [Done]
- * timestamp [Done]
- * encodeBlob [Done]
- * decodeBlob [Done]
- * buildConcat [Done]
- */
-
+
######################################
# Getters and Setters
######################################
-
+
/**
* Returns true if this database supports (and uses) cascading deletes
*/
@@ -330,20 +176,22 @@ class DatabaseIbm_db2 extends DatabaseBase {
}
/**
- * Returns true if this database supports (and uses) triggers (e.g. on the page table)
+ * Returns true if this database supports (and uses) triggers (e.g. on the
+ * page table)
*/
function cleanupTriggers() {
return true;
}
/**
- * Returns true if this database is strict about what can be put into an IP field.
+ * Returns true if this database is strict about what can be put into an
+ * IP field.
* Specifically, it uses a NULL value instead of an empty string.
*/
function strictIPs() {
return true;
}
-
+
/**
* Returns true if this database uses timestamps rather than integers
*/
@@ -359,7 +207,8 @@ class DatabaseIbm_db2 extends DatabaseBase {
}
/**
- * Returns true if this database does an implicit order by when the column has an index
+ * Returns true if this database does an implicit order by when the column
+ * has an index
* For example: SELECT page_title FROM page LIMIT 1
*/
function implicitOrderby() {
@@ -380,7 +229,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
function functionalIndexes() {
return true;
}
-
+
/**
* Returns a unique string representing the wiki on the server
*/
@@ -395,153 +244,148 @@ class DatabaseIbm_db2 extends DatabaseBase {
function getType() {
return 'ibm_db2';
}
-
- ######################################
- # Setup
- ######################################
-
-
+
/**
- *
+ *
* @param $server String: hostname of database server
* @param $user String: username
* @param $password String: password
* @param $dbName String: database name on the server
- * @param $failFunction Callback (optional)
* @param $flags Integer: database behaviour flags (optional, unused)
* @param $schema String
*/
- public function DatabaseIbm_db2($server = false, $user = false, $password = false,
- $dbName = false, $failFunction = false, $flags = 0,
+ public function __construct( $server = false, $user = false,
+ $password = false,
+ $dbName = false, $flags = 0,
$schema = self::USE_GLOBAL )
{
+ global $wgDBmwschema;
- global $wgOut, $wgDBmwschema;
- # Can't get a reference if it hasn't been set yet
- if ( !isset( $wgOut ) ) {
- $wgOut = null;
- }
- $this->mOut =& $wgOut;
- $this->mFailFunction = $failFunction;
- $this->mFlags = DBO_TRX | $flags;
-
if ( $schema == self::USE_GLOBAL ) {
$this->mSchema = $wgDBmwschema;
- }
- else {
+ } else {
$this->mSchema = $schema;
}
-
+
// configure the connection and statement objects
- $this->setDB2Option('db2_attr_case', 'DB2_CASE_LOWER', self::CONN_OPTION | self::STMT_OPTION);
- $this->setDB2Option('deferred_prepare', 'DB2_DEFERRED_PREPARE_ON', self::STMT_OPTION);
- $this->setDB2Option('rowcount', 'DB2_ROWCOUNT_PREFETCH_ON', self::STMT_OPTION);
-
- $this->open( $server, $user, $password, $dbName);
+ $this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER',
+ self::CONN_OPTION | self::STMT_OPTION );
+ $this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON',
+ self::STMT_OPTION );
+ $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON',
+ self::STMT_OPTION );
+
+ parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags );
}
-
+
/**
* Enables options only if the ibm_db2 extension version supports them
* @param $name String: name of the option in the options array
* @param $const String: name of the constant holding the right option value
* @param $type Integer: whether this is a Connection or Statement otion
*/
- private function setDB2Option($name, $const, $type) {
- if (defined($const)) {
- if ($type & self::CONN_OPTION) $this->mConnOptions[$name] = constant($const);
- if ($type & self::STMT_OPTION) $this->mStmtOptions[$name] = constant($const);
- }
- else {
- $this->installPrint("$const is not defined. ibm_db2 version is likely too low.");
+ private function setDB2Option( $name, $const, $type ) {
+ if ( defined( $const ) ) {
+ if ( $type & self::CONN_OPTION ) {
+ $this->mConnOptions[$name] = constant( $const );
+ }
+ if ( $type & self::STMT_OPTION ) {
+ $this->mStmtOptions[$name] = constant( $const );
+ }
+ } else {
+ $this->installPrint(
+ "$const is not defined. ibm_db2 version is likely too low." );
}
}
-
+
/**
* Outputs debug information in the appropriate place
* @param $string String: the relevant debug message
*/
- private function installPrint($string) {
- wfDebug("$string");
- if ($this->mMode == self::INSTALL_MODE) {
- print "<li>$string</li>";
+ private function installPrint( $string ) {
+ wfDebug( "$string\n" );
+ if ( $this->mMode == self::INSTALL_MODE ) {
+ print "<li><pre>$string</pre></li>";
flush();
- }
+ }
}
-
+
/**
* Opens a database connection and returns it
* Closes any existing connection
- * @return a fresh connection
+ *
* @param $server String: hostname
* @param $user String
* @param $password String
* @param $dbName String: database name
+ * @return a fresh connection
*/
- public function open( $server, $user, $password, $dbName )
- {
+ public function open( $server, $user, $password, $dbName ) {
// Load the port number
- global $wgDBport_db2, $wgDBcataloged;
+ global $wgDBport;
wfProfileIn( __METHOD__ );
-
+
// Load IBM DB2 driver if missing
- if (!@extension_loaded('ibm_db2')) {
- @dl('ibm_db2.so');
- }
+ wfDl( 'ibm_db2' );
+
// Test for IBM DB2 support, to avoid suppressed fatal error
if ( !function_exists( 'db2_connect' ) ) {
- $error = "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?\n";
- $this->installPrint($error);
- $this->reportConnectionError($error);
+ $error = <<<ERROR
+DB2 functions missing, have you enabled the ibm_db2 extension for PHP?
+
+ERROR;
+ $this->installPrint( $error );
+ $this->reportConnectionError( $error );
}
- if (!strlen($user)) { // Copied from Postgres
+ if ( strlen( $user ) < 1 ) {
+ wfProfileOut( __METHOD__ );
return null;
}
-
+
// Close existing connection
$this->close();
// Cache conn info
$this->mServer = $server;
- $this->mPort = $port = $wgDBport_db2;
+ $this->mPort = $port = $wgDBport;
$this->mUser = $user;
$this->mPassword = $password;
$this->mDBname = $dbName;
- $this->mCataloged = $cataloged = $wgDBcataloged;
-
- if ( $cataloged == self::CATALOGED ) {
- $this->openCataloged($dbName, $user, $password);
- }
- elseif ( $cataloged == self::UNCATALOGED ) {
- $this->openUncataloged($dbName, $user, $password, $server, $port);
- }
+
+ $this->openUncataloged( $dbName, $user, $password, $server, $port );
+
// Apply connection config
- db2_set_option($this->mConn, $this->mConnOptions, 1);
- // Not all MediaWiki code is transactional
- // Rather, turn autocommit off in the begin function and turn on after a commit
- db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
+ db2_set_option( $this->mConn, $this->mConnOptions, 1 );
+ // Some MediaWiki code is still transaction-less (?).
+ // The strategy is to keep AutoCommit on for that code
+ // but switch it off whenever a transaction is begun.
+ db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
- if ( $this->mConn == false ) {
+ if ( !$this->mConn ) {
$this->installPrint( "DB connection error\n" );
- $this->installPrint( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- $this->installPrint( $this->lastError()."\n" );
+ $this->installPrint(
+ "Server: $server, Database: $dbName, User: $user, Password: "
+ . substr( $password, 0, 3 ) . "...\n" );
+ $this->installPrint( $this->lastError() . "\n" );
+
+ wfProfileOut( __METHOD__ );
return null;
}
$this->mOpened = true;
$this->applySchema();
-
+
wfProfileOut( __METHOD__ );
return $this->mConn;
}
-
+
/**
* Opens a cataloged database connection, sets mConn
*/
- protected function openCataloged( $dbName, $user, $password )
- {
- @$this->mConn = db2_connect($dbName, $user, $password);
+ protected function openCataloged( $dbName, $user, $password ) {
+ @$this->mConn = db2_pconnect( $dbName, $user, $password );
}
-
+
/**
* Opens an uncataloged database connection, sets mConn
*/
@@ -550,14 +394,15 @@ class DatabaseIbm_db2 extends DatabaseBase {
$str = "DRIVER={IBM DB2 ODBC DRIVER};";
$str .= "DATABASE=$dbName;";
$str .= "HOSTNAME=$server;";
- if ($port) $str .= "PORT=$port;";
+ // port was formerly validated to not be 0
+ $str .= "PORT=$port;";
$str .= "PROTOCOL=TCPIP;";
$str .= "UID=$user;";
$str .= "PWD=$password;";
-
- @$this->mConn = db2_connect($str, $user, $password);
+
+ @$this->mConn = db2_pconnect( $str, $user, $password );
}
-
+
/**
* Closes a database connection, if it is open
* Returns success, true if already closed
@@ -565,16 +410,15 @@ class DatabaseIbm_db2 extends DatabaseBase {
public function close() {
$this->mOpened = false;
if ( $this->mConn ) {
- if ($this->trxLevel() > 0) {
+ if ( $this->trxLevel() > 0 ) {
$this->commit();
}
return db2_close( $this->mConn );
- }
- else {
+ } else {
return true;
}
}
-
+
/**
* Returns a fresh instance of this class
*
@@ -582,34 +426,35 @@ class DatabaseIbm_db2 extends DatabaseBase {
* @param $user String: username
* @param $password String
* @param $dbName String: database name on the server
- * @param $failFunction Callback (optional)
* @param $flags Integer: database behaviour flags (optional, unused)
* @return DatabaseIbm_db2 object
*/
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0)
+ static function newFromParams( $server, $user, $password, $dbName,
+ $flags = 0 )
{
- return new DatabaseIbm_db2( $server, $user, $password, $dbName, $failFunction, $flags );
+ return new DatabaseIbm_db2( $server, $user, $password, $dbName,
+ $flags );
}
-
+
/**
* Retrieves the most current database error
* Forces a database rollback
*/
public function lastError() {
$connerr = db2_conn_errormsg();
- if ($connerr) {
+ if ( $connerr ) {
//$this->rollback();
return $connerr;
}
$stmterr = db2_stmt_errormsg();
- if ($stmterr) {
+ if ( $stmterr ) {
//$this->rollback();
return $stmterr;
}
-
+
return false;
}
-
+
/**
* Get the last error number
* Return 0 if no error
@@ -617,43 +462,45 @@ class DatabaseIbm_db2 extends DatabaseBase {
*/
public function lastErrno() {
$connerr = db2_conn_error();
- if ($connerr) return $connerr;
+ if ( $connerr ) {
+ return $connerr;
+ }
$stmterr = db2_stmt_error();
- if ($stmterr) return $stmterr;
+ if ( $stmterr ) {
+ return $stmterr;
+ }
return 0;
}
-
+
/**
* Is a database connection open?
- * @return
+ * @return
*/
public function isOpen() { return $this->mOpened; }
-
+
/**
* The DBMS-dependent part of query()
* @param $sql String: SQL query.
- * @return object Result object to feed to fetchObject, fetchRow, ...; or false on failure
+ * @return object Result object for fetch functions or false on failure
* @access private
*/
/*private*/
public function doQuery( $sql ) {
- //print "<li><pre>$sql</pre></li>";
- // Switch into the correct namespace
$this->applySchema();
-
+
$ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
- if( !$ret ) {
- print "<br><pre>";
- print $sql;
- print "</pre><br>";
+ if( $ret == false ) {
$error = db2_stmt_errormsg();
- throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( $error ) );
+ $this->installPrint( "<pre>$sql</pre>" );
+ $this->installPrint( $error );
+ throw new DBUnexpectedError( $this, 'SQL error: '
+ . htmlspecialchars( $error ) );
}
$this->mLastResult = $ret;
- $this->mAffectedRows = null; // Not calculated until asked for
+ $this->mAffectedRows = null; // Not calculated until asked for
return $ret;
}
-
+
/**
* @return string Version information from the database
*/
@@ -661,7 +508,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
$info = db2_server_info( $this->mConn );
return $info->DBMS_VER;
}
-
+
/**
* Queries whether a given table exists
* @return boolean
@@ -669,22 +516,24 @@ class DatabaseIbm_db2 extends DatabaseBase {
public function tableExists( $table ) {
$schema = $this->mSchema;
$sql = <<< EOF
-SELECT COUNT(*) FROM SYSIBM.SYSTABLES ST
+SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST
WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema'
EOF;
$res = $this->query( $sql );
- if (!$res) return false;
-
+ if ( !$res ) {
+ return false;
+ }
+
// If the table exists, there should be one of it
- @$row = $this->fetchRow($res);
+ @$row = $this->fetchRow( $res );
$count = $row[0];
- if ($count == '1' or $count == 1) {
+ if ( $count == '1' || $count == 1 ) {
return true;
}
-
+
return false;
}
-
+
/**
* Fetch the next row from the given result object, in object form.
* Fields can be retrieved with $row->fieldname, with fields acting like
@@ -700,14 +549,15 @@ EOF;
}
@$row = db2_fetch_object( $res );
if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
+ throw new DBUnexpectedError( $this, 'Error in fetchObject(): '
+ . htmlspecialchars( $this->lastError() ) );
}
return $row;
}
/**
* Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
+ * form. Fields are retrieved with $row['fieldname'].
*
* @param $res SQL result object as returned from Database::query(), etc.
* @return DB2 row object
@@ -719,55 +569,47 @@ EOF;
}
@$row = db2_fetch_array( $res );
if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
+ throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
+ . htmlspecialchars( $this->lastError() ) );
}
return $row;
}
-
- /**
- * Override if introduced to base Database class
- */
- public function initial_setup() {
- // do nothing
- }
-
+
/**
* Create tables, stored procedures, and so on
*/
public function setup_database() {
- // Timeout was being changed earlier due to mysterious crashes
- // Changing it now may cause more problems than not changing it
- //set_time_limit(240);
try {
// TODO: switch to root login if available
-
+
// Switch into the correct namespace
$this->applySchema();
$this->begin();
-
+
$res = $this->sourceFile( "../maintenance/ibm_db2/tables.sql" );
- if ($res !== true) {
- print " <b>FAILED</b>: " . htmlspecialchars( $res ) . "</li>";
+ if ( $res !== true ) {
+ print ' <b>FAILED</b>: ' . htmlspecialchars( $res ) . '</li>';
} else {
- print " done</li>";
+ print ' done</li>';
}
- $res = null;
-
- // TODO: update mediawiki_version table
-
+ $res = $this->sourceFile( "../maintenance/ibm_db2/foreignkeys.sql" );
+ if ( $res !== true ) {
+ print ' <b>FAILED</b>: ' . htmlspecialchars( $res ) . '</li>';
+ } else {
+ print '<li>Foreign keys done</li>';
+ }
+
// TODO: populate interwiki links
-
- if ($this->lastError()) {
- print "<li>Errors encountered during table creation -- rolled back</li>\n";
- print "<li>Please install again</li>\n";
+
+ if ( $this->lastError() ) {
+ $this->installPrint(
+ 'Errors encountered during table creation -- rolled back' );
+ $this->installPrint( 'Please install again' );
$this->rollback();
- }
- else {
+ } else {
$this->commit();
}
- }
- catch (MWException $mwe)
- {
+ } catch ( MWException $mwe ) {
print "<br><pre>$mwe</pre><br>";
}
}
@@ -775,47 +617,48 @@ EOF;
/**
* Escapes strings
* Doesn't escape numbers
+ *
* @param $s String: string to escape
* @return escaped string
*/
public function addQuotes( $s ) {
- //$this->installPrint("DB2::addQuotes($s)\n");
+ //$this->installPrint( "DB2::addQuotes( $s )\n" );
if ( is_null( $s ) ) {
- return "NULL";
- } else if ($s instanceof Blob) {
- return "'".$s->fetch($s)."'";
- } else if ($s instanceof IBM_DB2Blob) {
- return "'".$this->decodeBlob($s)."'";
- }
- $s = $this->strencode($s);
- if ( is_numeric($s) ) {
+ return 'NULL';
+ } elseif ( $s instanceof Blob ) {
+ return "'" . $s->fetch( $s ) . "'";
+ } elseif ( $s instanceof IBM_DB2Blob ) {
+ return "'" . $this->decodeBlob( $s ) . "'";
+ }
+ $s = $this->strencode( $s );
+ if ( is_numeric( $s ) ) {
return $s;
- }
- else {
+ } else {
return "'$s'";
}
}
-
+
/**
* Verifies that a DB2 column/field type is numeric
- * @return bool true if numeric
+ *
* @param $type String: DB2 column type
+ * @return Boolean: true if numeric
*/
public function is_numeric_type( $type ) {
- switch (strtoupper($type)) {
- case 'SMALLINT':
- case 'INTEGER':
- case 'INT':
- case 'BIGINT':
- case 'DECIMAL':
- case 'REAL':
- case 'DOUBLE':
- case 'DECFLOAT':
- return true;
+ switch ( strtoupper( $type ) ) {
+ case 'SMALLINT':
+ case 'INTEGER':
+ case 'INT':
+ case 'BIGINT':
+ case 'DECIMAL':
+ case 'REAL':
+ case 'DOUBLE':
+ case 'DECFLOAT':
+ return true;
}
return false;
}
-
+
/**
* Alias for addQuotes()
* @param $s String: string to escape
@@ -823,178 +666,153 @@ EOF;
*/
public function strencode( $s ) {
// Bloody useless function
- // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
+ // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
// But also necessary
- $s = db2_escape_string($s);
+ $s = db2_escape_string( $s );
// Wide characters are evil -- some of them look like '
- $s = utf8_encode($s);
+ $s = utf8_encode( $s );
// Fix its stupidity
- $from = array("\\\\", "\\'", '\\n', '\\t', '\\"', '\\r');
- $to = array("\\", "''", "\n", "\t", '"', "\r");
- $s = str_replace($from, $to, $s); // DB2 expects '', not \' escaping
+ $from = array( "\\\\", "\\'", '\\n', '\\t', '\\"', '\\r' );
+ $to = array( "\\", "''", "\n", "\t", '"', "\r" );
+ $s = str_replace( $from, $to, $s ); // DB2 expects '', not \' escaping
return $s;
}
-
+
/**
* Switch into the database schema
*/
protected function applySchema() {
- if ( !($this->mSchemaSet) ) {
+ if ( !( $this->mSchemaSet ) ) {
$this->mSchemaSet = true;
$this->begin();
- $this->doQuery("SET SCHEMA = $this->mSchema");
+ $this->doQuery( "SET SCHEMA = $this->mSchema" );
$this->commit();
- }
+ }
}
-
+
/**
* Start a transaction (mandatory)
*/
public function begin( $fname = 'DatabaseIbm_db2::begin' ) {
- // turn off auto-commit
- db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
+ // BEGIN is implicit for DB2
+ // However, it requires that AutoCommit be off.
+
+ // Some MediaWiki code is still transaction-less (?).
+ // The strategy is to keep AutoCommit on for that code
+ // but switch it off whenever a transaction is begun.
+ db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_OFF );
+
$this->mTrxLevel = 1;
}
-
+
/**
* End a transaction
* Must have a preceding begin()
*/
public function commit( $fname = 'DatabaseIbm_db2::commit' ) {
- db2_commit($this->mConn);
- // turn auto-commit back on
- db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
+ db2_commit( $this->mConn );
+
+ // Some MediaWiki code is still transaction-less (?).
+ // The strategy is to keep AutoCommit on for that code
+ // but switch it off whenever a transaction is begun.
+ db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
+
$this->mTrxLevel = 0;
}
-
+
/**
* Cancel a transaction
*/
public function rollback( $fname = 'DatabaseIbm_db2::rollback' ) {
- db2_rollback($this->mConn);
+ db2_rollback( $this->mConn );
// turn auto-commit back on
// not sure if this is appropriate
- db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
+ db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
$this->mTrxLevel = 0;
}
-
+
/**
* Makes an encoded list of strings from an array
* $mode:
- * LIST_COMMA - comma separated, no field names
- * LIST_AND - ANDed WHERE clause (without the WHERE)
- * LIST_OR - ORed WHERE clause (without the WHERE)
- * LIST_SET - comma separated with field names, like a SET clause
- * LIST_NAMES - comma separated field names
- */
- public function makeList( $a, $mode = LIST_COMMA ) {
+ * LIST_COMMA - comma separated, no field names
+ * LIST_AND - ANDed WHERE clause (without the WHERE)
+ * LIST_OR - ORed WHERE clause (without the WHERE)
+ * LIST_SET - comma separated with field names, like a SET clause
+ * LIST_NAMES - comma separated field names
+ * LIST_SET_PREPARED - like LIST_SET, except with ? tokens as values
+ */
+ function makeList( $a, $mode = LIST_COMMA ) {
if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
+ throw new DBUnexpectedError( $this,
+ 'DatabaseIbm_db2::makeList called with incorrect parameters' );
}
- $first = true;
- $list = '';
- foreach ( $a as $field => $value ) {
- if ( !$first ) {
- if ( $mode == LIST_AND ) {
- $list .= ' AND ';
- } elseif($mode == LIST_OR) {
- $list .= ' OR ';
+ // if this is for a prepared UPDATE statement
+ // (this should be promoted to the parent class
+ // once other databases use prepared statements)
+ if ( $mode == LIST_SET_PREPARED ) {
+ $first = true;
+ $list = '';
+ foreach ( $a as $field => $value ) {
+ if ( !$first ) {
+ $list .= ", $field = ?";
} else {
- $list .= ',';
- }
- } else {
- $first = false;
- }
- if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
- $list .= "($value)";
- } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
- $list .= "$value";
- } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
- if( count( $value ) == 0 ) {
- throw new MWException( __METHOD__.': empty input' );
- } 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( is_null($value) ) {
- 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 = ";
- }
- if ( $mode == LIST_NAMES ) {
- $list .= $value;
- }
- // Leo: Can't insert quoted numbers into numeric columns
- // (?) Might cause other problems. May have to check column type before insertion.
- else if ( is_numeric($value) ) {
- $list .= $value;
- }
- else {
- $list .= $this->addQuotes( $value );
+ $list .= "$field = ?";
+ $first = false;
}
}
+ $list .= '';
+
+ return $list;
}
- return $list;
+
+ // otherwise, call the usual function
+ return parent::makeList( $a, $mode );
}
-
+
/**
* Construct a LIMIT query with optional offset
* This is used for query pages
+ *
* @param $sql string SQL query we will append the limit too
* @param $limit integer the SQL limit
* @param $offset integer the SQL offset (default false)
*/
- public function limitResult($sql, $limit, $offset=false) {
- if( !is_numeric($limit) ) {
- throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
+ public function limitResult( $sql, $limit, $offset=false ) {
+ if( !is_numeric( $limit ) ) {
+ throw new DBUnexpectedError( $this,
+ "Invalid non-numeric limit passed to limitResult()\n" );
}
if( $offset ) {
- $this->installPrint("Offset parameter not supported in limitResult()\n");
+ if ( stripos( $sql, 'where' ) === false ) {
+ return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )";
+ } else {
+ return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )";
+ }
}
- // TODO implement proper offset handling
- // idea: get all the rows between 0 and offset, advance cursor to offset
return "$sql FETCH FIRST $limit ROWS ONLY ";
}
-
+
/**
* Handle reserved keyword replacement in table names
- * @return
+ *
* @param $name Object
+ * @return String
*/
public function tableName( $name ) {
- # Replace reserved words with better ones
-// switch( $name ) {
-// case 'user':
-// return 'mwuser';
-// case 'text':
-// return 'pagecontent';
-// default:
-// return $name;
-// }
// we want maximum compatibility with MySQL schema
return $name;
}
-
+
/**
* Generates a timestamp in an insertable format
- * @return string timestamp value
+ *
* @param $ts timestamp
+ * @return String: timestamp value
*/
- public function timestamp( $ts=0 ) {
+ public function timestamp( $ts = 0 ) {
// TS_MW cannot be easily distinguished from an integer
- return wfTimestamp(TS_DB2,$ts);
+ return wfTimestamp( TS_DB2, $ts );
}
/**
@@ -1003,19 +821,20 @@ EOF;
* @return next value in that sequence
*/
public function nextSequenceValue( $seqName ) {
- // Not using sequences in the primary schema to allow for easy third-party migration scripts
- // Emulating MySQL behaviour of using NULL to signal that sequences aren't used
+ // Not using sequences in the primary schema to allow for easier migration
+ // from MySQL
+ // Emulating MySQL behaviour of using NULL to signal that sequences
+ // aren't used
/*
$safeseq = preg_replace( "/'/", "''", $seqName );
$res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
- $this->freeResult( $res );
return $this->mInsertId;
*/
return null;
}
-
+
/**
* This must be called after nextSequenceVal
* @return Last sequence value used as a primary key
@@ -1023,26 +842,27 @@ EOF;
public function insertId() {
return $this->mInsertId;
}
-
+
/**
- * Updates the mInsertId property with the value of the last insert into a generated column
+ * Updates the mInsertId property with the value of the last insert
+ * into a generated column
+ *
* @param $table String: sanitized table name
- * @param $primaryKey Mixed: string name of the primary key or a bool if this call is a do-nothing
+ * @param $primaryKey Mixed: string name of the primary key
* @param $stmt Resource: prepared statement resource
* of the SELECT primary_key FROM FINAL TABLE ( INSERT ... ) form
*/
- private function calcInsertId($table, $primaryKey, $stmt) {
- if ($primaryKey) {
- $id_row = $this->fetchRow($stmt);
- $this->mInsertId = $id_row[0];
+ private function calcInsertId( $table, $primaryKey, $stmt ) {
+ if ( $primaryKey ) {
+ $this->mInsertId = db2_last_insert_id( $this->mConn );
}
}
-
+
/**
* INSERT wrapper, inserts an array into a table
*
- * $args may be a single associative array, or an array of these with numeric keys,
- * for multi-row insert
+ * $args may be a single associative array, or an array of arrays
+ * with numeric keys, for multi-row insert
*
* @param $table String: Name of the table to insert to.
* @param $args Array: Items to insert into the table.
@@ -1051,30 +871,33 @@ EOF;
*
* @return bool Success of insert operation. IGNORE always returns true.
*/
- public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', $options = array() ) {
+ public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert',
+ $options = array() )
+ {
if ( !count( $args ) ) {
return true;
}
// get database-specific table name (not used)
$table = $this->tableName( $table );
// format options as an array
- if ( !is_array( $options ) ) $options = array( $options );
+ $options = IBM_DB2Helper::makeArray( $options );
// format args as an array of arrays
if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) {
- $args = array($args);
+ $args = array( $args );
}
+
// prevent insertion of NULL into primary key columns
- list($args, $primaryKeys) = $this->removeNullPrimaryKeys($table, $args);
+ list( $args, $primaryKeys ) = $this->removeNullPrimaryKeys( $table, $args );
// if there's only one primary key
// we'll be able to read its value after insertion
$primaryKey = false;
- if (count($primaryKeys) == 1) {
+ if ( count( $primaryKeys ) == 1 ) {
$primaryKey = $primaryKeys[0];
}
-
+
// get column names
$keys = array_keys( $args[0] );
- $key_count = count($keys);
+ $key_count = count( $keys );
// If IGNORE is set, we use savepoints to emulate mysql's behavior
$ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
@@ -1082,144 +905,169 @@ EOF;
// assume success
$res = true;
// If we are not in a transaction, we need to be for savepoint trickery
- $didbegin = 0;
- if (! $this->mTrxLevel) {
+ if ( !$this->mTrxLevel ) {
$this->begin();
- $didbegin = 1;
}
- $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
- switch($key_count) {
- //case 0 impossible
- case 1:
- $sql .= '(?)';
- break;
- default:
- $sql .= '(?' . str_repeat(',?', $key_count-1) . ')';
- }
- // add logic to read back the new primary key value
- if ($primaryKey) {
- $sql = "SELECT $primaryKey FROM FINAL TABLE($sql)";
+ $sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES ';
+ if ( $key_count == 1 ) {
+ $sql .= '( ? )';
+ } else {
+ $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )';
}
- $stmt = $this->prepare($sql);
-
+ //$this->installPrint( "Preparing the following SQL:" );
+ //$this->installPrint( "$sql" );
+ //$this->installPrint( print_r( $args, true ));
+ $stmt = $this->prepare( $sql );
+
// start a transaction/enter transaction mode
$this->begin();
if ( !$ignore ) {
- $first = true;
+ //$first = true;
foreach ( $args as $row ) {
+ //$this->installPrint( "Inserting " . print_r( $row, true ));
// insert each row into the database
- $res = $res & $this->execute($stmt, $row);
+ $res = $res & $this->execute( $stmt, $row );
+ if ( !$res ) {
+ $this->installPrint( 'Last error:' );
+ $this->installPrint( $this->lastError() );
+ }
// get the last inserted value into a generated column
- $this->calcInsertId($table, $primaryKey, $stmt);
+ $this->calcInsertId( $table, $primaryKey, $stmt );
}
- }
- else {
+ } else {
$olde = error_reporting( 0 );
// For future use, we may want to track the number of actual inserts
// Right now, insert (all writes) simply return true/false
$numrowsinserted = 0;
-
+
// always return true
$res = true;
-
+
foreach ( $args as $row ) {
$overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
- db2_exec($this->mConn, $overhead, $this->mStmtOptions);
-
- $res2 = $this->execute($stmt, $row);
+ db2_exec( $this->mConn, $overhead, $this->mStmtOptions );
+
+ $res2 = $this->execute( $stmt, $row );
+
+ if ( !$res2 ) {
+ $this->installPrint( 'Last error:' );
+ $this->installPrint( $this->lastError() );
+ }
// get the last inserted value into a generated column
- $this->calcInsertId($table, $primaryKey, $stmt);
-
+ $this->calcInsertId( $table, $primaryKey, $stmt );
+
$errNum = $this->lastErrno();
- if ($errNum) {
- db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore", $this->mStmtOptions );
- }
- else {
- db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore", $this->mStmtOptions );
+ if ( $errNum ) {
+ db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore",
+ $this->mStmtOptions );
+ } else {
+ db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore",
+ $this->mStmtOptions );
$numrowsinserted++;
}
}
-
+
$olde = error_reporting( $olde );
// Set the affected row count for the whole operation
$this->mAffectedRows = $numrowsinserted;
}
// commit either way
$this->commit();
-
+ $this->freePrepared( $stmt );
+
return $res;
}
-
+
/**
* Given a table name and a hash of columns with values
* Removes primary key columns from the hash where the value is NULL
- *
+ *
* @param $table String: name of the table
* @param $args Array of hashes of column names with values
- * @return Array: tuple containing filtered array of columns, array of primary keys
+ * @return Array: tuple( filtered array of columns, array of primary keys )
*/
- private function removeNullPrimaryKeys($table, $args) {
+ private function removeNullPrimaryKeys( $table, $args ) {
$schema = $this->mSchema;
// find out the primary keys
- $keyres = db2_primary_keys($this->mConn, null, strtoupper($schema), strtoupper($table));
+ $keyres = db2_primary_keys( $this->mConn, null, strtoupper( $schema ),
+ strtoupper( $table )
+ );
$keys = array();
- for ($row = $this->fetchObject($keyres); $row != null; $row = $this->fetchRow($keyres)) {
- $keys[] = strtolower($row->column_name);
+ for (
+ $row = $this->fetchObject( $keyres );
+ $row != null;
+ $row = $this->fetchObject( $keyres )
+ )
+ {
+ $keys[] = strtolower( $row->column_name );
}
// remove primary keys
- foreach ($args as $ai => $row) {
- foreach ($keys as $ki => $key) {
- if ($row[$key] == null) {
- unset($row[$key]);
+ foreach ( $args as $ai => $row ) {
+ foreach ( $keys as $key ) {
+ if ( $row[$key] == null ) {
+ unset( $row[$key] );
}
}
$args[$ai] = $row;
}
// return modified hash
- return array($args, $keys);
+ return array( $args, $keys );
}
-
+
/**
* UPDATE wrapper, takes a condition array and a SET array
*
* @param $table String: The table to UPDATE
* @param $values An array of values to SET
- * @param $conds An array of conditions (WHERE). Use '*' to update all rows.
+ * @param $conds An array of conditions ( WHERE ). Use '*' to update all rows.
* @param $fname String: The Class::Function calling this function
- * (for the log)
+ * ( for the log )
* @param $options An array of UPDATE options, can be one or
* more of IGNORE, LOW_PRIORITY
* @return Boolean
*/
- public function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
+ public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update',
+ $options = array() )
+ {
$table = $this->tableName( $table );
$opts = $this->makeUpdateOptions( $options );
- $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
+ $sql = "UPDATE $opts $table SET "
+ . $this->makeList( $values, LIST_SET_PREPARED );
if ( $conds != '*' ) {
$sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
}
- return $this->query( $sql, $fname );
+ $stmt = $this->prepare( $sql );
+ $this->installPrint( 'UPDATE: ' . print_r( $values, true ) );
+ // assuming for now that an array with string keys will work
+ // if not, convert to simple array first
+ $result = $this->execute( $stmt, $values );
+ $this->freePrepared( $stmt );
+
+ return $result;
}
-
+
/**
* DELETE query wrapper
*
* Use $conds == "*" to delete all rows
*/
- public function delete( $table, $conds, $fname = 'Database::delete' ) {
+ public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
+ throw new DBUnexpectedError( $this,
+ 'DatabaseIbm_db2::delete() called with no conditions' );
}
$table = $this->tableName( $table );
$sql = "DELETE FROM $table";
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
- return $this->query( $sql, $fname );
+ $result = $this->query( $sql, $fname );
+
+ return $result;
}
-
+
/**
* Returns the number of rows affected by the last query or 0
* @return Integer: the number of rows affected by the last query
@@ -1229,11 +1077,12 @@ EOF;
// Forced result for simulated queries
return $this->mAffectedRows;
}
- if( empty( $this->mLastResult ) )
+ if( empty( $this->mLastResult ) ) {
return 0;
+ }
return db2_num_rows( $this->mLastResult );
}
-
+
/**
* Simulates REPLACE with a DELETE followed by INSERT
* @param $table Object
@@ -1242,10 +1091,12 @@ EOF;
* @param $fname String: name of the function for profiling
* @return nothing
*/
- function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseIbm_db2::replace' ) {
+ function replace( $table, $uniqueIndexes, $rows,
+ $fname = 'DatabaseIbm_db2::replace' )
+ {
$table = $this->tableName( $table );
- if (count($rows)==0) {
+ if ( count( $rows )==0 ) {
return;
}
@@ -1262,9 +1113,9 @@ EOF;
foreach ( $uniqueIndexes as $index ) {
if ( $first ) {
$first = false;
- $sql .= "(";
+ $sql .= '( ';
} else {
- $sql .= ') OR (';
+ $sql .= ' ) OR ( ';
}
if ( is_array( $index ) ) {
$first2 = true;
@@ -1274,23 +1125,24 @@ EOF;
} else {
$sql .= ' AND ';
}
- $sql .= $col.'=' . $this->addQuotes( $row[$col] );
+ $sql .= $col . '=' . $this->addQuotes( $row[$col] );
}
} else {
- $sql .= $index.'=' . $this->addQuotes( $row[$index] );
+ $sql .= $index . '=' . $this->addQuotes( $row[$index] );
}
}
- $sql .= ')';
+ $sql .= ' )';
$this->query( $sql, $fname );
}
# Now insert the row
- $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
- $this->makeList( $row, LIST_COMMA ) . ')';
+ $sql = "INSERT INTO $table ( "
+ . $this->makeList( array_keys( $row ), LIST_NAMES )
+ .' ) VALUES ( ' . $this->makeList( $row, LIST_COMMA ) . ' )';
$this->query( $sql, $fname );
}
}
-
+
/**
* Returns the number of rows in the result set
* Has to be called right after the corresponding select query
@@ -1303,12 +1155,11 @@ EOF;
}
if ( $this->mNumRows ) {
return $this->mNumRows;
- }
- else {
+ } else {
return 0;
}
}
-
+
/**
* Moves the row pointer of the result set
* @param $res Object: result set
@@ -1321,11 +1172,11 @@ EOF;
}
return db2_fetch_row( $res, $row );
}
-
+
###
- # Fix notices in Block.php
+ # Fix notices in Block.php
###
-
+
/**
* Frees memory associated with a statement resource
* @param $res Object: statement resource to free
@@ -1336,10 +1187,10 @@ EOF;
$res = $res->result;
}
if ( !@db2_free_result( $res ) ) {
- throw new DBUnexpectedError($this, "Unable to free DB2 result\n" );
+ throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
}
}
-
+
/**
* Returns the number of columns in a resource
* @param $res Object: statement resource
@@ -1351,7 +1202,7 @@ EOF;
}
return db2_num_fields( $res );
}
-
+
/**
* Returns the nth column name
* @param $res Object: statement resource
@@ -1364,57 +1215,65 @@ EOF;
}
return db2_field_name( $res, $n );
}
-
+
/**
* SELECT wrapper
*
* @param $table Array or string, table name(s) (prefix auto-added)
* @param $vars Array or string, field name(s) to be retrieved
* @param $conds Array or string, condition(s) for WHERE
- * @param $fname String: calling function name (use __METHOD__) for logs/profiling
- * @param $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
+ * @param $fname String: calling function name (use __METHOD__)
+ * for logs/profiling
+ * @param $options Associative array of options
+ * (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of
+ * supported stuff
* @param $join_conds Associative array of table join conditions (optional)
- * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return Mixed: database result resource (feed to Database::fetchObject or whatever), or false on failure
+ * (e.g. array( 'page' => array('LEFT JOIN',
+ * 'page_latest=rev_id') )
+ * @return Mixed: database result resource for fetch functions or false
+ * on failure
*/
- public function select( $table, $vars, $conds='', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
+ public function select( $table, $vars, $conds = '', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
{
- $res = parent::select( $table, $vars, $conds, $fname, $options, $join_conds );
-
+ $res = parent::select( $table, $vars, $conds, $fname, $options,
+ $join_conds );
+
// We must adjust for offset
- if ( isset( $options['LIMIT'] ) ) {
- if ( isset ($options['OFFSET'] ) ) {
- $limit = $options['LIMIT'];
- $offset = $options['OFFSET'];
- }
+ if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
+ $limit = $options['LIMIT'];
+ $offset = $options['OFFSET'];
}
-
-
- // DB2 does not have a proper num_rows() function yet, so we must emulate it
- // DB2 9.5.3/9.5.4 and the corresponding ibm_db2 driver will introduce a working one
- // Yay!
-
+
+ // DB2 does not have a proper num_rows() function yet, so we must emulate
+ // DB2 9.5.4 and the corresponding ibm_db2 driver will introduce
+ // a working one
+ // TODO: Yay!
+
// we want the count
- $vars2 = array('count(*) as num_rows');
+ $vars2 = array( 'count( * ) as num_rows' );
// respecting just the limit option
$options2 = array();
- if ( isset( $options['LIMIT'] ) ) $options2['LIMIT'] = $options['LIMIT'];
+ if ( isset( $options['LIMIT'] ) ) {
+ $options2['LIMIT'] = $options['LIMIT'];
+ }
// but don't try to emulate for GROUP BY
- if ( isset( $options['GROUP BY'] ) ) return $res;
-
- $res2 = parent::select( $table, $vars2, $conds, $fname, $options2, $join_conds );
- $obj = $this->fetchObject($res2);
+ if ( isset( $options['GROUP BY'] ) ) {
+ return $res;
+ }
+
+ $res2 = parent::select( $table, $vars2, $conds, $fname, $options2,
+ $join_conds );
+ $obj = $this->fetchObject( $res2 );
$this->mNumRows = $obj->num_rows;
-
-
+
return $res;
}
-
+
/**
* Handles ordering, grouping, and having options ('GROUP BY' => colname)
* Has limited support for per-column options (colnum => 'DISTINCT')
- *
+ *
* @private
*
* @param $options Associative array of options to be turned into
@@ -1432,31 +1291,41 @@ EOF;
}
}
- if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
- if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
-
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
-
+ if ( isset( $options['GROUP BY'] ) ) {
+ $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
+ }
+ if ( isset( $options['HAVING'] ) ) {
+ $preLimitTail .= " HAVING {$options['HAVING']}";
+ }
+ if ( isset( $options['ORDER BY'] ) ) {
+ $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
+ }
+
+ if ( isset( $noKeyOptions['DISTINCT'] )
+ || isset( $noKeyOptions['DISTINCTROW'] ) )
+ {
+ $startOpts .= 'DISTINCT';
+ }
+
return array( $startOpts, '', $preLimitTail, $postLimitTail );
}
-
+
/**
* Returns link to IBM DB2 free download
- * @return string wikitext of a link to the server software's web site
+ * @return String: wikitext of a link to the server software's web site
*/
- public function getSoftwareLink() {
- return "[http://www.ibm.com/software/data/db2/express/?s_cmp=ECDDWW01&s_tact=MediaWiki IBM DB2]";
+ public static function getSoftwareLink() {
+ return '[http://www.ibm.com/db2/express/ IBM DB2]';
}
-
+
/**
* Get search engine class. All subclasses of this
* need to implement this if they wish to use searching.
- *
+ *
* @return String
*/
public function getSearchEngine() {
- return "SearchIBM_DB2";
+ return 'SearchIBM_DB2';
}
/**
@@ -1466,16 +1335,17 @@ EOF;
public function wasDeadlock() {
// get SQLSTATE
$err = $this->lastErrno();
- switch($err) {
+ switch( $err ) {
+ // This is literal port of the MySQL logic and may be wrong for DB2
case '40001': // sql0911n, Deadlock or timeout, rollback
case '57011': // sql0904n, Resource unavailable, no rollback
case '57033': // sql0913n, Deadlock or timeout, no rollback
- $this->installPrint("In a deadlock because of SQLSTATE $err");
+ $this->installPrint( "In a deadlock because of SQLSTATE $err" );
return true;
}
return false;
}
-
+
/**
* Ping the server and try to reconnect if it there is no connection
* The connection may be closed and reopened while this happens
@@ -1485,15 +1355,9 @@ EOF;
// db2_ping() doesn't exist
// Emulate
$this->close();
- if ($this->mCataloged == null) {
- return false;
- }
- else if ($this->mCataloged) {
- $this->mConn = $this->openCataloged($this->mDBName, $this->mUser, $this->mPassword);
- }
- else if (!$this->mCataloged) {
- $this->mConn = $this->openUncataloged($this->mDBName, $this->mUser, $this->mPassword, $this->mServer, $this->mPort);
- }
+ $this->mConn = $this->openUncataloged( $this->mDBName, $this->mUser,
+ $this->mPassword, $this->mServer, $this->mPort );
+
return false;
}
######################################
@@ -1502,65 +1366,33 @@ EOF;
/**
* Not implemented
* @return string ''
- * @deprecated
*/
- public function getStatus( $which="%" ) { $this->installPrint('Not implemented for DB2: getStatus()'); return ''; }
- /**
- * Not implemented
- * TODO
- * @return bool true
- */
- /**
- * Not implemented
- * @deprecated
- */
- public function setFakeSlaveLag( $lag ) { $this->installPrint('Not implemented for DB2: setFakeSlaveLag()'); }
- /**
- * Not implemented
- * @deprecated
- */
- public function setFakeMaster( $enabled = true ) { $this->installPrint('Not implemented for DB2: setFakeMaster()'); }
+ public function getStatus( $which = '%' ) {
+ $this->installPrint( 'Not implemented for DB2: getStatus()' );
+ return '';
+ }
/**
* Not implemented
* @return string $sql
- * @deprecated
- */
- public function limitResultForUpdate($sql, $num) { $this->installPrint('Not implemented for DB2: limitResultForUpdate()'); return $sql; }
-
+ */
+ public function limitResultForUpdate( $sql, $num ) {
+ $this->installPrint( 'Not implemented for DB2: limitResultForUpdate()' );
+ return $sql;
+ }
+
/**
* Only useful with fake prepare like in base Database class
* @return string
*/
- public function fillPreparedArg( $matches ) { $this->installPrint('Not useful for DB2: fillPreparedArg()'); return ''; }
-
+ public function fillPreparedArg( $matches ) {
+ $this->installPrint( 'Not useful for DB2: fillPreparedArg()' );
+ return '';
+ }
+
######################################
# Reflection
######################################
-
- /**
- * Query whether a given column exists in the mediawiki schema
- * @param $table String: name of the table
- * @param $field String: name of the column
- * @param $fname String: function name for logging and profiling
- */
- public function fieldExists( $table, $field, $fname = 'DatabaseIbm_db2::fieldExists' ) {
- $table = $this->tableName( $table );
- $schema = $this->mSchema;
- $etable = preg_replace("/'/", "''", $table);
- $eschema = preg_replace("/'/", "''", $schema);
- $ecol = preg_replace("/'/", "''", $field);
- $sql = <<<SQL
-SELECT 1 as fieldexists
-FROM sysibm.syscolumns sc
-WHERE sc.name='$ecol' AND sc.tbname='$etable' AND sc.tbcreator='$eschema'
-SQL;
- $res = $this->query( $sql, $fname );
- $count = $res ? $this->numRows($res) : 0;
- if ($res)
- $this->freeResult( $res );
- return $count;
- }
-
+
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
@@ -1569,22 +1401,28 @@ SQL;
* @param $fname String: function name for logging and profiling
* @return Object query row in object form
*/
- public function indexInfo( $table, $index, $fname = 'DatabaseIbm_db2::indexExists' ) {
+ public function indexInfo( $table, $index,
+ $fname = 'DatabaseIbm_db2::indexExists' )
+ {
$table = $this->tableName( $table );
$sql = <<<SQL
SELECT name as indexname
FROM sysibm.sysindexes si
-WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
+WHERE si.name='$index' AND si.tbname='$table'
+AND sc.tbcreator='$this->mSchema'
SQL;
$res = $this->query( $sql, $fname );
if ( !$res ) {
return null;
}
$row = $this->fetchObject( $res );
- if ($row != null) return $row;
- else return false;
+ if ( $row != null ) {
+ return $row;
+ } else {
+ return false;
+ }
}
-
+
/**
* Returns an information object on a table column
* @param $table String: table name
@@ -1592,9 +1430,9 @@ SQL;
* @return IBM_DB2Field
*/
public function fieldInfo( $table, $field ) {
- return IBM_DB2Field::fromText($this, $table, $field);
+ return IBM_DB2Field::fromText( $this, $table, $field );
}
-
+
/**
* db2_field_type() wrapper
* @param $res Object: result of executed statement
@@ -1607,7 +1445,7 @@ SQL;
}
return db2_field_type( $res, $index );
}
-
+
/**
* Verifies that an index was created as unique
* @param $table String: table name
@@ -1615,25 +1453,28 @@ SQL;
* @param $fname function name for profiling
* @return Bool
*/
- public function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
+ public function indexUnique ( $table, $index,
+ $fname = 'DatabaseIbm_db2::indexUnique' )
+ {
$table = $this->tableName( $table );
$sql = <<<SQL
SELECT si.name as indexname
FROM sysibm.sysindexes si
-WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
-AND si.uniquerule IN ('U', 'P')
+WHERE si.name='$index' AND si.tbname='$table'
+AND sc.tbcreator='$this->mSchema'
+AND si.uniquerule IN ( 'U', 'P' )
SQL;
$res = $this->query( $sql, $fname );
if ( !$res ) {
return null;
}
- if ($this->fetchObject( $res )) {
+ if ( $this->fetchObject( $res ) ) {
return true;
}
return false;
}
-
+
/**
* Returns the size of a text field, or -1 for "unlimited"
* @param $table String: table name
@@ -1645,15 +1486,15 @@ SQL;
$sql = <<<SQL
SELECT length as size
FROM sysibm.syscolumns sc
-WHERE sc.name='$field' AND sc.tbname='$table' AND sc.tbcreator='$this->mSchema'
+WHERE sc.name='$field' AND sc.tbname='$table'
+AND sc.tbcreator='$this->mSchema'
SQL;
- $res = $this->query($sql);
- $row = $this->fetchObject($res);
+ $res = $this->query( $sql );
+ $row = $this->fetchObject( $res );
$size = $row->size;
- $this->freeResult( $res );
return $size;
}
-
+
/**
* DELETE where the condition is a join
* @param $delTable String: deleting from this table
@@ -1663,18 +1504,26 @@ SQL;
* @param $conds Array: conditionals for join table
* @param $fname String: function name for profiling
*/
- public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseIbm_db2::deleteJoin" ) {
+ public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar,
+ $conds, $fname = "DatabaseIbm_db2::deleteJoin" )
+ {
if ( !$conds ) {
- throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this,
+ 'DatabaseIbm_db2::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
$joinTable = $this->tableName( $joinTable );
- $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
+ $sql = <<<SQL
+DELETE FROM $delTable
+WHERE $delVar IN (
+ SELECT $joinVar FROM $joinTable
+
+SQL;
if ( $conds != '*' ) {
$sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
}
- $sql .= ')';
+ $sql .= ' )';
$this->query( $sql, $fname );
}
@@ -1684,22 +1533,23 @@ SQL;
* @param $b Mixed: data to be encoded
* @return IBM_DB2Blob
*/
- public function encodeBlob($b) {
- return new IBM_DB2Blob($b);
+ public function encodeBlob( $b ) {
+ return new IBM_DB2Blob( $b );
}
-
+
/**
* Description is left as an exercise for the reader
* @param $b IBM_DB2Blob: data to be decoded
* @return mixed
*/
- public function decodeBlob($b) {
- return $b->getData();
+ public function decodeBlob( $b ) {
+ return "$b";
}
-
+
/**
* Convert into a list of string being concatenated
- * @param $stringList Array: strings that need to be joined together by the SQL engine
+ * @param $stringList Array: strings that need to be joined together
+ * by the SQL engine
* @return String: joined by the concatenation operator
*/
public function buildConcat( $stringList ) {
@@ -1707,7 +1557,7 @@ SQL;
// Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz'
return implode( ' || ', $stringList );
}
-
+
/**
* Generates the SQL required to convert a DB2 timestamp into a Unix epoch
* @param $column String: name of timestamp column
@@ -1717,11 +1567,11 @@ SQL;
// TODO
// see SpecialAncientpages
}
-
+
######################################
# Prepared statements
######################################
-
+
/**
* Intended to be compatible with the PEAR::DB wrapper functions.
* http://pear.php.net/manual/en/package.database.db.intro-execute.php
@@ -1735,7 +1585,7 @@ SQL;
* @return resource a prepared DB2 SQL statement
*/
public function prepare( $sql, $func = 'DB2::prepare' ) {
- $stmt = db2_prepare($this->mConn, $sql, $this->mStmtOptions);
+ $stmt = db2_prepare( $this->mConn, $sql, $this->mStmtOptions );
return $stmt;
}
@@ -1744,7 +1594,7 @@ SQL;
* @return Boolean success or failure
*/
public function freePrepared( $prepared ) {
- return db2_free_stmt($prepared);
+ return db2_free_stmt( $prepared );
}
/**
@@ -1759,7 +1609,10 @@ SQL;
$args = func_get_args();
array_shift( $args );
}
- $res = db2_execute($prepared, $args);
+ $res = db2_execute( $prepared, $args );
+ if ( !$res ) {
+ $this->installPrint( db2_stmt_errormsg() );
+ }
return $res;
}
@@ -1792,32 +1645,32 @@ SQL;
public function fillPrepared( $preparedQuery, $args ) {
reset( $args );
$this->preparedArgs =& $args;
-
- foreach ($args as $i => $arg) {
- db2_bind_param($preparedQuery, $i+1, $args[$i]);
+
+ foreach ( $args as $i => $arg ) {
+ db2_bind_param( $preparedQuery, $i+1, $args[$i] );
}
-
+
return $preparedQuery;
}
-
+
/**
* Switches module between regular and install modes
*/
- public function setMode($mode) {
- $old = $this->mMode;
+ public function setMode( $mode ) {
+ $old = $this->mMode;
$this->mMode = $mode;
return $old;
}
-
+
/**
* Bitwise negation of a column or value in SQL
* Same as (~field) in C
* @param $field String
* @return String
*/
- function bitNot($field) {
- //expecting bit-fields smaller than 4bytes
- return 'BITNOT('.$bitField.')';
+ function bitNot( $field ) {
+ // expecting bit-fields smaller than 4bytes
+ return "BITNOT( $field )";
}
/**
@@ -1827,8 +1680,8 @@ SQL;
* @param $fieldRight String
* @return String
*/
- function bitAnd($fieldLeft, $fieldRight) {
- return 'BITAND('.$fieldLeft.', '.$fieldRight.')';
+ function bitAnd( $fieldLeft, $fieldRight ) {
+ return "BITAND( $fieldLeft, $fieldRight )";
}
/**
@@ -1838,7 +1691,17 @@ SQL;
* @param $fieldRight String
* @return String
*/
- function bitOr($fieldLeft, $fieldRight) {
- return 'BITOR('.$fieldLeft.', '.$fieldRight.')';
+ function bitOr( $fieldLeft, $fieldRight ) {
+ return "BITOR( $fieldLeft, $fieldRight )";
+ }
+}
+
+class IBM_DB2Helper {
+ public static function makeArray( $maybeArray ) {
+ if ( !is_array( $maybeArray ) ) {
+ return array( $maybeArray );
+ }
+
+ return $maybeArray;
}
}
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 6b1206b0..41ba2d08 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -1,968 +1,1226 @@
<?php
/**
- * This script is the MSSQL Server database abstraction layer
+ * This is the MS SQL Server Native database abstraction layer.
*
- * See maintenance/mssql/README for development notes and other specific information
- * @ingroup Database
* @file
+ * @ingroup Database
+ * @author Joel Penner <a-joelpe at microsoft dot com>
+ * @author Chris Pucci <a-cpucci at microsoft dot com>
+ * @author Ryan Biesemeyer <v-ryanbi at microsoft dot com>
*/
/**
* @ingroup Database
*/
class DatabaseMssql extends DatabaseBase {
+ var $mInsertId = NULL;
+ var $mLastResult = NULL;
+ var $mAffectedRows = NULL;
- var $mAffectedRows;
- var $mLastResult;
- var $mLastError;
- var $mLastErrorNo;
- var $mDatabaseFile;
-
- /**
- * Constructor
- */
- function __construct($server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0, $tablePrefix = 'get from global') {
-
- global $wgOut, $wgDBprefix, $wgCommandLineMode;
- if (!isset($wgOut)) $wgOut = null; # Can't get a reference if it hasn't been set yet
- $this->mOut =& $wgOut;
- $this->mFailFunction = $failFunction;
- $this->mFlags = $flags;
-
- if ( $this->mFlags & DBO_DEFAULT ) {
- if ( $wgCommandLineMode ) {
- $this->mFlags &= ~DBO_TRX;
- } else {
- $this->mFlags |= DBO_TRX;
- }
- }
-
- /** Get the default table prefix*/
- $this->mTablePrefix = $tablePrefix == 'get from global' ? $wgDBprefix : $tablePrefix;
-
- if ($server) $this->open($server, $user, $password, $dbName);
-
+ function cascadingDeletes() {
+ return true;
}
-
- function getType() {
- return 'mssql';
+ function cleanupTriggers() {
+ return true;
+ }
+ function strictIPs() {
+ return true;
+ }
+ function realTimestamps() {
+ return true;
+ }
+ function implicitGroupby() {
+ return false;
+ }
+ function implicitOrderby() {
+ return false;
+ }
+ function functionalIndexes() {
+ return true;
+ }
+ function unionSupportsOrderAndLimit() {
+ return false;
}
- /**
- * todo: check if these should be true like parent class
- */
- function implicitGroupby() { return false; }
- function implicitOrderby() { return false; }
-
- static function newFromParams($server, $user, $password, $dbName, $failFunction = false, $flags = 0) {
- return new DatabaseMssql($server, $user, $password, $dbName, $failFunction, $flags);
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
+ return new DatabaseMssql( $server, $user, $password, $dbName, $flags );
}
- /** Open an MSSQL database and return a resource handle to it
- * NOTE: only $dbName is used, the other parameters are irrelevant for MSSQL databases
+ /**
+ * Usually aborts on failure
*/
- function open($server,$user,$password,$dbName) {
- wfProfileIn(__METHOD__);
-
- # Test for missing mysql.so
- # First try to load it
- if (!@extension_loaded('mssql')) {
- @dl('mssql.so');
+ function open( $server, $user, $password, $dbName ) {
+ # Test for driver support, to avoid suppressed fatal error
+ if ( !function_exists( 'sqlsrv_connect' ) ) {
+ throw new DBConnectionError( $this, "MS Sql Server Native (sqlsrv) functions missing. You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n" );
}
- # Fail now
- # Otherwise we get a suppressed fatal error, which is very hard to track down
- if (!function_exists( 'mssql_connect')) {
- throw new DBConnectionError( $this, "MSSQL functions missing, have you compiled PHP with the --with-mssql option?\n" );
+ global $wgDBport;
+
+ if ( !strlen( $user ) ) { # e.g. the class is being loaded
+ return;
}
$this->close();
- $this->mServer = $server;
- $this->mUser = $user;
+ $this->mServer = $server;
+ $this->mPort = $wgDBport;
+ $this->mUser = $user;
$this->mPassword = $password;
- $this->mDBname = $dbName;
-
- wfProfileIn("dbconnect-$server");
-
- # Try to connect up to three times
- # The kernel's default SYN retransmission period is far too slow for us,
- # so we use a short timeout plus a manual retry.
- $this->mConn = false;
- $max = 3;
- for ( $i = 0; $i < $max && !$this->mConn; $i++ ) {
- if ( $i > 1 ) {
- usleep( 1000 );
- }
- if ($this->mFlags & DBO_PERSISTENT) {
- @/**/$this->mConn = mssql_pconnect($server, $user, $password);
- } else {
- # Create a new connection...
- @/**/$this->mConn = mssql_connect($server, $user, $password, true);
- }
+ $this->mDBname = $dbName;
+
+ $connectionInfo = array();
+
+ if( $dbName ) {
+ $connectionInfo['Database'] = $dbName;
}
-
- wfProfileOut("dbconnect-$server");
-
- if ($dbName != '') {
- if ($this->mConn !== false) {
- $success = @/**/mssql_select_db($dbName, $this->mConn);
- if (!$success) {
- $error = "Error selecting database $dbName on server {$this->mServer} " .
- "from client host " . wfHostname() . "\n";
- wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n");
- wfDebug( $error );
- }
- } else {
- wfDebug("DB connection error\n");
- wfDebug("Server: $server, User: $user, Password: ".substr($password, 0, 3)."...\n");
- $success = false;
- }
+
+ // Start NT Auth Hack
+ // Quick and dirty work around to provide NT Auth designation support.
+ // Current solution requires installer to know to input 'ntauth' for both username and password
+ // to trigger connection via NT Auth. - ugly, ugly, ugly
+ // TO-DO: Make this better and add NT Auth choice to MW installer when SQL Server option is chosen.
+ $ntAuthUserTest = strtolower( $user );
+ $ntAuthPassTest = strtolower( $password );
+
+ // Decide which auth scenerio to use
+ if( ( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ) ){
+ // Don't add credentials to $connectionInfo
} else {
- # Delay USE query
- $success = (bool)$this->mConn;
+ $connectionInfo['UID'] = $user;
+ $connectionInfo['PWD'] = $password;
+ }
+ // End NT Auth Hack
+
+ $this->mConn = @sqlsrv_connect( $server, $connectionInfo );
+
+ if ( $this->mConn === false ) {
+ wfDebug( "DB connection error\n" );
+ wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
+ wfDebug( $this->lastError() . "\n" );
+ return false;
}
- if (!$success) $this->reportConnectionError();
- $this->mOpened = $success;
- wfProfileOut(__METHOD__);
- return $success;
+ $this->mOpened = true;
+ return $this->mConn;
}
/**
- * Close an MSSQL database
+ * Closes a database connection, if it is open
+ * Returns success, true if already closed
*/
function close() {
$this->mOpened = false;
- if ($this->mConn) {
- if ($this->trxLevel()) $this->commit();
- return mssql_close($this->mConn);
- } else return true;
+ if ( $this->mConn ) {
+ return sqlsrv_close( $this->mConn );
+ } else {
+ return true;
+ }
}
- /**
- * - MSSQL doesn't seem to do buffered results
- * - the trasnaction syntax is modified here to avoid having to replicate
- * Database::query which uses BEGIN, COMMIT, ROLLBACK
- */
- function doQuery($sql) {
- if ($sql == 'BEGIN' || $sql == 'COMMIT' || $sql == 'ROLLBACK') return true; # $sql .= ' TRANSACTION';
- $sql = preg_replace('|[^\x07-\x7e]|','?',$sql); # TODO: need to fix unicode - just removing it here while testing
- $ret = mssql_query($sql, $this->mConn);
- if ($ret === false) {
- $err = mssql_get_last_message();
- if ($err) $this->mlastError = $err;
- $row = mssql_fetch_row(mssql_query('select @@ERROR'));
- if ($row[0]) $this->mlastErrorNo = $row[0];
- } else $this->mlastErrorNo = false;
- return $ret;
+ function doQuery( $sql ) {
+ wfDebug( "SQL: [$sql]\n" );
+ $this->offset = 0;
+
+ // several extensions seem to think that all databases support limits via LIMIT N after the WHERE clause
+ // well, MSSQL uses SELECT TOP N, so to catch any of those extensions we'll do a quick check for a LIMIT
+ // clause and pass $sql through $this->LimitToTopN() which parses the limit clause and passes the result to
+ // $this->limitResult();
+ if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
+ // massage LIMIT -> TopN
+ $sql = $this->LimitToTopN( $sql ) ;
+ }
+
+ // MSSQL doesn't have EXTRACT(epoch FROM XXX)
+ if ( preg_match('#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
+ // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
+ $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
+ }
+
+ // perform query
+ $stmt = sqlsrv_query( $this->mConn, $sql );
+ if ( $stmt == false ) {
+ $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
+ "Query: " . htmlentities( $sql ) . "\n" .
+ "Function: " . __METHOD__ . "\n";
+ // process each error (our driver will give us an array of errors unlike other providers)
+ foreach ( sqlsrv_errors() as $error ) {
+ $message .= $message . "ERROR[" . $error['code'] . "] " . $error['message'] . "\n";
+ }
+
+ throw new DBUnexpectedError( $this, $message );
+ }
+ // remember number of rows affected
+ $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
+
+ // if it is a SELECT statement, or an insert with a request to output something we want to return a row.
+ if ( ( preg_match( '#\bSELECT\s#i', $sql ) ) ||
+ ( preg_match( '#\bINSERT\s#i', $sql ) && preg_match( '#\bOUTPUT\s+INSERTED\b#i', $sql ) ) ) {
+ // this is essentially a rowset, but Mediawiki calls these 'result'
+ // the rowset owns freeing the statement
+ $res = new MssqlResult( $stmt );
+ } else {
+ // otherwise we simply return it was successful, failure throws an exception
+ $res = true;
+ }
+ return $res;
}
- /**
- * Free a result object
- */
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- if ( !@/**/mssql_free_result( $res ) ) {
- throw new DBUnexpectedError( $this, "Unable to free MSSQL result" );
- }
+ $res->free();
}
- /**
- * 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.
- *
- * @param $res SQL result object as returned from Database::query(), etc.
- * @return MySQL row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @/**/$row = mssql_fetch_object( $res );
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
- }
+ $row = $res->fetch( 'OBJECT' );
return $row;
}
- /**
- * Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
- *
- * @param $res SQL result object as returned from Database::query(), etc.
- * @return MySQL row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchRow( $res ) {
+ function getErrors() {
+ $strRet = '';
+ $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
+ if ( $retErrors != null ) {
+ foreach ( $retErrors as $arrError ) {
+ $strRet .= "SQLState: " . $arrError[ 'SQLSTATE'] . "\n";
+ $strRet .= "Error Code: " . $arrError[ 'code'] . "\n";
+ $strRet .= "Message: " . $arrError[ 'message'] . "\n";
+ }
+ } else {
+ $strRet = "No errors found";
+ }
+ return $strRet;
+ }
+
+ function fetchRow( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @/**/$row = mssql_fetch_array( $res );
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
- }
+ $row = $res->fetch( SQLSRV_FETCH_BOTH );
return $row;
}
- /**
- * Get the number of rows in a result object
- */
function numRows( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @/**/$n = mssql_num_rows( $res );
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $n;
+ return ( $res ) ? $res->numrows() : 0;
}
- /**
- * Get the number of fields in a result object
- * See documentation for mysql_num_fields()
- * @param $res SQL result object as returned from Database::query(), etc.
- */
function numFields( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return mssql_num_fields( $res );
+ return ( $res ) ? $res->numfields() : 0;
}
- /**
- * Get a field name in a result object
- * See documentation for mysql_field_name():
- * http://www.php.net/mysql_field_name
- * @param $res SQL result object as returned from Database::query(), etc.
- * @param $n Int
- */
function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return mssql_field_name( $res, $n );
+ return ( $res ) ? $res->fieldname( $n ) : 0;
}
/**
- * 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();
+ * This must be called after nextSequenceVal
*/
function insertId() {
- $row = mssql_fetch_row(mssql_query('select @@IDENTITY'));
- return $row[0];
+ return $this->mInsertId;
}
- /**
- * Change the position of the cursor in a result object
- * See mysql_data_seek()
- * @param $res SQL result object as returned from Database::query(), etc.
- * @param $row Database row
- */
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return mssql_data_seek( $res, $row );
+ return ( $res ) ? $res->seek( $row ) : false;
}
- /**
- * Get the last error number
- */
- function lastErrno() {
- return $this->mlastErrorNo;
+ function lastError() {
+ if ( $this->mConn ) {
+ return $this->getErrors();
+ }
+ else {
+ return "No database connection";
+ }
}
- /**
- * Get a description of the last error
- */
- function lastError() {
- return $this->mlastError;
+ function lastErrno() {
+ $err = sqlsrv_errors( SQLSRV_ERR_ALL );
+ if ( $err[0] ) return $err[0]['code'];
+ else return 0;
}
- /**
- * Get the number of rows affected by the last write query
- */
function affectedRows() {
- return mssql_rows_affected( $this->mConn );
+ return $this->mAffectedRows;
}
/**
- * Simple UPDATE wrapper
- * Usually aborts on failure
- * If errors are explicitly ignored, returns success
+ * SELECT wrapper
*
- * This function exists for historical reasons, Database::update() has a more standard
- * calling convention and feature set
+ * @param $table Mixed: array or string, table name(s) (prefix auto-added)
+ * @param $vars Mixed: array or string, field name(s) to be retrieved
+ * @param $conds Mixed: array or string, condition(s) for WHERE
+ * @param $fname String: calling function name (use __METHOD__) for logs/profiling
+ * @param $options Array: associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of supported stuff
+ * @param $join_conds Array: Associative array of table join conditions (optional)
+ * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * @return Mixed: database result resource (feed to Database::fetchObject or whatever), or false on failure
*/
- function set( $table, $var, $value, $cond, $fname = 'Database::set' )
+ function select( $table, $vars, $conds = '', $fname = 'DatabaseMssql::select', $options = array(), $join_conds = array() )
{
- if ($value == "NULL") $value = "''"; # see comments in makeListWithoutNulls()
- $table = $this->tableName( $table );
- $sql = "UPDATE $table SET $var = '" .
- $this->strencode( $value ) . "' WHERE ($cond)";
- return (bool)$this->query( $sql, $fname );
- }
-
- /**
- * Simple SELECT wrapper, returns a single field, input must be encoded
- * Usually aborts on failure
- * If errors are explicitly ignored, returns FALSE on failure
- */
- function selectField( $table, $var, $cond='', $fname = 'Database::selectField', $options = array() ) {
- if ( !is_array( $options ) ) {
- $options = array( $options );
- }
- $options['LIMIT'] = 1;
-
- $res = $this->select( $table, $var, $cond, $fname, $options );
- if ( $res === false || !$this->numRows( $res ) ) {
- return false;
- }
- $row = $this->fetchRow( $res );
- if ( $row !== false ) {
- $this->freeResult( $res );
- return $row[0];
- } else {
- return false;
- }
- }
-
- /**
- * Returns an optional USE INDEX clause to go after the table, and a
- * string to go at the end of the query
- *
- * @private
- *
- * @param $options Array: an associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return array
- */
- function makeSelectOptions( $options ) {
- $preLimitTail = $postLimitTail = '';
- $startOpts = '';
-
- $noKeyOptions = array();
- foreach ( $options as $key => $option ) {
- if ( is_numeric( $key ) ) {
- $noKeyOptions[$option] = true;
- }
- }
-
- if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
- if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
-
- //if (isset($options['LIMIT'])) {
- // $tailOpts .= $this->limitResult('', $options['LIMIT'],
- // isset($options['OFFSET']) ? $options['OFFSET']
- // : false);
- //}
-
- if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
- if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
-
- # Various MySQL extensions
- if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */';
- if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY';
- if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT';
- if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT';
- if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT';
- if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS';
- if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE';
- if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE';
-
- if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
- $useIndex = $this->useIndexClause( $options['USE INDEX'] );
- } else {
- $useIndex = '';
+ $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+ if ( isset( $options['EXPLAIN'] ) ) {
+ sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL ON;" );
+ $ret = $this->query( $sql, $fname );
+ sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL OFF;" );
+ return $ret;
}
-
- return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
+ return $this->query( $sql, $fname );
}
/**
* SELECT wrapper
*
- * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
- * @param $vars Mixed: Array or string, field name(s) to be retrieved
- * @param $conds Mixed: Array or string, condition(s) for WHERE
+ * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
+ * @param $vars Mixed: Array or string, field name(s) to be retrieved
+ * @param $conds Mixed: Array or string, condition(s) for WHERE
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
- * @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
- * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
+ * @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of supported stuff
+ * @param $join_conds Array: Associative array of table join conditions (optional)
+ * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * @return string, the SQL text
*/
- function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array() )
- {
- if( is_array( $vars ) ) {
- $vars = implode( ',', $vars );
- }
- if( !is_array( $options ) ) {
- $options = array( $options );
- }
- if( is_array( $table ) ) {
- if ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
- $from = ' FROM ' . $this->tableNamesWithUseIndex( $table, $options['USE INDEX'] );
- else
- $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
- } elseif ($table!='') {
- if ($table{0}==' ') {
- $from = ' FROM ' . $table;
- } else {
- $from = ' FROM ' . $this->tableName( $table );
- }
- } else {
- $from = '';
- }
-
- list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
-
- if( !empty( $conds ) ) {
- if ( is_array( $conds ) ) {
- $conds = $this->makeList( $conds, LIST_AND );
- }
- $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
- } else {
- $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
- }
-
- if (isset($options['LIMIT']))
- $sql = $this->limitResult($sql, $options['LIMIT'],
- isset($options['OFFSET']) ? $options['OFFSET'] : false);
- $sql = "$sql $postLimitTail";
-
- if (isset($options['EXPLAIN'])) {
- $sql = 'EXPLAIN ' . $sql;
+ function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseMssql::select', $options = array(), $join_conds = array() ) {
+ if ( isset( $options['EXPLAIN'] ) ) {
+ unset( $options['EXPLAIN'] );
}
- return $this->query( $sql, $fname );
+ return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
}
/**
- * Determines whether a field exists in a table
- * Usually aborts on failure
- * If errors are explicitly ignored, returns NULL on failure
+ * Estimate rows in dataset
+ * Returns estimated count, based on SHOWPLAN_ALL output
+ * This is not necessarily an accurate estimate, so use sparingly
+ * Returns -1 if count cannot be found
+ * Takes same arguments as Database::select()
*/
- function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) {
- $table = $this->tableName( $table );
- $sql = "SELECT TOP 1 * FROM $table";
- $res = $this->query( $sql, 'Database::fieldExists' );
-
- $found = false;
- while ( $row = $this->fetchArray( $res ) ) {
- if ( isset($row[$field]) ) {
- $found = true;
- break;
- }
+ function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseMssql::estimateRowCount', $options = array() ) {
+ $options['EXPLAIN'] = true;// http://msdn2.microsoft.com/en-us/library/aa259203.aspx
+ $res = $this->select( $table, $vars, $conds, $fname, $options );
+
+ $rows = -1;
+ if ( $res ) {
+ $row = $this->fetchRow( $res );
+ if ( isset( $row['EstimateRows'] ) ) $rows = $row['EstimateRows'];
}
-
- $this->freeResult( $res );
- return $found;
+ return $rows;
}
+
/**
- * Get information about an index into an object
- * Returns false if the index does not exist
+ * Returns information about an index
+ * If errors are explicitly ignored, returns NULL on failure
*/
- function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
-
- throw new DBUnexpectedError( $this, 'Database::indexInfo called which is not supported yet' );
- return null;
-
- $table = $this->tableName( $table );
- $sql = 'SHOW INDEX FROM '.$table;
+ function indexInfo( $table, $index, $fname = 'DatabaseMssql::indexExists' ) {
+ # This does not return the same info as MYSQL would, but that's OK because MediaWiki never uses the
+ # returned value except to check for the existance of indexes.
+ $sql = "sp_helpindex '" . $table . "'";
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return null;
+ return NULL;
}
$result = array();
- while ( $row = $this->fetchObject( $res ) ) {
- if ( $row->Key_name == $index ) {
- $result[] = $row;
- }
- }
- $this->freeResult($res);
-
- return empty($result) ? false : $result;
- }
-
- /**
- * Query whether a given table exists
- */
- function tableExists( $table ) {
- $table = $this->tableName( $table );
- $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '$table'" );
- $exist = ($res->numRows() > 0);
- $this->freeResult($res);
- return $exist;
- }
-
- /**
- * mysql_fetch_field() wrapper
- * Returns false if the field doesn't exist
- *
- * @param $table
- * @param $field
- */
- function fieldInfo( $table, $field ) {
- $table = $this->tableName( $table );
- $res = $this->query( "SELECT TOP 1 * FROM $table" );
- $n = mssql_num_fields( $res->result );
- for( $i = 0; $i < $n; $i++ ) {
- $meta = mssql_fetch_field( $res->result, $i );
- if( $field == $meta->name ) {
- return new MSSQLField($meta);
+ foreach ( $res as $row ) {
+ if ( $row->index_name == $index ) {
+ $row->Non_unique = !stristr( $row->index_description, "unique" );
+ $cols = explode( ", ", $row->index_keys );
+ foreach ( $cols as $col ) {
+ $row->Column_name = trim( $col );
+ $result[] = clone $row;
+ }
+ } else if ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
+ $row->Non_unique = 0;
+ $cols = explode( ", ", $row->index_keys );
+ foreach ( $cols as $col ) {
+ $row->Column_name = trim( $col );
+ $result[] = clone $row;
+ }
}
}
- return false;
- }
-
- /**
- * mysql_field_type() wrapper
- */
- function fieldType( $res, $index ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mssql_field_type( $res, $index );
+ return empty( $result ) ? false : $result;
}
/**
* INSERT wrapper, inserts an array into a table
*
- * $a may be a single associative array, or an array of these with numeric keys, for
+ * $arrToInsert may be a single associative array, or an array of these with numeric keys, for
* multi-row insert.
*
* Usually aborts on failure
* If errors are explicitly ignored, returns success
- *
- * Same as parent class implementation except that it removes primary key from column lists
- * because MSSQL doesn't support writing nulls to IDENTITY (AUTO_INCREMENT) columns
*/
- function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
+ function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) {
# No rows to insert, easy just return now
- if ( !count( $a ) ) {
+ if ( !count( $arrToInsert ) ) {
return true;
}
- $table = $this->tableName( $table );
+
if ( !is_array( $options ) ) {
$options = array( $options );
}
-
- # todo: need to record primary keys at table create time, and remove NULL assignments to them
- if ( isset( $a[0] ) && is_array( $a[0] ) ) {
- $multi = true;
- $keys = array_keys( $a[0] );
-# if (ereg('_id$',$keys[0])) {
- foreach ($a as $i) {
- if (is_null($i[$keys[0]])) unset($i[$keys[0]]); # remove primary-key column from multiple insert lists if empty value
- }
-# }
- $keys = array_keys( $a[0] );
- } else {
- $multi = false;
- $keys = array_keys( $a );
-# if (ereg('_id$',$keys[0]) && empty($a[$keys[0]])) unset($a[$keys[0]]); # remove primary-key column from insert list if empty value
- if (is_null($a[$keys[0]])) unset($a[$keys[0]]); # remove primary-key column from insert list if empty value
- $keys = array_keys( $a );
+
+ $table = $this->tableName( $table );
+
+ if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) {// Not multi row
+ $arrToInsert = array( 0 => $arrToInsert );// make everything multi row compatible
}
- # handle IGNORE option
- # example:
- # MySQL: INSERT IGNORE INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
- # MSSQL: IF NOT EXISTS (SELECT * FROM user_groups WHERE ug_user = '1') INSERT INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
- $ignore = in_array('IGNORE',$options);
-
- # remove IGNORE from options list
- if ($ignore) {
- $oldoptions = $options;
- $options = array();
- foreach ($oldoptions as $o) if ($o != 'IGNORE') $options[] = $o;
- }
-
- $keylist = implode(',', $keys);
- $sql = 'INSERT '.implode(' ', $options)." INTO $table (".implode(',', $keys).') VALUES ';
- if ($multi) {
- if ($ignore) {
- # If multiple and ignore, then do each row as a separate conditional insert
- foreach ($a as $row) {
- $prival = $row[$keys[0]];
- $sql = "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival') $sql";
- if (!$this->query("$sql (".$this->makeListWithoutNulls($row).')', $fname)) return false;
+ $allOk = true;
+
+
+ // We know the table we're inserting into, get its identity column
+ $identity = null;
+ $tableRaw = preg_replace( '#\[([^\]]*)\]#', '$1', $table ); // strip matching square brackets from table name
+ $res = $this->doQuery( "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'" );
+ if( $res && $res->numrows() ){
+ // There is an identity for this table.
+ $identity = array_pop( $res->fetch( SQLSRV_FETCH_ASSOC ) );
+ }
+ unset( $res );
+
+ foreach ( $arrToInsert as $a ) {
+ // start out with empty identity column, this is so we can return it as a result of the insert logic
+ $sqlPre = '';
+ $sqlPost = '';
+ $identityClause = '';
+
+ // if we have an identity column
+ if( $identity ) {
+ // iterate through
+ foreach ($a as $k => $v ) {
+ if ( $k == $identity ) {
+ if( !is_null($v) ){
+ // there is a value being passed to us, we need to turn on and off inserted identity
+ $sqlPre = "SET IDENTITY_INSERT $table ON;" ;
+ $sqlPost = ";SET IDENTITY_INSERT $table OFF;";
+
+ } else {
+ // we can't insert NULL into an identity column, so remove the column from the insert.
+ unset( $a[$k] );
+ }
+ }
}
- return true;
- } else {
- $first = true;
- foreach ($a as $row) {
- if ($first) $first = false; else $sql .= ',';
- $sql .= '('.$this->makeListWithoutNulls($row).')';
+ $identityClause = "OUTPUT INSERTED.$identity "; // we want to output an identity column as result
+ }
+
+ $keys = array_keys( $a );
+
+
+ // INSERT IGNORE is not supported by SQL Server
+ // remove IGNORE from options list and set ignore flag to true
+ $ignoreClause = false;
+ foreach ( $options as $k => $v ) {
+ if ( strtoupper( $v ) == "IGNORE" ) {
+ unset( $options[$k] );
+ $ignoreClause = true;
}
}
- } else {
- if ($ignore) {
+
+ // translate MySQL INSERT IGNORE to something SQL Server can use
+ // example:
+ // MySQL: INSERT IGNORE INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
+ // MSSQL: IF NOT EXISTS (SELECT * FROM user_groups WHERE ug_user = '1') INSERT INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
+ if ( $ignoreClause == true ) {
$prival = $a[$keys[0]];
- $sql = "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival') $sql";
+ $sqlPre .= "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival')";
+ }
+
+ // Build the actual query
+ $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
+ " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
+
+ $first = true;
+ foreach ( $a as $value ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $sql .= ',';
+ }
+ if ( is_string( $value ) ) {
+ $sql .= $this->addIdentifierQuotes( $value );
+ } elseif ( is_null( $value ) ) {
+ $sql .= 'null';
+ } elseif ( is_array( $value ) || is_object( $value ) ) {
+ if ( is_object( $value ) && strtolower( get_class( $value ) ) == 'blob' ) {
+ $sql .= $this->addIdentifierQuotes( $value->fetch() );
+ } else {
+ $sql .= $this->addIdentifierQuotes( serialize( $value ) );
+ }
+ } else {
+ $sql .= $value;
+ }
}
- $sql .= '('.$this->makeListWithoutNulls($a).')';
+ $sql .= ')' . $sqlPost;
+
+ // Run the query
+ $ret = sqlsrv_query( $this->mConn, $sql );
+
+ if ( $ret === false ) {
+ throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname );
+ } elseif ( $ret != NULL ) {
+ // remember number of rows affected
+ $this->mAffectedRows = sqlsrv_rows_affected( $ret );
+ if ( !is_null($identity) ) {
+ // then we want to get the identity column value we were assigned and save it off
+ $row = sqlsrv_fetch_object( $ret );
+ $this->mInsertId = $row->$identity;
+ }
+ sqlsrv_free_stmt( $ret );
+ continue;
+ }
+ $allOk = false;
}
- return (bool)$this->query( $sql, $fname );
+ return $allOk;
}
/**
- * MSSQL doesn't allow implicit casting of NULL's into non-null values for NOT NULL columns
- * for now I've just converted the NULL's in the lists for updates and inserts into empty strings
- * which get implicitly casted to 0 for numeric columns
- * NOTE: the set() method above converts NULL to empty string as well but not via this method
+ * INSERT SELECT wrapper
+ * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
+ * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
+ * $conds may be "*" to copy the whole table
+ * srcTable may be an array of tables.
*/
- function makeListWithoutNulls($a, $mode = LIST_COMMA) {
- return str_replace("NULL","''",$this->makeList($a,$mode));
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect',
+ $insertOptions = array(), $selectOptions = array() )
+ {
+ $ret = parent::insertSelect( $destTable, $srcTable, $varMap, $conds, $fname, $insertOptions, $selectOptions );
+
+ if ( $ret === false ) {
+ throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname );
+ } elseif ( $ret != NULL ) {
+ // remember number of rows affected
+ $this->mAffectedRows = sqlsrv_rows_affected( $ret );
+ return $ret;
+ }
+ return NULL;
}
/**
- * UPDATE wrapper, takes a condition array and a SET array
+ * Format a table name ready for use in constructing an SQL query
+ *
+ * This does two important things: it brackets table names which as necessary,
+ * and it adds a table prefix if there is one.
*
- * @param $table String: The table to UPDATE
- * @param $values Array: An array of values to SET
- * @param $conds Array: An array of conditions (WHERE). Use '*' to update all rows.
- * @param $fname String: The Class::Function calling this function
- * (for the log)
- * @param $options Array: An array of UPDATE options, can be one or
- * more of IGNORE, LOW_PRIORITY
- * @return bool
+ * All functions of this object which require a table name call this function
+ * themselves. Pass the canonical name to such functions. This is only needed
+ * when calling query() directly.
+ *
+ * @param $name String: database table name
*/
- function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
- $table = $this->tableName( $table );
- $opts = $this->makeUpdateOptions( $options );
- $sql = "UPDATE $opts $table SET " . $this->makeListWithoutNulls( $values, LIST_SET );
- if ( $conds != '*' ) {
- $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
+ function tableName( $name ) {
+ global $wgSharedDB;
+ # Skip quoted literals
+ if ( $name != '' && $name { 0 } != '[' ) {
+ if ( $this->mTablePrefix !== '' && strpos( '.', $name ) === false ) {
+ $name = "{$this->mTablePrefix}$name";
+ }
+ if ( isset( $wgSharedDB ) && "{$this->mTablePrefix}user" == $name ) {
+ $name = "[$wgSharedDB].[$name]";
+ } else {
+ # Standard quoting
+ if ( $name != '' ) $name = "[$name]";
+ }
}
- return $this->query( $sql, $fname );
+ return $name;
}
/**
- * Make UPDATE options for the Database::update function
- *
- * @private
- * @param $options Array: The options passed to Database::update
- * @return string
+ * Return the next in a sequence, save the value for retrieval via insertId()
*/
- function makeUpdateOptions( $options ) {
- if( !is_array( $options ) ) {
- $options = array( $options );
+ function nextSequenceValue( $seqName ) {
+ if ( !$this->tableExists( 'sequence_' . $seqName ) ) {
+ sqlsrv_query( $this->mConn, "CREATE TABLE [sequence_$seqName] (id INT NOT NULL IDENTITY PRIMARY KEY, junk varchar(10) NULL)" );
}
- $opts = array();
- if ( in_array( 'LOW_PRIORITY', $options ) )
- $opts[] = $this->lowPriorityOption();
- if ( in_array( 'IGNORE', $options ) )
- $opts[] = 'IGNORE';
- return implode(' ', $opts);
- }
+ sqlsrv_query( $this->mConn, "INSERT INTO [sequence_$seqName] (junk) VALUES ('')" );
+ $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" );
+ $row = sqlsrv_fetch_array( $ret, SQLSRV_FETCH_ASSOC );// KEEP ASSOC THERE, weird weird bug dealing with the return value if you don't
- /**
- * Change the current database
- */
- function selectDB( $db ) {
- $this->mDBname = $db;
- return mssql_select_db( $db, $this->mConn );
+ sqlsrv_free_stmt( $ret );
+ $this->mInsertId = $row['id'];
+ return $row['id'];
}
/**
- * MSSQL has a problem with the backtick quoting, so all this does is ensure the prefix is added exactly once
+ * Return the current value of a sequence. Assumes it has ben nextval'ed in this session.
*/
- function tableName($name) {
- return strpos($name, $this->mTablePrefix) === 0 ? $name : "{$this->mTablePrefix}$name";
+ function currentSequenceValue( $seqName ) {
+ $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" );
+ if ( $ret !== false ) {
+ $row = sqlsrv_fetch_array( $ret );
+ sqlsrv_free_stmt( $ret );
+ return $row['id'];
+ } else {
+ return $this->nextSequenceValue( $seqName );
+ }
}
- /**
- * MSSQL doubles quotes instead of escaping them
- * @param $s String to be slashed.
- * @return string slashed string.
- */
- function strencode($s) {
- return str_replace("'","''",$s);
- }
- /**
- * REPLACE query wrapper
- * PostgreSQL simulates this with a DELETE followed by INSERT
- * $row is the row to insert, an associative array
- * $uniqueIndexes is an array of indexes. Each element may be either a
- * field name or an array of field names
- *
- * It may be more efficient to leave off unique indexes which are unlikely to collide.
- * However if you do this, you run the risk of encountering errors which wouldn't have
- * occurred in MySQL
- *
- * @todo migrate comment to phodocumentor format
- */
- function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
+ # REPLACE query wrapper
+ # MSSQL simulates this with a DELETE followed by INSERT
+ # $row is the row to insert, an associative array
+ # $uniqueIndexes is an array of indexes. Each element may be either a
+ # field name or an array of field names
+ #
+ # It may be more efficient to leave off unique indexes which are unlikely to collide.
+ # However if you do this, you run the risk of encountering errors which wouldn't have
+ # occurred in MySQL
+ function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMssql::replace' ) {
$table = $this->tableName( $table );
+ if ( count( $rows ) == 0 ) {
+ return;
+ }
+
# Single row case
if ( !is_array( reset( $rows ) ) ) {
$rows = array( $rows );
}
- $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) .') VALUES ';
- $first = true;
foreach ( $rows as $row ) {
- if ( $first ) {
- $first = false;
- } else {
- $sql .= ',';
+ # Delete rows which collide
+ if ( $uniqueIndexes ) {
+ $sql = "DELETE FROM $table WHERE ";
+ $first = true;
+ foreach ( $uniqueIndexes as $index ) {
+ if ( $first ) {
+ $first = false;
+ $sql .= "(";
+ } else {
+ $sql .= ') OR (';
+ }
+ if ( is_array( $index ) ) {
+ $first2 = true;
+ foreach ( $index as $col ) {
+ if ( $first2 ) {
+ $first2 = false;
+ } else {
+ $sql .= ' AND ';
+ }
+ $sql .= $col . '=' . $this->addQuotes( $row[$col] );
+ }
+ } else {
+ $sql .= $index . '=' . $this->addQuotes( $row[$index] );
+ }
+ }
+ $sql .= ')';
+ $this->query( $sql, $fname );
}
- $sql .= '(' . $this->makeList( $row ) . ')';
+
+ # Now insert the row
+ $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) . ') VALUES (' .
+ $this->makeList( $row, LIST_COMMA ) . ')';
+ $this->query( $sql, $fname );
}
- return $this->query( $sql, $fname );
}
- /**
- * DELETE where the condition is a join
- * MySQL does this with a multi-table DELETE syntax, PostgreSQL does it with sub-selects
- *
- * For safety, an empty $conds will not delete everything. If you want to delete all rows where the
- * join condition matches, set $conds='*'
- *
- * DO NOT put the join condition in $conds
- *
- * @param $delTable String: The table to delete from.
- * @param $joinTable String: The other table.
- * @param $delVar String: The variable to join on, in the first table.
- * @param $joinVar String: The variable to join on, in the second table.
- * @param $conds Array: Condition array of field names mapped to variables, ANDed together in the WHERE clause
- * @param $fname String: Calling function name
- */
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) {
+ # DELETE where the condition is a join
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseMssql::deleteJoin" ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, 'DatabaseMssql::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
$joinTable = $this->tableName( $joinTable );
- $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
+ $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
if ( $conds != '*' ) {
- $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
+ $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
}
+ $sql .= ')';
- return $this->query( $sql, $fname );
+ $this->query( $sql, $fname );
}
- /**
- * Returns the size of a text field, or -1 for "unlimited"
- */
+ # Returns the size of a text field, or -1 for "unlimited"
function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
- $sql = "SELECT TOP 1 * FROM $table;";
- $res = $this->query( $sql, 'Database::textFieldSize' );
- $row = $this->fetchObject( $res );
- $this->freeResult( $res );
-
- $m = array();
- if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
- $size = $m[1];
+ $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
+ WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
+ $res = $this->query( $sql );
+ $row = $this->fetchRow( $res );
+ $size = -1;
+ if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) $size = $row['CHARACTER_MAXIMUM_LENGTH'];
+ return $size;
+ }
+
+ /**
+ * Construct a LIMIT query with optional offset
+ * This is used for query pages
+ * $sql string SQL query we will append the limit too
+ * $limit integer the SQL limit
+ * $offset integer the SQL offset (default false)
+ */
+ function limitResult( $sql, $limit, $offset = false ) {
+ if ( $offset === false || $offset == 0 ) {
+ if ( strpos( $sql, "SELECT" ) === false ) {
+ return "TOP {$limit} " . $sql;
+ } else {
+ return preg_replace( '/\bSELECT(\s*DISTINCT)?\b/Dsi', 'SELECT$1 TOP ' . $limit, $sql, 1 );
+ }
} else {
- $size = -1;
+ $sql = '
+ SELECT * FROM (
+ SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 FROM (
+ SELECT 1 AS line2, sub1.* FROM (' . $sql . ') AS sub1
+ ) as sub2
+ ) AS sub3
+ WHERE line3 BETWEEN ' . ( $offset + 1 ) . ' AND ' . ( $offset + $limit );
+ return $sql;
}
- return $size;
+ }
+
+ // If there is a limit clause, parse it, strip it, and pass the remaining sql through limitResult()
+ // with the appropriate parameters. Not the prettiest solution, but better than building a whole new parser.
+ // This exists becase there are still too many extensions that don't use dynamic sql generation.
+ function LimitToTopN( $sql ) {
+ // 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;
+
+ // strip the matching LIMIT clause out
+ $sql = str_replace( $matches[0], '', $sql );
+ return $this->limitResult( $sql, $row_count, $offset );
+ }
+ return $sql;
+ }
+
+ // MSSQL does support this, but documentation is too thin to make a generalized
+ // function for this. Apparently UPDATE TOP (N) works, but the sort order
+ // may not be what we're expecting so the top n results may be a random selection.
+ // TODO: Implement properly.
+ function limitResultForUpdate( $sql, $num ) {
+ return $sql;
+ }
+
+
+ function timestamp( $ts = 0 ) {
+ return wfTimestamp( TS_ISO_8601, $ts );
}
/**
- * @return string Returns the text of the low priority option if it is supported, or a blank string otherwise
+ * @return string wikitext of a link to the server software's web site
*/
- function lowPriorityOption() {
- return 'LOW_PRIORITY';
+ public static function getSoftwareLink() {
+ return "[http://www.microsoft.com/sql/ MS SQL Server]";
}
/**
- * INSERT SELECT wrapper
- * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
- * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
- * $conds may be "*" to copy the whole table
- * srcTable may be an array of tables.
+ * @return string Version information from the database
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect',
- $insertOptions = array(), $selectOptions = array() )
- {
- $destTable = $this->tableName( $destTable );
- if ( is_array( $insertOptions ) ) {
- $insertOptions = implode( ' ', $insertOptions );
- }
- if( !is_array( $selectOptions ) ) {
- $selectOptions = array( $selectOptions );
- }
- list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
- if( is_array( $srcTable ) ) {
- $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
- } else {
- $srcTable = $this->tableName( $srcTable );
- }
- $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
- " SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex ";
- if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ function getServerVersion() {
+ $server_info = sqlsrv_server_info( $this->mConn );
+ $version = 'Error';
+ if ( isset( $server_info['SQLServerVersion'] ) ) $version = $server_info['SQLServerVersion'];
+ return $version;
+ }
+
+ function tableExists ( $table, $schema = false ) {
+ $res = sqlsrv_query( $this->mConn, "SELECT * FROM information_schema.tables
+ WHERE table_type='BASE TABLE' AND table_name = '$table'" );
+ if ( $res === false ) {
+ print( "Error in tableExists query: " . $this->getErrors() );
+ return false;
}
- $sql .= " $tailOpts";
- return $this->query( $sql, $fname );
+ if ( sqlsrv_fetch( $res ) )
+ return true;
+ else
+ return false;
}
/**
- * Construct a LIMIT query with optional offset
- * This is used for query pages
- * $sql string SQL query we will append the limit to
- * $limit integer the SQL limit
- * $offset integer the SQL offset (default false)
+ * Query whether a given column exists in the mediawiki schema
*/
- function limitResult($sql, $limit, $offset=false) {
- if( !is_numeric($limit) ) {
- throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
+ function fieldExists( $table, $field, $fname = 'DatabaseMssql::fieldExists' ) {
+ $table = $this->tableName( $table );
+ $res = sqlsrv_query( $this->mConn, "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.Columns
+ WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+ if ( $res === false ) {
+ print( "Error in fieldExists query: " . $this->getErrors() );
+ return false;
}
- if ($offset) {
- throw new DBUnexpectedError( $this, 'Database::limitResult called with non-zero offset which is not supported yet' );
- } else {
- $sql = ereg_replace("^SELECT", "SELECT TOP $limit", $sql);
+ if ( sqlsrv_fetch( $res ) )
+ return true;
+ else
+ return false;
+ }
+
+ function fieldInfo( $table, $field ) {
+ $table = $this->tableName( $table );
+ $res = sqlsrv_query( $this->mConn, "SELECT * FROM INFORMATION_SCHEMA.Columns
+ WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+ if ( $res === false ) {
+ print( "Error in fieldInfo query: " . $this->getErrors() );
+ return false;
}
- return $sql;
+ $meta = $this->fetchRow( $res );
+ if ( $meta ) {
+ return new MssqlField( $meta );
+ }
+ return false;
+ }
+
+ public function unixTimestamp( $field ) {
+ return "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),$field)";
}
/**
- * Should determine if the last failure was due to a deadlock
- * @return bool
+ * Begin a transaction, committing any previously open transaction
*/
- function wasDeadlock() {
- return $this->lastErrno() == 1205;
+ function begin( $fname = 'DatabaseMssql::begin' ) {
+ sqlsrv_begin_transaction( $this->mConn );
+ $this->mTrxLevel = 1;
}
/**
- * Return MW-style timestamp used for MySQL schema
+ * End a transaction
*/
- function timestamp( $ts=0 ) {
- return wfTimestamp(TS_MW,$ts);
+ function commit( $fname = 'DatabaseMssql::commit' ) {
+ sqlsrv_commit( $this->mConn );
+ $this->mTrxLevel = 0;
}
/**
- * Local database timestamp format or null
+ * Rollback a transaction.
+ * No-op on non-transactional databases.
*/
- function timestampOrNull( $ts = null ) {
- if( is_null( $ts ) ) {
- return null;
- } else {
- return $this->timestamp( $ts );
+ function rollback( $fname = 'DatabaseMssql::rollback' ) {
+ sqlsrv_rollback( $this->mConn );
+ $this->mTrxLevel = 0;
+ }
+
+ function setup_database() {
+ global $wgDBuser;
+
+ // Make sure that we can write to the correct schema
+ $ctest = "mediawiki_test_table";
+ if ( $this->tableExists( $ctest ) ) {
+ $this->doQuery( "DROP TABLE $ctest" );
+ }
+ $SQL = "CREATE TABLE $ctest (a int)";
+ $res = $this->doQuery( $SQL );
+ if ( !$res ) {
+ print "<b>FAILED</b>. Make sure that the user " . htmlspecialchars( $wgDBuser ) . " can write to the database</li>\n";
+ dieout( );
}
+ $this->doQuery( "DROP TABLE $ctest" );
+
+ $res = $this->sourceFile( "../maintenance/mssql/tables.sql" );
+ if ( $res !== true ) {
+ echo " <b>FAILED</b></li>";
+ dieout( htmlspecialchars( $res ) );
+ }
+
+ # Avoid the non-standard "REPLACE INTO" syntax
+ $f = fopen( "../maintenance/interwiki.sql", 'r' );
+ if ( $f == false ) {
+ dieout( "<li>Could not find the interwiki.sql file" );
+ }
+ # We simply assume it is already empty as we have just created it
+ $SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
+ while ( ! feof( $f ) ) {
+ $line = fgets( $f, 1024 );
+ $matches = array();
+ if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) {
+ continue;
+ }
+ $this->query( "$SQL $matches[1],$matches[2])" );
+ }
+ print " (table interwiki successfully populated)...\n";
+
+ $this->commit();
}
/**
- * @return string wikitext of a link to the server software's web site
+ * Escapes a identifier for use inm SQL.
+ * Throws an exception if it is invalid.
+ * Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
*/
- function getSoftwareLink() {
- return "[http://www.microsoft.com/sql/default.mspx Microsoft SQL Server 2005 Home]";
+ private function escapeIdentifier( $identifier ) {
+ if ( strlen( $identifier ) == 0 ) {
+ throw new MWException( "An identifier must not be empty" );
+ }
+ if ( strlen( $identifier ) > 128 ) {
+ throw new MWException( "The identifier '$identifier' is too long (max. 128)" );
+ }
+ if ( ( strpos( $identifier, '[' ) !== false ) || ( strpos( $identifier, ']' ) !== false ) ) {
+ // It may be allowed if you quoted with double quotation marks, but that would break if QUOTED_IDENTIFIER is OFF
+ throw new MWException( "You can't use square brackers in the identifier '$identifier'" );
+ }
+ return "[$identifier]";
}
/**
- * @return string Version information from the database
+ * Initial setup.
+ * Precondition: This object is connected as the superuser.
+ * Creates the database, schema, user and login.
*/
- function getServerVersion() {
- $row = mssql_fetch_row(mssql_query('select @@VERSION'));
- return ereg("^(.+[0-9]+\\.[0-9]+\\.[0-9]+) ",$row[0],$m) ? $m[1] : $row[0];
+ function initial_setup( $dbName, $newUser, $loginPassword ) {
+ $dbName = $this->escapeIdentifier( $dbName );
+
+ // It is not clear what can be used as a login,
+ // From http://msdn.microsoft.com/en-us/library/ms173463.aspx
+ // a sysname may be the same as an identifier.
+ $newUser = $this->escapeIdentifier( $newUser );
+ $loginPassword = $this->addQuotes( $loginPassword );
+
+ $this->doQuery("CREATE DATABASE $dbName;");
+ $this->doQuery("USE $dbName;");
+ $this->doQuery("CREATE SCHEMA $dbName;");
+ $this->doQuery("
+ CREATE
+ LOGIN $newUser
+ WITH
+ PASSWORD=$loginPassword
+ ;
+ ");
+ $this->doQuery("
+ CREATE
+ USER $newUser
+ FOR
+ LOGIN $newUser
+ WITH
+ DEFAULT_SCHEMA=$dbName
+ ;
+ ");
+ $this->doQuery("
+ GRANT
+ BACKUP DATABASE,
+ BACKUP LOG,
+ CREATE DEFAULT,
+ CREATE FUNCTION,
+ CREATE PROCEDURE,
+ CREATE RULE,
+ CREATE TABLE,
+ CREATE VIEW,
+ CREATE FULLTEXT CATALOG
+ ON
+ DATABASE::$dbName
+ TO $newUser
+ ;
+ ");
+ $this->doQuery("
+ GRANT
+ CONTROL
+ ON
+ SCHEMA::$dbName
+ TO $newUser
+ ;
+ ");
+
+
}
- function limitResultForUpdate($sql, $num) {
- return $sql;
+ function encodeBlob( $b ) {
+ // we can't have zero's and such, this is a simple encoding to make sure we don't barf
+ return base64_encode( $b );
+ }
+
+ function decodeBlob( $b ) {
+ // we can't have zero's and such, this is a simple encoding to make sure we don't barf
+ return base64_decode( $b );
}
/**
- * How lagged is this slave?
+ * @private
*/
- public function getLag() {
- return 0;
+ function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
+ $ret = array();
+ $retJOIN = array();
+ $use_index_safe = is_array( $use_index ) ? $use_index : array();
+ $join_conds_safe = is_array( $join_conds ) ? $join_conds : array();
+ foreach ( $tables as $table ) {
+ // Is there a JOIN and INDEX clause for this table?
+ if ( isset( $join_conds_safe[$table] ) && isset( $use_index_safe[$table] ) ) {
+ $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+ $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')';
+ $retJOIN[] = $tableClause;
+ // Is there an INDEX clause?
+ } else if ( isset( $use_index_safe[$table] ) ) {
+ $tableClause = $this->tableName( $table );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+ $ret[] = $tableClause;
+ // Is there a JOIN clause?
+ } else if ( isset( $join_conds_safe[$table] ) ) {
+ $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
+ $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')';
+ $retJOIN[] = $tableClause;
+ } else {
+ $tableClause = $this->tableName( $table );
+ $ret[] = $tableClause;
+ }
+ }
+ // We can't separate explicit JOIN clauses with ',', use ' ' for those
+ $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
+ $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
+ // Compile our final table clause
+ return implode( ' ', array( $straightJoins, $otherJoins ) );
+ }
+
+ function strencode( $s ) { # Should not be called by us
+ return str_replace( "'", "''", $s );
+ }
+
+ function addQuotes( $s ) {
+ if ( $s instanceof Blob ) {
+ return "'" . $s->fetch( $s ) . "'";
+ } else {
+ return parent::addQuotes( $s );
+ }
+ }
+
+ function selectDB( $db ) {
+ return ( $this->query( "SET DATABASE $db" ) !== false );
}
/**
- * Called by the installer script
- * - this is the same way as DatabasePostgresql.php, MySQL reads in tables.sql and interwiki.sql using dbsource (which calls db->sourceFile)
+ * @private
+ *
+ * @param $options Array: an associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
+ * @return Array
*/
- public function setup_database() {
- global $IP,$wgDBTableOptions;
- $wgDBTableOptions = '';
- $mysql_tmpl = "$IP/maintenance/tables.sql";
- $mysql_iw = "$IP/maintenance/interwiki.sql";
- $mssql_tmpl = "$IP/maintenance/mssql/tables.sql";
-
- # Make an MSSQL template file if it doesn't exist (based on the same one MySQL uses to create a new wiki db)
- if (!file_exists($mssql_tmpl)) { # todo: make this conditional again
- $sql = file_get_contents($mysql_tmpl);
- $sql = preg_replace('/^\s*--.*?$/m','',$sql); # strip comments
- $sql = preg_replace('/^\s*(UNIQUE )?(INDEX|KEY|FULLTEXT).+?$/m', '', $sql); # These indexes should be created with a CREATE INDEX query
- $sql = preg_replace('/(\sKEY) [^\(]+\(/is', '$1 (', $sql); # "KEY foo (foo)" should just be "KEY (foo)"
- $sql = preg_replace('/(varchar\([0-9]+\))\s+binary/i', '$1', $sql); # "varchar(n) binary" cannot be followed by "binary"
- $sql = preg_replace('/(var)?binary\(([0-9]+)\)/ie', '"varchar(".strlen(pow(2,$2)).")"', $sql); # use varchar(chars) not binary(bits)
- $sql = preg_replace('/ (var)?binary/i', ' varchar', $sql); # use varchar not binary
- $sql = preg_replace('/(varchar\([0-9]+\)(?! N))/', '$1 NULL', $sql); # MSSQL complains if NULL is put into a varchar
- #$sql = preg_replace('/ binary/i',' varchar',$sql); # MSSQL binary's can't be assigned with strings, so use varchar's instead
- #$sql = preg_replace('/(binary\([0-9]+\) (NOT NULL )?default) [\'"].*?[\'"]/i','$1 0',$sql); # binary default cannot be string
- $sql = preg_replace('/[a-z]*(blob|text)([ ,])/i', 'text$2', $sql); # no BLOB types in MSSQL
- $sql = preg_replace('/\).+?;/',');', $sql); # remove all table options
- $sql = preg_replace('/ (un)?signed/i', '', $sql);
- $sql = preg_replace('/ENUM\(.+?\)/','TEXT',$sql); # Make ENUM's into TEXT's
- $sql = str_replace(' bool ', ' bit ', $sql);
- $sql = str_replace('auto_increment', 'IDENTITY(1,1)', $sql);
- #$sql = preg_replace('/NOT NULL(?! IDENTITY)/', 'NULL', $sql); # Allow NULL's for non IDENTITY columns
-
- # Tidy up and write file
- $sql = preg_replace('/,\s*\)/s', "\n)", $sql); # Remove spurious commas left after INDEX removals
- $sql = preg_replace('/^\s*^/m', '', $sql); # Remove empty lines
- $sql = preg_replace('/;$/m', ";\n", $sql); # Separate each statement with an empty line
- file_put_contents($mssql_tmpl, $sql);
- }
-
- # Parse the MSSQL template replacing inline variables such as /*$wgDBprefix*/
- $err = $this->sourceFile($mssql_tmpl);
- if ($err !== true) $this->reportQueryError($err,0,$sql,__FUNCTION__);
-
- # Use DatabasePostgres's code to populate interwiki from MySQL template
- $f = fopen($mysql_iw,'r');
- if ($f == false) dieout("<li>Could not find the interwiki.sql file");
- $sql = "INSERT INTO {$this->mTablePrefix}interwiki(iw_prefix,iw_url,iw_local) VALUES ";
- while (!feof($f)) {
- $line = fgets($f,1024);
- $matches = array();
- if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) continue;
- $this->query("$sql $matches[1],$matches[2])");
+ function makeSelectOptions( $options ) {
+ $tailOpts = '';
+ $startOpts = '';
+
+ $noKeyOptions = array();
+ foreach ( $options as $key => $option ) {
+ if ( is_numeric( $key ) ) {
+ $noKeyOptions[$option] = true;
+ }
}
+
+ if ( isset( $options['GROUP BY'] ) ) $tailOpts .= " GROUP BY {$options['GROUP BY']}";
+ if ( isset( $options['HAVING'] ) ) $tailOpts .= " HAVING {$options['GROUP BY']}";
+ if ( isset( $options['ORDER BY'] ) ) $tailOpts .= " ORDER BY {$options['ORDER BY']}";
+
+ if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
+
+ // we want this to be compatible with the output of parent::makeSelectOptions()
+ return array( $startOpts, '' , $tailOpts, '' );
+ }
+
+ /**
+ * Get the type of the DBMS, as it appears in $wgDBtype.
+ */
+ function getType(){
+ return 'mssql';
}
-
+
+ function buildConcat( $stringList ) {
+ return implode( ' + ', $stringList );
+ }
+
public function getSearchEngine() {
- return "SearchEngineDummy";
+ return "SearchMssql";
+ }
+
+} // end DatabaseMssql class
+
+/**
+ * Utility class.
+ *
+ * @ingroup Database
+ */
+class MssqlField implements Field {
+ private $name, $tablename, $default, $max_length, $nullable, $type;
+ function __construct ( $info ) {
+ $this->name = $info['COLUMN_NAME'];
+ $this->tablename = $info['TABLE_NAME'];
+ $this->default = $info['COLUMN_DEFAULT'];
+ $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
+ $this->nullable = ( strtolower( $info['IS_NULLABLE'] ) == 'no' ) ? false:true;
+ $this->type = $info['DATA_TYPE'];
+ }
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tableName;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ function maxLength() {
+ return $this->max_length;
+ }
+
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function type() {
+ return $this->type;
}
}
/**
+ * The MSSQL PHP driver doesn't support sqlsrv_num_rows, so we recall all rows into an array and maintain our
+ * own cursor index into that array...This is similar to the way the Oracle driver handles this same issue
+ *
* @ingroup Database
*/
-class MSSQLField extends MySQLField {
+class MssqlResult {
+
+ public function __construct( $queryresult = false ) {
+ $this->mCursor = 0;
+ $this->mRows = array();
+ $this->mNumFields = sqlsrv_num_fields( $queryresult );
+ $this->mFieldMeta = sqlsrv_field_metadata( $queryresult );
+ while ( $row = sqlsrv_fetch_array( $queryresult, SQLSRV_FETCH_ASSOC ) ) {
+ if ( $row !== null ) {
+ foreach ( $row as $k => $v ) {
+ if ( is_object( $v ) && method_exists( $v, 'format' ) ) {// DateTime Object
+ $row[$k] = $v->format( "Y-m-d\TH:i:s\Z" );
+ }
+ }
+ $this->mRows[] = $row;// read results into memory, cursors are not supported
+ }
+ }
+ $this->mRowCount = count( $this->mRows );
+ sqlsrv_free_stmt( $queryresult );
+ }
+
+ private function array_to_obj( $array, &$obj ) {
+ foreach ( $array as $key => $value ) {
+ if ( is_array( $value ) ) {
+ $obj->$key = new stdClass();
+ $this->array_to_obj( $value, $obj->$key );
+ } else {
+ if ( !empty( $key ) ) {
+ $obj->$key = $value;
+ }
+ }
+ }
+ return $obj;
+ }
- function __construct() {
+ public function fetch( $mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass' ) {
+ if ( $this->mCursor >= $this->mRowCount || $this->mRowCount == 0 ) {
+ return false;
+ }
+ $arrNum = array();
+ if ( $mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH ) {
+ foreach ( $this->mRows[$this->mCursor] as $value ) {
+ $arrNum[] = $value;
+ }
+ }
+ switch( $mode ) {
+ case SQLSRV_FETCH_ASSOC:
+ $ret = $this->mRows[$this->mCursor];
+ break;
+ case SQLSRV_FETCH_NUMERIC:
+ $ret = $arrNum;
+ break;
+ case 'OBJECT':
+ $o = new $object_class;
+ $ret = $this->array_to_obj( $this->mRows[$this->mCursor], $o );
+ break;
+ case SQLSRV_FETCH_BOTH:
+ default:
+ $ret = $this->mRows[$this->mCursor] + $arrNum;
+ break;
}
- static function fromText($db, $table, $field) {
- $n = new MSSQLField;
- $n->name = $field;
- $n->tablename = $table;
- return $n;
+ $this->mCursor++;
+ return $ret;
+ }
+
+ public function get( $pos, $fld ) {
+ return $this->mRows[$pos][$fld];
+ }
+
+ public function numrows() {
+ return $this->mRowCount;
+ }
+
+ public function seek( $iRow ) {
+ $this->mCursor = min( $iRow, $this->mRowCount );
+ }
+
+ public function numfields() {
+ return $this->mNumFields;
+ }
+
+ public function fieldname( $nr ) {
+ $arrKeys = array_keys( $this->mRows[0] );
+ return $arrKeys[$nr];
+ }
+
+ public function fieldtype( $nr ) {
+ $i = 0;
+ $intType = -1;
+ foreach ( $this->mFieldMeta as $meta ) {
+ if ( $nr == $i ) {
+ $intType = $meta['Type'];
+ break;
+ }
+ $i++;
}
+ // http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table
+ switch( $intType ) {
+ case SQLSRV_SQLTYPE_BIGINT: $strType = 'bigint'; break;
+ case SQLSRV_SQLTYPE_BINARY: $strType = 'binary'; break;
+ case SQLSRV_SQLTYPE_BIT: $strType = 'bit'; break;
+ case SQLSRV_SQLTYPE_CHAR: $strType = 'char'; break;
+ case SQLSRV_SQLTYPE_DATETIME: $strType = 'datetime'; break;
+ case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $strType = 'decimal'; break;
+ case SQLSRV_SQLTYPE_FLOAT: $strType = 'float'; break;
+ case SQLSRV_SQLTYPE_IMAGE: $strType = 'image'; break;
+ case SQLSRV_SQLTYPE_INT: $strType = 'int'; break;
+ case SQLSRV_SQLTYPE_MONEY: $strType = 'money'; break;
+ case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $strType = 'nchar'; break;
+ case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $strType = 'numeric'; break;
+ case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $strType = 'nvarchar'; break;
+ // case SQLSRV_SQLTYPE_NVARCHAR('max'): $strType = 'nvarchar(MAX)'; break;
+ case SQLSRV_SQLTYPE_NTEXT: $strType = 'ntext'; break;
+ case SQLSRV_SQLTYPE_REAL: $strType = 'real'; break;
+ case SQLSRV_SQLTYPE_SMALLDATETIME: $strType = 'smalldatetime'; break;
+ case SQLSRV_SQLTYPE_SMALLINT: $strType = 'smallint'; break;
+ case SQLSRV_SQLTYPE_SMALLMONEY: $strType = 'smallmoney'; break;
+ case SQLSRV_SQLTYPE_TEXT: $strType = 'text'; break;
+ case SQLSRV_SQLTYPE_TIMESTAMP: $strType = 'timestamp'; break;
+ case SQLSRV_SQLTYPE_TINYINT: $strType = 'tinyint'; break;
+ case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $strType = 'uniqueidentifier'; break;
+ case SQLSRV_SQLTYPE_UDT: $strType = 'UDT'; break;
+ case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $strType = 'varbinary'; break;
+ // case SQLSRV_SQLTYPE_VARBINARY('max'): $strType = 'varbinary(MAX)'; break;
+ case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $strType = 'varchar'; break;
+ // case SQLSRV_SQLTYPE_VARCHAR('max'): $strType = 'varchar(MAX)'; break;
+ case SQLSRV_SQLTYPE_XML: $strType = 'xml'; break;
+ default: $strType = $intType;
+ }
+ return $strType;
+ }
-} // end DatabaseMssql class
+ public function free() {
+ unset( $this->mRows );
+ return;
+ }
+}
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index ea7ef5b9..ed276ec5 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -1,5 +1,12 @@
<?php
/**
+ * This is the MySQL database abstraction layer.
+ *
+ * @file
+ * @ingroup Database
+ */
+
+/**
* Database abstraction object for mySQL
* Inherit all methods and properties of Database::Database()
*
@@ -24,11 +31,8 @@ class DatabaseMysql extends DatabaseBase {
global $wgAllDBsAreLocalhost;
wfProfileIn( __METHOD__ );
- # Test for missing mysql.so
- # First try to load it
- if (!@extension_loaded('mysql')) {
- @dl('mysql.so');
- }
+ # Load mysql.so if we don't have it
+ wfDl( 'mysql' );
# Fail now
# Otherwise we get a suppressed fatal error, which is very hard to track down
@@ -48,8 +52,6 @@ class DatabaseMysql extends DatabaseBase {
$this->mPassword = $password;
$this->mDBname = $dbName;
- $success = false;
-
wfProfileIn("dbconnect-$server");
# The kernel's default SYN retransmission period is far too slow for us,
@@ -72,10 +74,10 @@ class DatabaseMysql extends DatabaseBase {
# Create a new connection...
$this->mConn = mysql_connect( $realServer, $user, $password, true );
}
- if ($this->mConn === false) {
+ #if ( $this->mConn === false ) {
#$iplus = $i + 1;
- #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
- }
+ #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
+ #}
}
$phpError = $this->restoreErrorHandler();
# Always log connection errors
@@ -88,9 +90,8 @@ class DatabaseMysql extends DatabaseBase {
wfDebug( "DB connection error\n" );
wfDebug( "Server: $server, User: $user, Password: " .
substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" );
- $success = false;
}
-
+
wfProfileOut("dbconnect-$server");
if ( $dbName != '' && $this->mConn !== false ) {
@@ -114,9 +115,15 @@ class DatabaseMysql extends DatabaseBase {
global $wgDBmysql5;
if( $wgDBmysql5 ) {
$this->query( 'SET NAMES utf8', __METHOD__ );
+ } else {
+ $this->query( 'SET NAMES binary', __METHOD__ );
+ }
+ // Set SQL mode, default is turning them all off, can be overridden or skipped with null
+ global $wgSQLMode;
+ if ( is_string( $wgSQLMode ) ) {
+ $mode = $this->addQuotes( $wgSQLMode );
+ $this->query( "SET sql_mode = $mode", __METHOD__ );
}
- // Turn off strict mode
- $this->query( "SET sql_mode = ''", __METHOD__ );
}
// Turn off strict mode if it is on
@@ -233,34 +240,35 @@ class DatabaseMysql extends DatabaseBase {
}
function affectedRows() { return mysql_affected_rows( $this->mConn ); }
-
+
/**
* Estimate rows in dataset
* Returns estimated count, based on EXPLAIN output
* Takes same arguments as Database::select()
*/
- public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
+ public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
- if ( $res === false )
+ if ( $res === false ) {
return false;
+ }
if ( !$this->numRows( $res ) ) {
- $this->freeResult($res);
return 0;
}
$rows = 1;
- while( $plan = $this->fetchObject( $res ) ) {
+ foreach ( $res as $plan ) {
$rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
}
-
- $this->freeResult($res);
- return $rows;
+ return $rows;
}
function fieldInfo( $table, $field ) {
$table = $this->tableName( $table );
- $res = $this->query( "SELECT * FROM $table LIMIT 1" );
+ $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
+ if ( !$res ) {
+ return false;
+ }
$n = mysql_num_fields( $res->result );
for( $i = 0; $i < $n; $i++ ) {
$meta = mysql_fetch_field( $res->result, $i );
@@ -271,32 +279,108 @@ class DatabaseMysql extends DatabaseBase {
return false;
}
+ /**
+ * Get information about an index into an object
+ * Returns false if the index does not exist
+ */
+ function indexInfo( $table, $index, $fname = 'DatabaseMysql::indexInfo' ) {
+ # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
+ # SHOW INDEX should work for 3.x and up:
+ # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
+ $table = $this->tableName( $table );
+ $index = $this->indexName( $index );
+ $sql = 'SHOW INDEX FROM ' . $table;
+ $res = $this->query( $sql, $fname );
+
+ if ( !$res ) {
+ return null;
+ }
+
+ $result = array();
+
+ foreach ( $res as $row ) {
+ if ( $row->Key_name == $index ) {
+ $result[] = $row;
+ }
+ }
+
+ return empty( $result ) ? false : $result;
+ }
+
function selectDB( $db ) {
$this->mDBname = $db;
return mysql_select_db( $db, $this->mConn );
}
function strencode( $s ) {
- return mysql_real_escape_string( $s, $this->mConn );
+ $sQuoted = mysql_real_escape_string( $s, $this->mConn );
+
+ if($sQuoted === false) {
+ $this->ping();
+ $sQuoted = mysql_real_escape_string( $s, $this->mConn );
+ }
+ return $sQuoted;
+ }
+
+ /**
+ * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
+ */
+ public function addIdentifierQuotes( $s ) {
+ return "`" . $this->strencode( $s ) . "`";
}
function ping() {
- if( !function_exists( 'mysql_ping' ) ) {
- wfDebug( "Tried to call mysql_ping but this is ancient PHP version. Faking it!\n" );
- return true;
- }
$ping = mysql_ping( $this->mConn );
if ( $ping ) {
return true;
}
- // Need to reconnect manually in MySQL client 5.0.13+
- if ( version_compare( mysql_get_client_info(), '5.0.13', '>=' ) ) {
- mysql_close( $this->mConn );
- $this->mOpened = false;
- $this->mConn = false;
- $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
- return true;
+ mysql_close( $this->mConn );
+ $this->mOpened = false;
+ $this->mConn = false;
+ $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+ return true;
+ }
+
+ /**
+ * Returns slave lag.
+ * At the moment, this will only work if the DB user has the PROCESS privilege
+ * @result int
+ */
+ function getLag() {
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
+ return $this->mFakeSlaveLag;
+ }
+ $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
+ if( !$res ) {
+ return false;
+ }
+ # Find slave SQL thread
+ foreach( $res as $row ) {
+ /* This should work for most situations - when default db
+ * for thread is not specified, it had no events executed,
+ * and therefore it doesn't know yet how lagged it is.
+ *
+ * Relay log I/O thread does not select databases.
+ */
+ if ( $row->User == 'system user' &&
+ $row->State != 'Waiting for master to send event' &&
+ $row->State != 'Connecting to master' &&
+ $row->State != 'Queueing master event to the relay log' &&
+ $row->State != 'Waiting for master update' &&
+ $row->State != 'Requesting binlog dump' &&
+ $row->State != 'Waiting to reconnect after a failed master event read' &&
+ $row->State != 'Reconnecting after a failed master event read' &&
+ $row->State != 'Registering slave on master'
+ ) {
+ # This is it, return the time (except -ve)
+ if ( $row->Time > 0x7fffffff ) {
+ return false;
+ } else {
+ return $row->Time;
+ }
+ }
}
return false;
}
@@ -313,7 +397,7 @@ class DatabaseMysql extends DatabaseBase {
return 'LOW_PRIORITY';
}
- function getSoftwareLink() {
+ public static function getSoftwareLink() {
return '[http://www.mysql.com/ MySQL]';
}
@@ -330,7 +414,6 @@ class DatabaseMysql extends DatabaseBase {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
$row = $this->fetchObject( $result );
- $this->freeResult( $result );
if( $row->lockstatus == 1 ) {
return true;
@@ -340,6 +423,9 @@ class DatabaseMysql extends DatabaseBase {
}
}
+ /**
+ * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
+ */
public function unlock( $lockName, $method ) {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
@@ -351,8 +437,8 @@ class DatabaseMysql extends DatabaseBase {
$items = array();
foreach( $write as $table ) {
- $tbl = $this->tableName( $table ) .
- ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
+ $tbl = $this->tableName( $table ) .
+ ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
' WRITE';
$items[] = $tbl;
}
@@ -367,6 +453,16 @@ class DatabaseMysql extends DatabaseBase {
$this->query( "UNLOCK TABLES", $method );
}
+ /**
+ * Get search engine class. All subclasses of this
+ * need to implement this if they wish to use searching.
+ *
+ * @return String
+ */
+ public function getSearchEngine() {
+ return 'SearchMySQL';
+ }
+
public function setBigSelects( $value = true ) {
if ( $value === 'default' ) {
if ( $this->mDefaultBigSelects === null ) {
@@ -382,7 +478,10 @@ class DatabaseMysql extends DatabaseBase {
$this->query( "SET sql_big_selects=$encValue", __METHOD__ );
}
-
+ public function unixTimestamp( $field ) {
+ return "UNIX_TIMESTAMP($field)";
+ }
+
/**
* Determines if the last failure was due to a deadlock
*/
@@ -391,7 +490,7 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * Determines if the last query error was something that should be dealt
+ * Determines if the last query error was something that should be dealt
* with by pinging the connection and reissuing the query
*/
function wasErrorReissuable() {
@@ -402,7 +501,7 @@ class DatabaseMysql extends DatabaseBase {
* Determines if the last failure was due to the database being read-only.
*/
function wasReadOnlyError() {
- return $this->lastErrno() == 1223 ||
+ return $this->lastErrno() == 1223 ||
( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
}
@@ -420,7 +519,7 @@ class DatabaseMysql extends DatabaseBase {
$res = $this->query( "SHOW CREATE TABLE $oldName" );
$row = $this->fetchRow( $res );
$oldQuery = $row[1];
- $query = preg_replace( '/CREATE TABLE `(.*?)`/',
+ $query = preg_replace( '/CREATE TABLE `(.*?)`/',
"CREATE $tmp TABLE `$newName`", $oldQuery );
if ($oldQuery === $query) {
# Couldn't do replacement
@@ -432,6 +531,11 @@ class DatabaseMysql extends DatabaseBase {
$this->query( $query, $fname );
}
+ protected function getDefaultSchemaVars() {
+ $vars = parent::getDefaultSchemaVars();
+ $vars['wgDBTableOptions'] = $GLOBALS['wgDBTableOptions'];
+ return $vars;
+ }
}
/**
@@ -439,6 +543,56 @@ class DatabaseMysql extends DatabaseBase {
*/
class Database extends DatabaseMysql {}
+/**
+ * Utility class.
+ * @ingroup Database
+ */
+class MySQLField implements Field {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_multiple, $is_key, $type;
+
+ function __construct ( $info ) {
+ $this->name = $info->name;
+ $this->tablename = $info->table;
+ $this->default = $info->def;
+ $this->max_length = $info->max_length;
+ $this->nullable = !$info->not_null;
+ $this->is_pk = $info->primary_key;
+ $this->is_unique = $info->unique_key;
+ $this->is_multiple = $info->multiple_key;
+ $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+ $this->type = $info->type;
+ }
+
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tableName;
+ }
+
+ function type() {
+ return $this->type;
+ }
+
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ function isKey() {
+ return $this->is_key;
+ }
+
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+}
+
class MySQLMasterPos {
var $file, $pos;
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index bd60bbf8..4fe3e980 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -1,24 +1,10 @@
<?php
/**
- * @ingroup Database
- * @file
- */
-
-/**
* This is the Oracle database abstraction layer.
+ *
+ * @file
* @ingroup Database
*/
-class ORABlob {
- var $mData;
-
- function __construct( $data ) {
- $this->mData = $data;
- }
-
- function getData() {
- return $this->mData;
- }
-}
/**
* The oci8 extension is fairly weak and doesn't support oci_num_rows, among
@@ -29,15 +15,15 @@ class ORABlob {
class ORAResult {
private $rows;
private $cursor;
- private $stmt;
private $nrows;
+
+ private $columns = array();
- private $unique;
private function array_unique_md( $array_in ) {
$array_out = array();
$array_hashes = array();
- foreach ( $array_in as $key => $item ) {
+ foreach ( $array_in as $item ) {
$hash = md5( serialize( $item ) );
if ( !isset( $array_hashes[$hash] ) ) {
$array_hashes[$hash] = $hash;
@@ -53,7 +39,8 @@ class ORAResult {
if ( ( $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, - 1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM ) ) === false ) {
$e = oci_error( $stmt );
- $db->reportQueryError( $e['message'], $e['code'], '', __FUNCTION__ );
+ $db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ );
+ $this->free();
return;
}
@@ -62,12 +49,18 @@ class ORAResult {
$this->nrows = count( $this->rows );
}
+ if ($this->nrows > 0) {
+ foreach ( $this->rows[0] as $k => $v ) {
+ $this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) );
+ }
+ }
+
$this->cursor = 0;
- $this->stmt = $stmt;
+ oci_free_statement( $stmt );
}
public function free() {
- oci_free_statement( $this->stmt );
+ unset($this->db);
}
public function seek( $row ) {
@@ -79,7 +72,7 @@ class ORAResult {
}
public function numFields() {
- return oci_num_fields( $this->stmt );
+ return count($this->columns);
}
public function fetchObject() {
@@ -89,7 +82,7 @@ class ORAResult {
$row = $this->rows[$this->cursor++];
$ret = new stdClass();
foreach ( $row as $k => $v ) {
- $lc = strtolower( oci_field_name( $this->stmt, $k + 1 ) );
+ $lc = $this->columns[$k];
$ret->$lc = $v;
}
@@ -104,7 +97,7 @@ class ORAResult {
$row = $this->rows[$this->cursor++];
$ret = array();
foreach ( $row as $k => $v ) {
- $lc = strtolower( oci_field_name( $this->stmt, $k + 1 ) );
+ $lc = $this->columns[$k];
$ret[$lc] = $v;
$ret[$k] = $v;
}
@@ -116,7 +109,7 @@ class ORAResult {
* Utility class.
* @ingroup Database
*/
-class ORAField {
+class ORAField implements Field {
private $name, $tablename, $default, $max_length, $nullable,
$is_pk, $is_unique, $is_multiple, $is_key, $type;
@@ -149,7 +142,7 @@ class ORAField {
return $this->max_length;
}
- function nullable() {
+ function isNullable() {
return $this->nullable;
}
@@ -185,11 +178,19 @@ class DatabaseOracle extends DatabaseBase {
var $mFieldInfoCache = array();
function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0, $tablePrefix = 'get from global' )
+ $flags = 0, $tablePrefix = 'get from global' )
{
$tablePrefix = $tablePrefix == 'get from global' ? $tablePrefix : strtoupper( $tablePrefix );
- parent::__construct( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix );
- wfRunHooks( 'DatabaseOraclePostInit', array( &$this ) );
+ parent::__construct( $server, $user, $password, $dbName, $flags, $tablePrefix );
+ wfRunHooks( 'DatabaseOraclePostInit', array( $this ) );
+ }
+
+ function __destruct() {
+ if ($this->mOpened) {
+ wfSuppressWarnings();
+ $this->close();
+ wfRestoreWarnings();
+ }
}
function getType() {
@@ -218,25 +219,36 @@ class DatabaseOracle extends DatabaseBase {
return true;
}
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 )
{
- return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags );
+ return new DatabaseOracle( $server, $user, $password, $dbName, $flags );
}
/**
* Usually aborts on failure
- * If the failFunction is set to a non-zero integer, returns success
*/
function open( $server, $user, $password, $dbName ) {
if ( !function_exists( 'oci_connect' ) ) {
throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
}
- $this->close();
- $this->mServer = $server;
$this->mUser = $user;
$this->mPassword = $password;
- $this->mDBname = $dbName;
+ // changed internal variables functions
+ // mServer now holds the TNS endpoint
+ // mDBname is schema name if different from username
+ if ( !$server ) {
+ // backward compatibillity (server used to be null and TNS was supplied in dbname)
+ $this->mServer = $dbName;
+ $this->mDBname = $user;
+ } else {
+ $this->mServer = $server;
+ if ( !$dbName ) {
+ $this->mDBname = $user;
+ } else {
+ $this->mDBname = $dbName;
+ }
+ }
if ( !strlen( $user ) ) { # e.g. the class is being loaded
return;
@@ -244,16 +256,18 @@ class DatabaseOracle extends DatabaseBase {
$session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
if ( $this->mFlags & DBO_DEFAULT ) {
- $this->mConn = oci_new_connect( $user, $password, $dbName, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_new_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
} else {
- $this->mConn = oci_connect( $user, $password, $dbName, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
}
- if ( $this->mConn == false ) {
- wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- wfDebug( $this->lastError() . "\n" );
- return false;
+ if ( $this->mUser != $this->mDBname ) {
+ //change current schema in session
+ $this->selectDB( $this->mDBname );
+ }
+
+ if ( !$this->mConn ) {
+ throw new DBConnectionError( $this, $this->lastError() );
}
$this->mOpened = true;
@@ -271,6 +285,9 @@ class DatabaseOracle extends DatabaseBase {
function close() {
$this->mOpened = false;
if ( $this->mConn ) {
+ if ( $this->mTrxLevel ) {
+ $this->commit();
+ }
return oci_close( $this->mConn );
} else {
return true;
@@ -289,9 +306,10 @@ class DatabaseOracle extends DatabaseBase {
// handle some oracle specifics
// remove AS column/table/subquery namings
- if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
+ if( !$this->getFlag( DBO_DDLMODE ) ) {
$sql = preg_replace( '/ as /i', ' ', $sql );
}
+
// Oracle has issues with UNION clause if the statement includes LOB fields
// So we do a UNION ALL and then filter the results array with array_unique
$union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 );
@@ -301,23 +319,22 @@ class DatabaseOracle extends DatabaseBase {
$sql = preg_replace( '/^EXPLAIN /', 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR', $sql, 1, $explain_count );
-
wfSuppressWarnings();
if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
$e = oci_error( $this->mConn );
- $this->reportQueryError( $e['message'], $e['code'], $sql, __FUNCTION__ );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
}
- if ( oci_execute( $stmt, $this->execFlags() ) == false ) {
+ if ( !oci_execute( $stmt, $this->execFlags() ) ) {
$e = oci_error( $stmt );
if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
- $this->reportQueryError( $e['message'], $e['code'], $sql, __FUNCTION__ );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
}
}
-
+
wfRestoreWarnings();
if ( $explain_count > 0 ) {
@@ -335,43 +352,43 @@ class DatabaseOracle extends DatabaseBase {
}
function freeResult( $res ) {
- if ( $res instanceof ORAResult ) {
- $res->free();
- } else {
- $res->result->free();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ $res->free();
}
function fetchObject( $res ) {
- if ( $res instanceof ORAResult ) {
- return $res->numRows();
- } else {
- return $res->result->fetchObject();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ return $res->fetchObject();
}
function fetchRow( $res ) {
- if ( $res instanceof ORAResult ) {
- return $res->fetchRow();
- } else {
- return $res->result->fetchRow();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ return $res->fetchRow();
}
function numRows( $res ) {
- if ( $res instanceof ORAResult ) {
- return $res->numRows();
- } else {
- return $res->result->numRows();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ return $res->numRows();
}
function numFields( $res ) {
- if ( $res instanceof ORAResult ) {
- return $res->numFields();
- } else {
- return $res->result->numFields();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ return $res->numFields();
}
function fieldName( $stmt, $n ) {
@@ -456,8 +473,38 @@ class DatabaseOracle extends DatabaseBase {
return $retVal;
}
+ private function fieldBindStatement ( $table, $col, &$val, $includeCol = false ) {
+ $col_info = $this->fieldInfoMulti( $table, $col );
+ $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
+
+ $bind = '';
+ if ( is_numeric( $col ) ) {
+ $bind = $val;
+ $val = null;
+ return $bind;
+ } else if ( $includeCol ) {
+ $bind = "$col = ";
+ }
+
+ if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
+ $val = null;
+ }
+
+ if ( $val === null ) {
+ if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
+ $bind .= 'DEFAULT';
+ } else {
+ $bind .= 'NULL';
+ }
+ } else {
+ $bind .= ':' . $col;
+ }
+
+ return $bind;
+ }
+
private function insertOneRow( $table, $row, $fname ) {
- global $wgLang;
+ global $wgContLang;
$table = $this->tableName( $table );
// "INSERT INTO tables (a, b, c)"
@@ -466,18 +513,22 @@ class DatabaseOracle extends DatabaseBase {
// for each value, append ":key"
$first = true;
- foreach ( $row as $col => $val ) {
- if ( $first ) {
- $sql .= $val !== null ? ':' . $col : 'NULL';
+ foreach ( $row as $col => &$val ) {
+ if ( !$first ) {
+ $sql .= ', ';
} else {
- $sql .= $val !== null ? ', :' . $col : ', NULL';
+ $first = false;
}
-
- $first = false;
+
+ $sql .= $this->fieldBindStatement( $table, $col, $val );
}
$sql .= ')';
- $stmt = oci_parse( $this->mConn, $sql );
+ if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
+ $e = oci_error( $this->mConn );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ return false;
+ }
foreach ( $row as $col => &$val ) {
$col_info = $this->fieldInfoMulti( $table, $col );
$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
@@ -486,16 +537,17 @@ class DatabaseOracle extends DatabaseBase {
// do nothing ... null was inserted in statement creation
} elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
if ( is_object( $val ) ) {
- $val = $val->getData();
+ $val = $val->fetch();
}
if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
$val = '31-12-2030 12:00:00.000000';
}
- $val = ( $wgLang != null ) ? $wgLang->checkTitleEncoding( $val ) : $val;
+ $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
- $this->reportQueryError( $this->lastErrno(), $this->lastError(), $sql, __METHOD__ );
+ $e = oci_error( $stmt );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
}
} else {
@@ -504,11 +556,15 @@ class DatabaseOracle extends DatabaseBase {
throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
}
- if ( $col_type == 'BLOB' ) { // is_object($val)) {
- $lob[$col]->writeTemporary( $val ); // ->getData());
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, SQLT_BLOB );
+ if ( is_object( $val ) ) {
+ $val = $val->fetch();
+ }
+
+ if ( $col_type == 'BLOB' ) {
+ $lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_BLOB );
} else {
- $lob[$col]->writeTemporary( $val );
+ $lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
}
}
@@ -516,9 +572,8 @@ class DatabaseOracle extends DatabaseBase {
wfSuppressWarnings();
- if ( oci_execute( $stmt, OCI_DEFAULT ) === false ) {
+ if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
$e = oci_error( $stmt );
-
if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
@@ -532,7 +587,7 @@ class DatabaseOracle extends DatabaseBase {
wfRestoreWarnings();
if ( isset( $lob ) ) {
- foreach ( $lob as $lob_i => $lob_v ) {
+ foreach ( $lob as $lob_v ) {
$lob_v->free();
}
}
@@ -560,11 +615,13 @@ class DatabaseOracle extends DatabaseBase {
if ( ( $sequenceData = $this->getSequenceData( $destTable ) ) !== false &&
!isset( $varMap[$sequenceData['column']] ) )
+ {
$varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
+ }
// count-alias subselect fields to avoid abigious definition errors
$i = 0;
- foreach ( $varMap as $key => &$val ) {
+ foreach ( $varMap as &$val ) {
$val = $val . ' field' . ( $i++ );
}
@@ -640,13 +697,18 @@ class DatabaseOracle extends DatabaseBase {
if ( isset( $database ) ) {
$database = ( $database[0] == '"' ? $database : "\"{$database}\"" );
}
- $table = ( $table[0] == '"' ? $table : "\"{$prefix}{$table}\"" );
+ $table = ( $table[0] == '"') ? $table : "\"{$prefix}{$table}\"" ;
$tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
return strtoupper( $tableName );
}
+ function tableNameInternal( $name ) {
+ $name = $this->tableName( $name );
+ return preg_replace( '/.*\."(.*)"/', '$1', $name);
+ }
+
/**
* Return the next in a sequence, save the value for retrieval via insertId()
*/
@@ -654,7 +716,6 @@ class DatabaseOracle extends DatabaseBase {
$res = $this->query( "SELECT $seqName.nextval FROM dual" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
- $this->freeResult( $res );
return $this->mInsertId;
}
@@ -663,7 +724,7 @@ class DatabaseOracle extends DatabaseBase {
*/
private function getSequenceData( $table ) {
if ( $this->sequenceData == null ) {
- $result = $this->query( "SELECT lower(us.sequence_name), lower(utc.table_name), lower(utc.column_name) from user_sequences us, user_tab_columns utc where us.sequence_name = utc.table_name||'_'||utc.column_name||'_SEQ'" );
+ $result = $this->doQuery( 'SELECT lower(us.sequence_name), lower(utc.table_name), lower(utc.column_name) from user_sequences us, user_tab_columns utc where us.sequence_name = utc.table_name||\'_\'||utc.column_name||\'_SEQ\'' );
while ( ( $row = $result->fetchRow() ) !== false ) {
$this->sequenceData[$this->tableName( $row[1] )] = array(
@@ -676,15 +737,23 @@ class DatabaseOracle extends DatabaseBase {
return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
}
- # REPLACE query wrapper
- # Oracle simulates this with a DELETE followed by INSERT
- # $row is the row to insert, an associative array
- # $uniqueIndexes is an array of indexes. Each element may be either a
- # field name or an array of field names
- #
- # It may be more efficient to leave off unique indexes which are unlikely to collide.
- # However if you do this, you run the risk of encountering errors which wouldn't have
- # occurred in MySQL
+ /**
+ * REPLACE query wrapper
+ * Oracle simulates this with a DELETE followed by INSERT
+ * $row is the row to insert, an associative array
+ * $uniqueIndexes is an array of indexes. Each element may be either a
+ * field name or an array of field names
+ *
+ * It may be more efficient to leave off unique indexes which are unlikely to collide.
+ * However if you do this, you run the risk of encountering errors which wouldn't have
+ * occurred in MySQL.
+ *
+ * @param $table String: table name
+ * @param $uniqueIndexes Array: array of indexes. Each element may be
+ * either a field name or an array of field names
+ * @param $rows Array: rows to insert to $table
+ * @param $fname String: function name, you can use __METHOD__ here
+ */
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseOracle::replace' ) {
$table = $this->tableName( $table );
@@ -703,9 +772,10 @@ class DatabaseOracle extends DatabaseBase {
# Delete rows which collide
if ( $uniqueIndexes ) {
$condsDelete = array();
- foreach ( $uniqueIndexes as $index )
+ foreach ( $uniqueIndexes as $index ) {
$condsDelete[$index] = $row[$index];
- if (count($condsDelete) > 0) {
+ }
+ if ( count( $condsDelete ) > 0 ) {
$this->delete( $table, $condsDelete, $fname );
}
}
@@ -720,9 +790,9 @@ class DatabaseOracle extends DatabaseBase {
}
# DELETE where the condition is a join
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseOracle::deleteJoin" ) {
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseOracle::deleteJoin' ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseOracle::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, 'DatabaseOracle::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
@@ -738,13 +808,8 @@ class DatabaseOracle extends DatabaseBase {
# Returns the size of a text field, or -1 for "unlimited"
function textFieldSize( $table, $field ) {
- $fieldInfoData = $this->fieldInfo( $table, $field);
- if ( $fieldInfoData->type == "varchar" ) {
- $size = $row->size - 4;
- } else {
- $size = $row->size;
- }
- return $size;
+ $fieldInfoData = $this->fieldInfo( $table, $field );
+ return $fieldInfoData->maxLength();
}
function limitResult( $sql, $limit, $offset = false ) {
@@ -754,40 +819,73 @@ class DatabaseOracle extends DatabaseBase {
return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
}
+ function encodeBlob( $b ) {
+ return new Blob( $b );
+ }
+
+ function decodeBlob( $b ) {
+ if ( $b instanceof Blob ) {
+ $b = $b->fetch();
+ }
+ return $b;
+ }
function unionQueries( $sqls, $all ) {
$glue = ' UNION ALL ';
return 'SELECT * ' . ( $all ? '':'/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')' ;
}
+ public function unixTimestamp( $field ) {
+ return "((trunc($field) - to_date('19700101','YYYYMMDD')) * 86400)";
+ }
+
function wasDeadlock() {
return $this->lastErrno() == 'OCI-00060';
}
-
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseOracle::duplicateTableStructure' ) {
- $temporary = $temporary ? 'TRUE' : 'FALSE';
- $oldName = trim(strtoupper($oldName), '"');
- $oldParts = explode('_', $oldName);
+ global $wgDBprefix;
- $newName = trim(strtoupper($newName), '"');
- $newParts = explode('_', $newName);
-
- $oldPrefix = '';
- $newPrefix = '';
- for ($i = count($oldParts)-1; $i >= 0; $i--) {
- if ($oldParts[$i] != $newParts[$i]) {
- $oldPrefix = implode('_', $oldParts).'_';
- $newPrefix = implode('_', $newParts).'_';
- break;
- }
- unset($oldParts[$i]);
- unset($newParts[$i]);
+ $temporary = $temporary ? 'TRUE' : 'FALSE';
+
+ $newName = trim( strtoupper( $newName ), '"');
+ $oldName = trim( strtoupper( $oldName ), '"');
+
+ $tabName = substr( $newName, strlen( $wgDBprefix ) );
+ $oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
+
+ return $this->doQuery( 'BEGIN DUPLICATE_TABLE(\'' . $tabName . '\', \'' . $oldPrefix . '\', \'' . strtoupper( $wgDBprefix ) . '\', ' . $temporary . '); END;' );
+ }
+
+ function listTables( $prefix = null, $fname = 'DatabaseOracle::listTables' ) {
+ $listWhere = '';
+ if (!empty($prefix)) {
+ $listWhere = ' AND table_name LIKE \''.strtoupper($prefix).'%\'';
}
- $tabName = substr($oldName, strlen($oldPrefix));
+ $result = $this->doQuery( "SELECT table_name FROM user_tables WHERE table_name NOT LIKE '%!_IDX$_' ESCAPE '!' $listWhere" );
+
+ // dirty code ... i know
+ $endArray = array();
+ $endArray[] = $prefix.'MWUSER';
+ $endArray[] = $prefix.'PAGE';
+ $endArray[] = $prefix.'IMAGE';
+ $fixedOrderTabs = $endArray;
+ while (($row = $result->fetchRow()) !== false) {
+ if (!in_array($row['table_name'], $fixedOrderTabs))
+ $endArray[] = $row['table_name'];
+ }
+
+ return $endArray;
+ }
+
+ public function dropTable( $tableName, $fName = 'DatabaseOracle::dropTable' ) {
+ $tableName = $this->tableName($tableName);
+ if( !$this->tableExists( $tableName ) ) {
+ return false;
+ }
- return $this->query( 'BEGIN DUPLICATE_TABLE(\'' . $tabName . '\', \'' . $oldPrefix . '\', \''.$newPrefix.'\', ' . $temporary . '); END;', $fname );
+ return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
}
function timestamp( $ts = 0 ) {
@@ -818,7 +916,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* @return string wikitext of a link to the server software's web site
*/
- function getSoftwareLink() {
+ public static function getSoftwareLink() {
return '[http://www.oracle.com/ Oracle]';
}
@@ -826,14 +924,21 @@ class DatabaseOracle extends DatabaseBase {
* @return string Version information from the database
*/
function getServerVersion() {
- return oci_server_version( $this->mConn );
+ //better version number, fallback on driver
+ $rset = $this->doQuery( 'SELECT version FROM product_component_version WHERE UPPER(product) LIKE \'ORACLE DATABASE%\'' );
+ if ( !( $row = $rset->fetchRow() ) ) {
+ return oci_server_version( $this->mConn );
+ }
+ return $row['version'];
}
/**
* Query whether a given table exists (in the given schema, or the default mw one if not given)
*/
function tableExists( $table ) {
- $SQL = "SELECT 1 FROM user_tables WHERE table_name='$table'";
+ $table = $this->addQuotes( trim( $this->tableName($table), '"' ) );
+ $owner = $this->addQuotes( strtoupper( $this->mDBname ) );
+ $SQL = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
$res = $this->doQuery( $SQL );
if ( $res ) {
$count = $res->numRows();
@@ -841,7 +946,7 @@ class DatabaseOracle extends DatabaseBase {
} else {
$count = 0;
}
- return $count;
+ return $count!=0;
}
/**
@@ -850,76 +955,78 @@ class DatabaseOracle extends DatabaseBase {
* For internal calls. Use fieldInfo for normal usage.
* Returns false if the field doesn't exist
*
- * @param Array $table
- * @param String $field
+ * @param $table Array
+ * @param $field String
*/
private function fieldInfoMulti( $table, $field ) {
- $tableWhere = '';
- $field = strtoupper($field);
- if (is_array($table)) {
- $table = array_map( array( &$this, 'tableName' ), $table );
+ $field = strtoupper( $field );
+ if ( is_array( $table ) ) {
+ $table = array_map( array( &$this, 'tableNameInternal' ), $table );
$tableWhere = 'IN (';
- foreach($table as &$singleTable) {
- $singleTable = strtoupper(trim( $singleTable, '"' ));
- if (isset($this->mFieldInfoCache["$singleTable.$field"])) {
+ foreach( $table as &$singleTable ) {
+ $singleTable = strtoupper( trim( $singleTable, '"' ) );
+ if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
return $this->mFieldInfoCache["$singleTable.$field"];
}
- $tableWhere .= '\''.$singleTable.'\',';
+ $tableWhere .= '\'' . $singleTable . '\',';
}
- $tableWhere = rtrim($tableWhere, ',').')';
+ $tableWhere = rtrim( $tableWhere, ',' ) . ')';
} else {
- $table = strtoupper(trim( $this->tableName($table), '"' ));
- if (isset($this->mFieldInfoCache["$table.$field"])) {
+ $table = strtoupper( trim( $this->tableNameInternal( $table ), '"' ) );
+ if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
return $this->mFieldInfoCache["$table.$field"];
}
$tableWhere = '= \''.$table.'\'';
}
$fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name '.$tableWhere.' and column_name = \''.$field.'\'' );
- if ( oci_execute( $fieldInfoStmt, OCI_DEFAULT ) === false ) {
+ if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
$e = oci_error( $fieldInfoStmt );
$this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
return false;
}
$res = new ORAResult( $this, $fieldInfoStmt );
- if ($res->numRows() == 0 ) {
- if (is_array($table)) {
- foreach($table as &$singleTable) {
+ if ( $res->numRows() == 0 ) {
+ if ( is_array( $table ) ) {
+ foreach( $table as &$singleTable ) {
$this->mFieldInfoCache["$singleTable.$field"] = false;
}
} else {
$this->mFieldInfoCache["$table.$field"] = false;
}
+ $fieldInfoTemp = null;
} else {
$fieldInfoTemp = new ORAField( $res->fetchRow() );
$table = $fieldInfoTemp->tableName();
$this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
- return $fieldInfoTemp;
}
+ $res->free();
+ return $fieldInfoTemp;
}
function fieldInfo( $table, $field ) {
if ( is_array( $table ) ) {
- throw new DBUnexpectedError( $this, 'Database::fieldInfo called with table array!' );
+ throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
}
return $this->fieldInfoMulti ($table, $field);
}
- function fieldExists( $table, $field, $fname = 'DatabaseOracle::fieldExists' ) {
- return (bool)$this->fieldInfo( $table, $field, $fname );
- }
-
- function begin( $fname = '' ) {
+ function begin( $fname = 'DatabaseOracle::begin' ) {
$this->mTrxLevel = 1;
}
- function immediateCommit( $fname = '' ) {
- return true;
+ function commit( $fname = 'DatabaseOracle::commit' ) {
+ if ( $this->mTrxLevel ) {
+ oci_commit( $this->mConn );
+ $this->mTrxLevel = 0;
+ }
}
- function commit( $fname = '' ) {
- oci_commit( $this->mConn );
- $this->mTrxLevel = 0;
+ function rollback( $fname = 'DatabaseOracle::rollback' ) {
+ if ( $this->mTrxLevel ) {
+ oci_rollback( $this->mConn );
+ $this->mTrxLevel = 0;
+ }
}
/* Not even sure why this is used in the main codebase... */
@@ -928,7 +1035,7 @@ class DatabaseOracle extends DatabaseBase {
}
/* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */
- function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
+ function sourceStream( $fp, $lineCallback = false, $resultCallback = false, $fname = 'DatabaseOracle::sourceStream' ) {
$cmd = '';
$done = false;
$dollarquote = false;
@@ -953,6 +1060,7 @@ class DatabaseOracle extends DatabaseBase {
if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
if ( $dollarquote ) {
$dollarquote = false;
+ $line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes
$done = true;
} else {
$dollarquote = true;
@@ -977,11 +1085,11 @@ class DatabaseOracle extends DatabaseBase {
}
} else {
foreach ( $replacements as $mwVar => $scVar ) {
- $cmd = str_replace( '&' . $scVar . '.', '{$' . $mwVar . '}', $cmd );
+ $cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd );
}
$cmd = $this->replaceVars( $cmd );
- $res = $this->query( $cmd, __METHOD__ );
+ $res = $this->doQuery( $cmd );
if ( $resultCallback ) {
call_user_func( $resultCallback, $res, $this );
}
@@ -999,36 +1107,24 @@ class DatabaseOracle extends DatabaseBase {
return true;
}
- function setup_database() {
- global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport, $wgDBuser;
-
- $res = $this->sourceFile( "../maintenance/ora/tables.sql" );
- if ($res === true) {
- print " done.</li>\n";
- } else {
- print " <b>FAILED</b></li>\n";
- dieout( htmlspecialchars( $res ) );
- }
-
- // Avoid the non-standard "REPLACE INTO" syntax
- echo "<li>Populating interwiki table</li>\n";
- $f = fopen( "../maintenance/interwiki.sql", 'r' );
- if ( $f == false ) {
- dieout( "Could not find the interwiki.sql file" );
+ function selectDB( $db ) {
+ $this->mDBname = $db;
+ if ( $db == null || $db == $this->mUser ) {
+ return true;
}
-
- // do it like the postgres :D
- $SQL = "INSERT INTO ".$this->tableName('interwiki')." (iw_prefix,iw_url,iw_local) VALUES ";
- while ( !feof( $f ) ) {
- $line = fgets( $f, 1024 );
- $matches = array();
- if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) {
- continue;
+ $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper($db);
+ $stmt = oci_parse( $this->mConn, $sql );
+ wfSuppressWarnings();
+ $success = oci_execute( $stmt );
+ wfRestoreWarnings();
+ if ( !$success ) {
+ $e = oci_error( $stmt );
+ if ( $e['code'] != '1435' ) {
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
}
- $this->query( "$SQL $matches[1],$matches[2])" );
+ return false;
}
-
- echo "<li>Table interwiki successfully populated</li>\n";
+ return true;
}
function strencode( $s ) {
@@ -1036,35 +1132,42 @@ class DatabaseOracle extends DatabaseBase {
}
function addQuotes( $s ) {
- global $wgLang;
- if ( isset( $wgLang->mLoaded ) && $wgLang->mLoaded ) {
- $s = $wgLang->checkTitleEncoding( $s );
+ global $wgContLang;
+ if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
+ $s = $wgContLang->checkTitleEncoding( $s );
}
return "'" . $this->strencode( $s ) . "'";
}
- function quote_ident( $s ) {
+ public function addIdentifierQuotes( $s ) {
+ if ( !$this->mFlags & DBO_DDLMODE ) {
+ $s = '"' . str_replace( '"', '""', $s ) . '"';
+ }
return $s;
}
function selectRow( $table, $vars, $conds, $fname = 'DatabaseOracle::selectRow', $options = array(), $join_conds = array() ) {
- global $wgLang;
+ global $wgContLang;
- $conds2 = array();
- $conds = ($conds != null && !is_array($conds)) ? array($conds) : $conds;
- foreach ( $conds as $col => $val ) {
- $col_info = $this->fieldInfoMulti( $table, $col );
- $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
- if ( $col_type == 'CLOB' ) {
- $conds2['TO_CHAR(' . $col . ')'] = $wgLang->checkTitleEncoding( $val );
- } elseif ( $col_type == 'VARCHAR2' && !mb_check_encoding( $val ) ) {
- $conds2[$col] = $wgLang->checkTitleEncoding( $val );
- } else {
- $conds2[$col] = $val;
+ if ($conds != null) {
+ $conds2 = array();
+ $conds = ( !is_array( $conds ) ) ? array( $conds ) : $conds;
+ foreach ( $conds as $col => $val ) {
+ $col_info = $this->fieldInfoMulti( $table, $col );
+ $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
+ if ( $col_type == 'CLOB' ) {
+ $conds2['TO_CHAR(' . $col . ')'] = $wgContLang->checkTitleEncoding( $val );
+ } elseif ( $col_type == 'VARCHAR2' && !mb_check_encoding( $val ) ) {
+ $conds2[$col] = $wgContLang->checkTitleEncoding( $val );
+ } else {
+ $conds2[$col] = $val;
+ }
}
- }
- return parent::selectRow( $table, $vars, $conds2, $fname, $options, $join_conds );
+ return parent::selectRow( $table, $vars, $conds2, $fname, $options, $join_conds );
+ } else {
+ return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
+ }
}
/**
@@ -1111,24 +1214,24 @@ class DatabaseOracle extends DatabaseBase {
}
public function delete( $table, $conds, $fname = 'DatabaseOracle::delete' ) {
- global $wgLang;
+ global $wgContLang;
- if ( $wgLang != null ) {
+ if ( $wgContLang != null && $conds != null && $conds != '*' ) {
$conds2 = array();
- $conds = ($conds != null && !is_array($conds)) ? array($conds) : $conds;
+ $conds = ( !is_array( $conds ) ) ? array( $conds ) : $conds;
foreach ( $conds as $col => $val ) {
$col_info = $this->fieldInfoMulti( $table, $col );
$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
if ( $col_type == 'CLOB' ) {
- $conds2['TO_CHAR(' . $col . ')'] = $wgLang->checkTitleEncoding( $val );
+ $conds2['TO_CHAR(' . $col . ')'] = $wgContLang->checkTitleEncoding( $val );
} else {
if ( is_array( $val ) ) {
$conds2[$col] = $val;
foreach ( $conds2[$col] as &$val2 ) {
- $val2 = $wgLang->checkTitleEncoding( $val2 );
+ $val2 = $wgContLang->checkTitleEncoding( $val2 );
}
} else {
- $conds2[$col] = $wgLang->checkTitleEncoding( $val );
+ $conds2[$col] = $wgContLang->checkTitleEncoding( $val );
}
}
}
@@ -1139,9 +1242,103 @@ class DatabaseOracle extends DatabaseBase {
}
}
+ function update( $table, $values, $conds, $fname = 'DatabaseOracle::update', $options = array() ) {
+ global $wgContLang;
+
+ $table = $this->tableName( $table );
+ $opts = $this->makeUpdateOptions( $options );
+ $sql = "UPDATE $opts $table SET ";
+
+ $first = true;
+ foreach ( $values as $col => &$val ) {
+ $sqlSet = $this->fieldBindStatement( $table, $col, $val, true );
+
+ if ( !$first ) {
+ $sqlSet = ', ' . $sqlSet;
+ } else {
+ $first = false;
+ }
+ $sql .= $sqlSet;
+ }
+
+ if ( $conds != '*' ) {
+ $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ }
+
+ if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
+ $e = oci_error( $this->mConn );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ return false;
+ }
+ foreach ( $values as $col => &$val ) {
+ $col_info = $this->fieldInfoMulti( $table, $col );
+ $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
+
+ if ( $val === null ) {
+ // do nothing ... null was inserted in statement creation
+ } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
+ if ( is_object( $val ) ) {
+ $val = $val->getData();
+ }
+
+ if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
+ $val = '31-12-2030 12:00:00.000000';
+ }
+
+ $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
+ if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
+ $e = oci_error( $stmt );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ return false;
+ }
+ } else {
+ if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
+ $e = oci_error( $stmt );
+ throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
+ }
+
+ if ( $col_type == 'BLOB' ) {
+ $lob[$col]->writeTemporary( $val );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, SQLT_BLOB );
+ } else {
+ $lob[$col]->writeTemporary( $val );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
+ }
+ }
+ }
+
+ wfSuppressWarnings();
+
+ if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
+ $e = oci_error( $stmt );
+ if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ return false;
+ } else {
+ $this->mAffectedRows = oci_num_rows( $stmt );
+ }
+ } else {
+ $this->mAffectedRows = oci_num_rows( $stmt );
+ }
+
+ wfRestoreWarnings();
+
+ if ( isset( $lob ) ) {
+ foreach ( $lob as $lob_v ) {
+ $lob_v->free();
+ }
+ }
+
+ if ( !$this->mTrxLevel ) {
+ oci_commit( $this->mConn );
+ }
+
+ oci_free_statement( $stmt );
+ }
+
function bitNot( $field ) {
// expecting bit-fields smaller than 4bytes
- return 'BITNOT(' . $bitField . ')';
+ return 'BITNOT(' . $field . ')';
}
function bitAnd( $fieldLeft, $fieldRight ) {
@@ -1152,17 +1349,6 @@ class DatabaseOracle extends DatabaseBase {
return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
}
- /**
- * How lagged is this slave?
- *
- * @return int
- */
- public function getLag() {
- # Not implemented for Oracle
- return 0;
- }
-
- function setFakeSlaveLag( $lag ) { }
function setFakeMaster( $enabled = true ) { }
function getDBname() {
@@ -1173,26 +1359,6 @@ class DatabaseOracle extends DatabaseBase {
return $this->mServer;
}
- public function replaceVars( $ins ) {
- $varnames = array( 'wgDBprefix' );
- if ( $this->mFlags & DBO_SYSDBA ) {
- $varnames[] = 'wgDBOracleDefTS';
- $varnames[] = 'wgDBOracleTempTS';
- }
-
- // Ordinary variables
- foreach ( $varnames as $var ) {
- if ( isset( $GLOBALS[$var] ) ) {
- $val = addslashes( $GLOBALS[$var] ); // FIXME: safety check?
- $ins = str_replace( '{$' . $var . '}', $val, $ins );
- $ins = str_replace( '/*$' . $var . '*/`', '`' . $val, $ins );
- $ins = str_replace( '/*$' . $var . '*/', $val, $ins );
- }
- }
-
- return parent::replaceVars( $ins );
- }
-
public function getSearchEngine() {
return 'SearchOracle';
}
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index 9072a5b2..bc71a9a5 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -1,46 +1,59 @@
<?php
/**
- * @ingroup Database
- * @file
* This is the Postgres database abstraction layer.
*
+ * @file
+ * @ingroup Database
*/
-class PostgresField {
- private $name, $tablename, $type, $nullable, $max_length;
+
+class PostgresField implements Field {
+ private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname;
static function fromText($db, $table, $field) {
global $wgDBmwschema;
$q = <<<SQL
SELECT
-CASE WHEN typname = 'int2' THEN 'smallint'
-WHEN typname = 'int4' THEN 'integer'
-WHEN typname = 'int8' THEN 'bigint'
-WHEN typname = 'bpchar' THEN 'char'
-ELSE typname END AS typname,
-attnotnull, attlen
-FROM pg_class, pg_namespace, pg_attribute, pg_type
-WHERE relnamespace=pg_namespace.oid
-AND relkind='r'
-AND attrelid=pg_class.oid
-AND atttypid=pg_type.oid
+ attnotnull, attlen, COALESCE(conname, '') AS conname,
+ COALESCE(condeferred, 'f') AS deferred,
+ COALESCE(condeferrable, 'f') AS deferrable,
+ CASE WHEN typname = 'int2' THEN 'smallint'
+ WHEN typname = 'int4' THEN 'integer'
+ WHEN typname = 'int8' THEN 'bigint'
+ WHEN typname = 'bpchar' THEN 'char'
+ ELSE typname END AS typname
+FROM pg_class c
+JOIN pg_namespace n ON (n.oid = c.relnamespace)
+JOIN pg_attribute a ON (a.attrelid = c.oid)
+JOIN pg_type t ON (t.oid = a.atttypid)
+LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
+WHERE relkind = 'r'
AND nspname=%s
AND relname=%s
AND attname=%s;
SQL;
- $res = $db->query(sprintf($q,
- $db->addQuotes($wgDBmwschema),
- $db->addQuotes($table),
- $db->addQuotes($field)));
- $row = $db->fetchObject($res);
- if (!$row)
+
+ $table = $db->tableName( $table );
+ $res = $db->query(
+ sprintf( $q,
+ $db->addQuotes( $wgDBmwschema ),
+ $db->addQuotes( $table ),
+ $db->addQuotes( $field )
+ )
+ );
+ $row = $db->fetchObject( $res );
+ if ( !$row ) {
return null;
+ }
$n = new PostgresField;
$n->type = $row->typname;
- $n->nullable = ($row->attnotnull == 'f');
+ $n->nullable = ( $row->attnotnull == 'f' );
$n->name = $field;
$n->tablename = $table;
$n->max_length = $row->attlen;
+ $n->deferrable = ( $row->deferrable == 't' );
+ $n->deferred = ( $row->deferred == 't' );
+ $n->conname = $row->conname;
return $n;
}
@@ -56,13 +69,26 @@ SQL;
return $this->type;
}
- function nullable() {
+ function isNullable() {
return $this->nullable;
}
function maxLength() {
return $this->max_length;
}
+
+ function is_deferrable() {
+ return $this->deferrable;
+ }
+
+ function is_deferred() {
+ return $this->deferred;
+ }
+
+ function conname() {
+ return $this->conname;
+ }
+
}
/**
@@ -74,16 +100,6 @@ class DatabasePostgres extends DatabaseBase {
var $numeric_version = null;
var $mAffectedRows = null;
- function DatabasePostgres($server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0 )
- {
-
- $this->mFailFunction = $failFunction;
- $this->mFlags = $flags;
- $this->open( $server, $user, $password, $dbName);
-
- }
-
function getType() {
return 'postgres';
}
@@ -115,18 +131,18 @@ class DatabasePostgres extends DatabaseBase {
function hasConstraint( $name ) {
global $wgDBmwschema;
- $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" . pg_escape_string( $name ) . "' AND n.nspname = '" . pg_escape_string($wgDBmwschema) ."'";
- return $this->numRows($res = $this->doQuery($SQL));
+ $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" .
+ pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $wgDBmwschema ) ."'";
+ $res = $this->doQuery( $SQL );
+ return $this->numRows( $res );
}
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0)
- {
- return new DatabasePostgres( $server, $user, $password, $dbName, $failFunction, $flags );
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
+ return new DatabasePostgres( $server, $user, $password, $dbName, $flags );
}
/**
* Usually aborts on failure
- * If the failFunction is set to a non-zero integer, returns success
*/
function open( $server, $user, $password, $dbName ) {
# Test for Postgres support, to avoid suppressed fatal error
@@ -136,7 +152,7 @@ class DatabasePostgres extends DatabaseBase {
global $wgDBport;
- if (!strlen($user)) { ## e.g. the class is being loaded
+ if ( !strlen( $user ) ) { # e.g. the class is being loaded
return;
}
$this->close();
@@ -149,11 +165,12 @@ class DatabasePostgres extends DatabaseBase {
$connectVars = array(
'dbname' => $dbName,
'user' => $user,
- 'password' => $password );
- if ($server!=false && $server!="") {
+ 'password' => $password
+ );
+ if ( $server != false && $server != '' ) {
$connectVars['host'] = $server;
}
- if ($port!=false && $port!="") {
+ if ( $port != false && $port != '' ) {
$connectVars['port'] = $port;
}
$connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
@@ -162,35 +179,30 @@ class DatabasePostgres extends DatabaseBase {
$this->mConn = pg_connect( $connectString );
$phpError = $this->restoreErrorHandler();
- if ( $this->mConn == false ) {
+ if ( !$this->mConn ) {
wfDebug( "DB connection error\n" );
wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- wfDebug( $this->lastError()."\n" );
- if ( !$this->mFailFunction ) {
- throw new DBConnectionError( $this, $phpError );
- } else {
- return false;
- }
+ wfDebug( $this->lastError() . "\n" );
+ throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
}
$this->mOpened = true;
global $wgCommandLineMode;
- ## If called from the command-line (e.g. importDump), only show errors
- if ($wgCommandLineMode) {
+ # If called from the command-line (e.g. importDump), only show errors
+ if ( $wgCommandLineMode ) {
$this->doQuery( "SET client_min_messages = 'ERROR'" );
}
$this->doQuery( "SET client_encoding='UTF8'" );
global $wgDBmwschema, $wgDBts2schema;
- if (isset( $wgDBmwschema ) && isset( $wgDBts2schema )
+ if ( isset( $wgDBmwschema ) && isset( $wgDBts2schema )
&& $wgDBmwschema !== 'mediawiki'
&& preg_match( '/^\w+$/', $wgDBmwschema )
&& preg_match( '/^\w+$/', $wgDBts2schema )
) {
- $safeschema = $this->quote_ident($wgDBmwschema);
- $safeschema2 = $this->quote_ident($wgDBts2schema);
+ $safeschema = $this->addIdentifierQuotes( $wgDBmwschema );
$this->doQuery( "SET search_path = $safeschema, $wgDBts2schema, public" );
}
@@ -205,365 +217,6 @@ class DatabasePostgres extends DatabaseBase {
return $s;
}
-
- function initial_setup($password, $dbName) {
- // If this is the initial connection, setup the schema stuff and possibly create the user
- global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBsuperuser, $wgDBmwschema, $wgDBts2schema;
-
- print "<li>Checking the version of Postgres...";
- $version = $this->getServerVersion();
- $PGMINVER = '8.1';
- if ($version < $PGMINVER) {
- print "<b>FAILED</b>. Required version is $PGMINVER. You have " . htmlspecialchars( $version ) . "</li>\n";
- dieout("</ul>");
- }
- print "version " . htmlspecialchars( $this->numeric_version ) . " is OK.</li>\n";
-
- $safeuser = $this->quote_ident($wgDBuser);
- // Are we connecting as a superuser for the first time?
- if ($wgDBsuperuser) {
- // Are we really a superuser? Check out our rights
- $SQL = "SELECT
- CASE WHEN usesuper IS TRUE THEN
- CASE WHEN usecreatedb IS TRUE THEN 3 ELSE 1 END
- ELSE CASE WHEN usecreatedb IS TRUE THEN 2 ELSE 0 END
- END AS rights
- FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBsuperuser);
- $rows = $this->numRows($res = $this->doQuery($SQL));
- if (!$rows) {
- print "<li>ERROR: Could not read permissions for user \"" . htmlspecialchars( $wgDBsuperuser ) . "\"</li>\n";
- dieout('</ul>');
- }
- $perms = pg_fetch_result($res, 0, 0);
-
- $SQL = "SELECT 1 FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBuser);
- $rows = $this->numRows($this->doQuery($SQL));
- if ($rows) {
- print "<li>User \"" . htmlspecialchars( $wgDBuser ) . "\" already exists, skipping account creation.</li>";
- }
- else {
- if ($perms != 1 and $perms != 3) {
- print "<li>ERROR: the user \"" . htmlspecialchars( $wgDBsuperuser ) . "\" cannot create other users. ";
- print 'Please use a different Postgres user.</li>';
- dieout('</ul>');
- }
- print "<li>Creating user <b>" . htmlspecialchars( $wgDBuser ) . "</b>...";
- $safepass = $this->addQuotes($wgDBpassword);
- $SQL = "CREATE USER $safeuser NOCREATEDB PASSWORD $safepass";
- $this->doQuery($SQL);
- print "OK</li>\n";
- }
- // User now exists, check out the database
- if ($dbName != $wgDBname) {
- $SQL = "SELECT 1 FROM pg_catalog.pg_database WHERE datname = " . $this->addQuotes($wgDBname);
- $rows = $this->numRows($this->doQuery($SQL));
- if ($rows) {
- print "<li>Database \"" . htmlspecialchars( $wgDBname ) . "\" already exists, skipping database creation.</li>";
- }
- else {
- if ($perms < 1) {
- print "<li>ERROR: the user \"" . htmlspecialchars( $wgDBsuperuser ) . "\" cannot create databases. ";
- print 'Please use a different Postgres user.</li>';
- dieout('</ul>');
- }
- print "<li>Creating database <b>" . htmlspecialchars( $wgDBname ) . "</b>...";
- $safename = $this->quote_ident($wgDBname);
- $SQL = "CREATE DATABASE $safename OWNER $safeuser ";
- $this->doQuery($SQL);
- print "OK</li>\n";
- // Hopefully tsearch2 and plpgsql are in template1...
- }
-
- // Reconnect to check out tsearch2 rights for this user
- print "<li>Connecting to \"" . htmlspecialchars( $wgDBname ) . "\" as superuser \"" .
- htmlspecialchars( $wgDBsuperuser ) . "\" to check rights...";
-
- $connectVars = array();
- if ($this->mServer!=false && $this->mServer!="") {
- $connectVars['host'] = $this->mServer;
- }
- if ($this->mPort!=false && $this->mPort!="") {
- $connectVars['port'] = $this->mPort;
- }
- $connectVars['dbname'] = $wgDBname;
- $connectVars['user'] = $wgDBsuperuser;
- $connectVars['password'] = $password;
-
- @$this->mConn = pg_connect( $this->makeConnectionString( $connectVars ) );
- if ( $this->mConn == false ) {
- print "<b>FAILED TO CONNECT!</b></li>";
- dieout("</ul>");
- }
- print "OK</li>\n";
- }
-
- if ($this->numeric_version < 8.3) {
- // Tsearch2 checks
- print "<li>Checking that tsearch2 is installed in the database \"" .
- htmlspecialchars( $wgDBname ) . "\"...";
- if (! $this->tableExists("pg_ts_cfg", $wgDBts2schema)) {
- print "<b>FAILED</b>. tsearch2 must be installed in the database \"" .
- htmlspecialchars( $wgDBname ) . "\".";
- print "Please see <a href='http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>";
- print " for instructions or ask on #postgresql on irc.freenode.net</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- print "<li>Ensuring that user \"" . htmlspecialchars( $wgDBuser ) .
- "\" has select rights on the tsearch2 tables...";
- foreach (array('cfg','cfgmap','dict','parser') as $table) {
- $SQL = "GRANT SELECT ON pg_ts_$table TO $safeuser";
- $this->doQuery($SQL);
- }
- print "OK</li>\n";
- }
-
- // Setup the schema for this user if needed
- $result = $this->schemaExists($wgDBmwschema);
- $safeschema = $this->quote_ident($wgDBmwschema);
- if (!$result) {
- print "<li>Creating schema <b>" . htmlspecialchars( $wgDBmwschema ) . "</b> ...";
- $result = $this->doQuery("CREATE SCHEMA $safeschema AUTHORIZATION $safeuser");
- if (!$result) {
- print "<b>FAILED</b>.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- }
- else {
- print "<li>Schema already exists, explicitly granting rights...\n";
- $safeschema2 = $this->addQuotes($wgDBmwschema);
- $SQL = "SELECT 'GRANT ALL ON '||pg_catalog.quote_ident(relname)||' TO $safeuser;'\n".
- "FROM pg_catalog.pg_class p, pg_catalog.pg_namespace n\n".
- "WHERE relnamespace = n.oid AND n.nspname = $safeschema2\n".
- "AND p.relkind IN ('r','S','v')\n";
- $SQL .= "UNION\n";
- $SQL .= "SELECT 'GRANT ALL ON FUNCTION '||pg_catalog.quote_ident(proname)||'('||\n".
- "pg_catalog.oidvectortypes(p.proargtypes)||') TO $safeuser;'\n".
- "FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n\n".
- "WHERE p.pronamespace = n.oid AND n.nspname = $safeschema2";
- $res = $this->doQuery($SQL);
- if (!$res) {
- print "<b>FAILED</b>. Could not set rights for the user.</li>\n";
- dieout("</ul>");
- }
- $this->doQuery("SET search_path = $safeschema");
- $rows = $this->numRows($res);
- while ($rows) {
- $rows--;
- $this->doQuery(pg_fetch_result($res, $rows, 0));
- }
- print "OK</li>";
- }
-
- // Install plpgsql if needed
- $this->setup_plpgsql();
-
- $wgDBsuperuser = '';
- return true; // Reconnect as regular user
-
- } // end superuser
-
- if (!defined('POSTGRES_SEARCHPATH')) {
-
- if ($this->numeric_version < 8.3) {
- // Do we have the basic tsearch2 table?
- print "<li>Checking for tsearch2 in the schema \"" . htmlspecialchars( $wgDBts2schema ) . "\"...";
- if (! $this->tableExists("pg_ts_dict", $wgDBts2schema)) {
- print "<b>FAILED</b>. Make sure tsearch2 is installed. See <a href=";
- print "'http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>";
- print " for instructions.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
-
- // Does this user have the rights to the tsearch2 tables?
- $ctype = pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0);
- print "<li>Checking tsearch2 permissions...";
- // Let's check all four, just to be safe
- error_reporting( 0 );
- $ts2tables = array('cfg','cfgmap','dict','parser');
- $safetsschema = $this->quote_ident($wgDBts2schema);
- foreach ( $ts2tables AS $tname ) {
- $SQL = "SELECT count(*) FROM $safetsschema.pg_ts_$tname";
- $res = $this->doQuery($SQL);
- if (!$res) {
- print "<b>FAILED</b> to access " . htmlspecialchars( "pg_ts_$tname" ) .
- ". Make sure that the user \"". htmlspecialchars( $wgDBuser ) .
- "\" has SELECT access to all four tsearch2 tables</li>\n";
- dieout("</ul>");
- }
- }
- $SQL = "SELECT ts_name FROM $safetsschema.pg_ts_cfg WHERE locale = " . $this->addQuotes( $ctype ) ;
- $SQL .= " ORDER BY CASE WHEN ts_name <> 'default' THEN 1 ELSE 0 END";
- $res = $this->doQuery($SQL);
- error_reporting( E_ALL );
- if (!$res) {
- print "<b>FAILED</b>. Could not determine the tsearch2 locale information</li>\n";
- dieout("</ul>");
- }
- print "OK</li>";
-
- // Will the current locale work? Can we force it to?
- print "<li>Verifying tsearch2 locale with " . htmlspecialchars( $ctype ) . "...";
- $rows = $this->numRows($res);
- $resetlocale = 0;
- if (!$rows) {
- print "<b>not found</b></li>\n";
- print "<li>Attempting to set default tsearch2 locale to \"" . htmlspecialchars( $ctype ) . "\"...";
- $resetlocale = 1;
- }
- else {
- $tsname = pg_fetch_result($res, 0, 0);
- if ($tsname != 'default') {
- print "<b>not set to default (" . htmlspecialchars( $tsname ) . ")</b>";
- print "<li>Attempting to change tsearch2 default locale to \"" .
- htmlspecialchars( $ctype ) . "\"...";
- $resetlocale = 1;
- }
- }
- if ($resetlocale) {
- $SQL = "UPDATE $safetsschema.pg_ts_cfg SET locale = " . $this->addQuotes( $ctype ) . " WHERE ts_name = 'default'";
- $res = $this->doQuery($SQL);
- if (!$res) {
- print "<b>FAILED</b>. ";
- print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"" .
- htmlspecialchars( $ctype ) . "\"</li>\n";
- dieout("</ul>");
- }
- print "OK</li>";
- }
-
- // Final test: try out a simple tsearch2 query
- $SQL = "SELECT $safetsschema.to_tsvector('default','MediaWiki tsearch2 testing')";
- $res = $this->doQuery($SQL);
- if (!$res) {
- print "<b>FAILED</b>. Specifically, \"" . htmlspecialchars( $SQL ) . "\" did not work.</li>";
- dieout("</ul>");
- }
- print "OK</li>";
- }
-
- // Install plpgsql if needed
- $this->setup_plpgsql();
-
- // Does the schema already exist? Who owns it?
- $result = $this->schemaExists($wgDBmwschema);
- if (!$result) {
- print "<li>Creating schema <b>" . htmlspecialchars( $wgDBmwschema ) . "</b> ...";
- error_reporting( 0 );
- $safeschema = $this->quote_ident($wgDBmwschema);
- $result = $this->doQuery("CREATE SCHEMA $safeschema");
- error_reporting( E_ALL );
- if (!$result) {
- print "<b>FAILED</b>. The user \"" . htmlspecialchars( $wgDBuser ) .
- "\" must be able to access the schema. ".
- "You can try making them the owner of the database, or try creating the schema with a ".
- "different user, and then grant access to the \"" .
- htmlspecialchars( $wgDBuser ) . "\" user.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- }
- else if ($result != $wgDBuser) {
- print "<li>Schema \"" . htmlspecialchars( $wgDBmwschema ) . "\" exists but is not owned by \"" .
- htmlspecialchars( $wgDBuser ) . "\". Not ideal.</li>\n";
- }
- else {
- print "<li>Schema \"" . htmlspecialchars( $wgDBmwschema ) . "\" exists and is owned by \"" .
- htmlspecialchars( $wgDBuser ) . "\". Excellent.</li>\n";
- }
-
- // Always return GMT time to accomodate the existing integer-based timestamp assumption
- print "<li>Setting the timezone to GMT for user \"" . htmlspecialchars( $wgDBuser ) . "\" ...";
- $SQL = "ALTER USER $safeuser SET timezone = 'GMT'";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<b>FAILED</b>.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- // Set for the rest of this session
- $SQL = "SET timezone = 'GMT'";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<li>Failed to set timezone</li>\n";
- dieout("</ul>");
- }
-
- print "<li>Setting the datestyle to ISO, YMD for user \"" . htmlspecialchars( $wgDBuser ) . "\" ...";
- $SQL = "ALTER USER $safeuser SET datestyle = 'ISO, YMD'";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<b>FAILED</b>.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- // Set for the rest of this session
- $SQL = "SET datestyle = 'ISO, YMD'";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<li>Failed to set datestyle</li>\n";
- dieout("</ul>");
- }
-
- // Fix up the search paths if needed
- print "<li>Setting the search path for user \"" . htmlspecialchars( $wgDBuser ) . "\" ...";
- $path = $this->quote_ident($wgDBmwschema);
- if ($wgDBts2schema !== $wgDBmwschema)
- $path .= ", ". $this->quote_ident($wgDBts2schema);
- if ($wgDBmwschema !== 'public' and $wgDBts2schema !== 'public')
- $path .= ", public";
- $SQL = "ALTER USER $safeuser SET search_path = $path";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<b>FAILED</b>.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- // Set for the rest of this session
- $SQL = "SET search_path = $path";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<li>Failed to set search_path</li>\n";
- dieout("</ul>");
- }
- define( "POSTGRES_SEARCHPATH", $path );
- }
- }
-
-
- function setup_plpgsql() {
- print "<li>Checking for Pl/Pgsql ...";
- $SQL = "SELECT 1 FROM pg_catalog.pg_language WHERE lanname = 'plpgsql'";
- $rows = $this->numRows($this->doQuery($SQL));
- if ($rows < 1) {
- // plpgsql is not installed, but if we have a pg_pltemplate table, we should be able to create it
- print "not installed. Attempting to install Pl/Pgsql ...";
- $SQL = "SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) ".
- "WHERE relname = 'pg_pltemplate' AND nspname='pg_catalog'";
- $rows = $this->numRows($this->doQuery($SQL));
- if ($rows >= 1) {
- $olde = error_reporting(0);
- error_reporting($olde - E_WARNING);
- $result = $this->doQuery("CREATE LANGUAGE plpgsql");
- error_reporting($olde);
- if (!$result) {
- print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>" .
- htmlspecialchars( $wgDBname ) . "</tt></li>";
- dieout("</ul>");
- }
- }
- else {
- print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>" .
- htmlspecialchars( $wgDBname ) . "</tt></li>";
- dieout("</ul>");
- }
- }
- print "OK</li>\n";
- }
-
-
/**
* Closes a database connection, if it is open
* Returns success, true if already closed
@@ -578,15 +231,15 @@ class DatabasePostgres extends DatabaseBase {
}
function doQuery( $sql ) {
- if (function_exists('mb_convert_encoding')) {
- $sql = mb_convert_encoding($sql,'UTF-8');
+ if ( function_exists( 'mb_convert_encoding' ) ) {
+ $sql = mb_convert_encoding( $sql, 'UTF-8' );
}
- $this->mLastResult = pg_query( $this->mConn, $sql);
+ $this->mLastResult = pg_query( $this->mConn, $sql );
$this->mAffectedRows = null; // use pg_affected_rows(mLastResult)
return $this->mLastResult;
}
- function queryIgnore( $sql, $fname = '' ) {
+ function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) {
return $this->query( $sql, $fname, true );
}
@@ -595,7 +248,7 @@ class DatabasePostgres extends DatabaseBase {
$res = $res->result;
}
if ( !@pg_free_result( $res ) ) {
- throw new DBUnexpectedError($this, "Unable to free Postgres result\n" );
+ throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
}
}
@@ -609,8 +262,8 @@ class DatabasePostgres extends DatabaseBase {
# TODO:
# hashar : not sure if the following test really trigger if the object
# fetching failed.
- if( pg_last_error($this->mConn) ) {
- throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
+ if( pg_last_error( $this->mConn ) ) {
+ throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
return $row;
}
@@ -620,8 +273,8 @@ class DatabasePostgres extends DatabaseBase {
$res = $res->result;
}
@$row = pg_fetch_array( $res );
- if( pg_last_error($this->mConn) ) {
- throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
+ if( pg_last_error( $this->mConn ) ) {
+ throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
return $row;
}
@@ -631,17 +284,19 @@ class DatabasePostgres extends DatabaseBase {
$res = $res->result;
}
@$n = pg_num_rows( $res );
- if( pg_last_error($this->mConn) ) {
- throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
+ if( pg_last_error( $this->mConn ) ) {
+ throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
return $n;
}
+
function numFields( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
return pg_num_fields( $res );
}
+
function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -666,9 +321,8 @@ class DatabasePostgres extends DatabaseBase {
function lastError() {
if ( $this->mConn ) {
return pg_last_error();
- }
- else {
- return "No database connection";
+ } else {
+ return 'No database connection';
}
}
function lastErrno() {
@@ -680,8 +334,9 @@ class DatabasePostgres extends DatabaseBase {
// Forced result for simulated queries
return $this->mAffectedRows;
}
- if( empty( $this->mLastResult ) )
+ if( empty( $this->mLastResult ) ) {
return 0;
+ }
return pg_affected_rows( $this->mLastResult );
}
@@ -692,8 +347,7 @@ class DatabasePostgres extends DatabaseBase {
* Returns -1 if count cannot be found
* Takes same arguments as Database::select()
*/
-
- function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
+ function estimateRowCount( $table, $vars = '*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
@@ -703,12 +357,10 @@ class DatabasePostgres extends DatabaseBase {
if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
$rows = $count[1];
}
- $this->freeResult($res);
}
return $rows;
}
-
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
@@ -719,7 +371,7 @@ class DatabasePostgres extends DatabaseBase {
if ( !$res ) {
return null;
}
- while ( $row = $this->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( $row->indexname == $this->indexName( $index ) ) {
return $row;
}
@@ -727,18 +379,19 @@ class DatabasePostgres extends DatabaseBase {
return false;
}
- function indexUnique ($table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
+ function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
$sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
- " AND indexdef LIKE 'CREATE UNIQUE%(" .
+ " AND indexdef LIKE 'CREATE UNIQUE%(" .
$this->strencode( $this->indexName( $index ) ) .
")'";
$res = $this->query( $sql, $fname );
- if ( !$res )
+ if ( !$res ) {
return null;
- while ($row = $this->fetchObject( $res ))
+ }
+ foreach ( $res as $row ) {
return true;
+ }
return false;
-
}
/**
@@ -755,25 +408,23 @@ class DatabasePostgres extends DatabaseBase {
* @return bool Success of insert operation. IGNORE always returns true.
*/
function insert( $table, $args, $fname = 'DatabasePostgres::insert', $options = array() ) {
- global $wgDBversion;
-
if ( !count( $args ) ) {
return true;
}
$table = $this->tableName( $table );
- if (! isset( $wgDBversion ) ) {
- $wgDBversion = $this->getServerVersion();
+ if (! isset( $this->numeric_version ) ) {
+ $this->getServerVersion();
}
- if ( !is_array( $options ) )
+ if ( !is_array( $options ) ) {
$options = array( $options );
+ }
if ( isset( $args[0] ) && is_array( $args[0] ) ) {
$multi = true;
$keys = array_keys( $args[0] );
- }
- else {
+ } else {
$multi = false;
$keys = array_keys( $args );
}
@@ -784,7 +435,7 @@ class DatabasePostgres extends DatabaseBase {
// If we are not in a transaction, we need to be for savepoint trickery
$didbegin = 0;
if ( $ignore ) {
- if (! $this->mTrxLevel) {
+ if ( !$this->mTrxLevel ) {
$this->begin();
$didbegin = 1;
}
@@ -797,7 +448,7 @@ class DatabasePostgres extends DatabaseBase {
$sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
if ( $multi ) {
- if ( $wgDBversion >= 8.2 && !$ignore ) {
+ if ( $this->numeric_version >= 8.2 && !$ignore ) {
$first = true;
foreach ( $args as $row ) {
if ( $first ) {
@@ -808,8 +459,7 @@ class DatabasePostgres extends DatabaseBase {
$sql .= '(' . $this->makeList( $row ) . ')';
}
$res = (bool)$this->query( $sql, $fname, $ignore );
- }
- else {
+ } else {
$res = true;
$origsql = $sql;
foreach ( $args as $row ) {
@@ -817,17 +467,16 @@ class DatabasePostgres extends DatabaseBase {
$tempsql .= '(' . $this->makeList( $row ) . ')';
if ( $ignore ) {
- pg_query($this->mConn, "SAVEPOINT $ignore");
+ pg_query( $this->mConn, "SAVEPOINT $ignore" );
}
$tempres = (bool)$this->query( $tempsql, $fname, $ignore );
if ( $ignore ) {
$bar = pg_last_error();
- if ($bar != false) {
+ if ( $bar != false ) {
pg_query( $this->mConn, "ROLLBACK TO $ignore" );
- }
- else {
+ } else {
pg_query( $this->mConn, "RELEASE $ignore" );
$numrowsinserted++;
}
@@ -835,12 +484,12 @@ class DatabasePostgres extends DatabaseBase {
// If any of them fail, we fail overall for this function call
// Note that this will be ignored if IGNORE is set
- if (! $tempres)
+ if ( !$tempres ) {
$res = false;
+ }
}
}
- }
- else {
+ } else {
// Not multi, just a lone insert
if ( $ignore ) {
pg_query($this->mConn, "SAVEPOINT $ignore");
@@ -850,10 +499,9 @@ class DatabasePostgres extends DatabaseBase {
$res = (bool)$this->query( $sql, $fname, $ignore );
if ( $ignore ) {
$bar = pg_last_error();
- if ($bar != false) {
+ if ( $bar != false ) {
pg_query( $this->mConn, "ROLLBACK TO $ignore" );
- }
- else {
+ } else {
pg_query( $this->mConn, "RELEASE $ignore" );
$numrowsinserted++;
}
@@ -861,7 +509,7 @@ class DatabasePostgres extends DatabaseBase {
}
if ( $ignore ) {
$olde = error_reporting( $olde );
- if ($didbegin) {
+ if ( $didbegin ) {
$this->commit();
}
@@ -872,9 +520,7 @@ class DatabasePostgres extends DatabaseBase {
return true;
}
-
return $res;
-
}
/**
@@ -884,7 +530,7 @@ class DatabasePostgres extends DatabaseBase {
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
* @todo FIXME: implement this a little better (seperate select/insert)?
- */
+ */
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect',
$insertOptions = array(), $selectOptions = array() )
{
@@ -922,7 +568,7 @@ class DatabasePostgres extends DatabaseBase {
" SELECT $startOpts " . implode( ',', $varMap ) .
" FROM $srcTable $useIndex";
- if ( $conds != '*') {
+ if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
@@ -951,7 +597,7 @@ class DatabasePostgres extends DatabaseBase {
return $res;
}
-
+
function tableName( $name ) {
# Replace reserved words with better ones
switch( $name ) {
@@ -968,11 +614,10 @@ class DatabasePostgres extends DatabaseBase {
* Return the next in a sequence, save the value for retrieval via insertId()
*/
function nextSequenceValue( $seqName ) {
- $safeseq = preg_replace( "/'/", "''", $seqName );
+ $safeseq = str_replace( "'", "''", $seqName );
$res = $this->query( "SELECT nextval('$safeseq')" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
- $this->freeResult( $res );
return $this->mInsertId;
}
@@ -980,27 +625,28 @@ class DatabasePostgres extends DatabaseBase {
* Return the current value of a sequence. Assumes it has been nextval'ed in this session.
*/
function currentSequenceValue( $seqName ) {
- $safeseq = preg_replace( "/'/", "''", $seqName );
+ $safeseq = str_replace( "'", "''", $seqName );
$res = $this->query( "SELECT currval('$safeseq')" );
$row = $this->fetchRow( $res );
$currval = $row[0];
- $this->freeResult( $res );
return $currval;
}
- # REPLACE query wrapper
- # Postgres simulates this with a DELETE followed by INSERT
- # $row is the row to insert, an associative array
- # $uniqueIndexes is an array of indexes. Each element may be either a
- # field name or an array of field names
- #
- # It may be more efficient to leave off unique indexes which are unlikely to collide.
- # However if you do this, you run the risk of encountering errors which wouldn't have
- # occurred in MySQL
+ /**
+ * REPLACE query wrapper
+ * Postgres simulates this with a DELETE followed by INSERT
+ * $row is the row to insert, an associative array
+ * $uniqueIndexes is an array of indexes. Each element may be either a
+ * field name or an array of field names
+ *
+ * It may be more efficient to leave off unique indexes which are unlikely to collide.
+ * However if you do this, you run the risk of encountering errors which wouldn't have
+ * occurred in MySQL
+ */
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabasePostgres::replace' ) {
$table = $this->tableName( $table );
- if (count($rows)==0) {
+ if ( count( $rows ) == 0 ) {
return;
}
@@ -1017,7 +663,7 @@ class DatabasePostgres extends DatabaseBase {
foreach ( $uniqueIndexes as $index ) {
if ( $first ) {
$first = false;
- $sql .= "(";
+ $sql .= '(';
} else {
$sql .= ') OR (';
}
@@ -1049,7 +695,7 @@ class DatabasePostgres extends DatabaseBase {
# DELETE where the condition is a join
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabasePostgres::deleteJoin' ) {
if ( !$conds ) {
- throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, 'DatabasePostgres::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
@@ -1070,19 +716,18 @@ class DatabasePostgres extends DatabaseBase {
FROM pg_class c, pg_attribute a, pg_type t
WHERE relname='$table' AND a.attrelid=c.oid AND
a.atttypid=t.oid and a.attname='$field'";
- $res =$this->query($sql);
- $row=$this->fetchObject($res);
- if ($row->ftype=="varchar") {
- $size=$row->size-4;
+ $res =$this->query( $sql );
+ $row = $this->fetchObject( $res );
+ if ( $row->ftype == 'varchar' ) {
+ $size = $row->size - 4;
} else {
- $size=$row->size;
+ $size = $row->size;
}
- $this->freeResult( $res );
return $size;
}
- function limitResult($sql, $limit, $offset=false) {
- return "$sql LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":"");
+ function limitResult( $sql, $limit, $offset = false ) {
+ return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
}
function wasDeadlock() {
@@ -1093,57 +738,40 @@ class DatabasePostgres extends DatabaseBase {
return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
}
- function timestamp( $ts=0 ) {
- return wfTimestamp(TS_POSTGRES,$ts);
+ function timestamp( $ts = 0 ) {
+ return wfTimestamp( TS_POSTGRES, $ts );
}
/**
* Return aggregated value function call
*/
- function aggregateValue ($valuedata,$valuename='value') {
+ function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
}
-
- 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) {
- wfDebug("SQL ERROR (ignored): $error\n");
- $this->ignoreErrors( $ignore );
- }
- else {
- $message = "A database error has occurred\n" .
- "Query: $sql\n" .
- "Function: $fname\n" .
- "Error: $errno $error\n";
- throw new DBUnexpectedError($this, $message);
- }
- }
-
/**
* @return string wikitext of a link to the server software's web site
*/
- function getSoftwareLink() {
- return "[http://www.postgresql.org/ PostgreSQL]";
+ public static function getSoftwareLink() {
+ return '[http://www.postgresql.org/ PostgreSQL]';
}
/**
* @return string Version information from the database
*/
function getServerVersion() {
- $versionInfo = pg_version( $this->mConn );
- if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
- // Old client, abort install
- $this->numeric_version = '7.3 or earlier';
- } elseif ( isset( $versionInfo['server'] ) ) {
- // Normal client
- $this->numeric_version = $versionInfo['server'];
- } else {
- // Bug 16937: broken pgsql extension from PHP<5.3
- $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' );
+ if ( !isset( $this->numeric_version ) ) {
+ $versionInfo = pg_version( $this->mConn );
+ if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
+ // Old client, abort install
+ $this->numeric_version = '7.3 or earlier';
+ } elseif ( isset( $versionInfo['server'] ) ) {
+ // Normal client
+ $this->numeric_version = $versionInfo['server'];
+ } else {
+ // Bug 16937: broken pgsql extension from PHP<5.3
+ $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' );
+ }
}
return $this->numeric_version;
}
@@ -1154,23 +782,23 @@ class DatabasePostgres extends DatabaseBase {
*/
function relationExists( $table, $types, $schema = false ) {
global $wgDBmwschema;
- if ( !is_array( $types ) )
+ if ( !is_array( $types ) ) {
$types = array( $types );
- if ( !$schema )
+ }
+ if ( !$schema ) {
$schema = $wgDBmwschema;
+ }
$etable = $this->addQuotes( $table );
$eschema = $this->addQuotes( $schema );
$SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
. "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
- . "AND c.relkind IN ('" . implode("','", $types) . "')";
+ . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
$res = $this->query( $SQL );
$count = $res ? $res->numRows() : 0;
- if ($res)
- $this->freeResult( $res );
- return $count ? true : false;
+ return (bool)$count;
}
- /*
+ /**
* For backward compatibility, this function checks both tables and
* views.
*/
@@ -1191,82 +819,71 @@ class DatabasePostgres extends DatabaseBase {
AND tgrelid=pg_class.oid
AND nspname=%s AND relname=%s AND tgname=%s
SQL;
- $res = $this->query(sprintf($q,
- $this->addQuotes($wgDBmwschema),
- $this->addQuotes($table),
- $this->addQuotes($trigger)));
- if (!$res)
+ $res = $this->query(
+ sprintf(
+ $q,
+ $this->addQuotes( $wgDBmwschema ),
+ $this->addQuotes( $table ),
+ $this->addQuotes( $trigger )
+ )
+ );
+ if ( !$res ) {
return null;
+ }
$rows = $res->numRows();
- $this->freeResult( $res );
return $rows;
}
function ruleExists( $table, $rule ) {
global $wgDBmwschema;
- $exists = $this->selectField("pg_rules", "rulename",
- array( "rulename" => $rule,
- "tablename" => $table,
- "schemaname" => $wgDBmwschema ) );
+ $exists = $this->selectField( 'pg_rules', 'rulename',
+ array(
+ 'rulename' => $rule,
+ 'tablename' => $table,
+ 'schemaname' => $wgDBmwschema
+ )
+ );
return $exists === $rule;
}
function constraintExists( $table, $constraint ) {
global $wgDBmwschema;
- $SQL = sprintf("SELECT 1 FROM information_schema.table_constraints ".
+ $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints ".
"WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
- $this->addQuotes($wgDBmwschema),
- $this->addQuotes($table),
- $this->addQuotes($constraint));
- $res = $this->query($SQL);
- if (!$res)
+ $this->addQuotes( $wgDBmwschema ),
+ $this->addQuotes( $table ),
+ $this->addQuotes( $constraint )
+ );
+ $res = $this->query( $SQL );
+ if ( !$res ) {
return null;
+ }
$rows = $res->numRows();
- $this->freeResult($res);
return $rows;
}
/**
- * Query whether a given schema exists. Returns the name of the owner
+ * Query whether a given schema exists. Returns true if it does, false if it doesn't.
*/
function schemaExists( $schema ) {
- $eschema = preg_replace("/'/", "''", $schema);
- $SQL = "SELECT rolname FROM pg_catalog.pg_namespace n, pg_catalog.pg_roles r "
- ."WHERE n.nspowner=r.oid AND n.nspname = '$eschema'";
- $res = $this->query( $SQL );
- if ( $res && $res->numRows() ) {
- $row = $res->fetchObject();
- $owner = $row->rolname;
- } else {
- $owner = false;
- }
- if ($res)
- $this->freeResult($res);
- return $owner;
+ $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
+ array( 'nspname' => $schema ), __METHOD__ );
+ return (bool)$exists;
}
/**
- * Query whether a given column exists in the mediawiki schema
+ * Returns true if a given role (i.e. user) exists, false otherwise.
*/
- function fieldExists( $table, $field, $fname = 'DatabasePostgres::fieldExists' ) {
- global $wgDBmwschema;
- $etable = preg_replace("/'/", "''", $table);
- $eschema = preg_replace("/'/", "''", $wgDBmwschema);
- $ecol = preg_replace("/'/", "''", $field);
- $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a "
- . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' "
- . "AND a.attrelid = c.oid AND a.attname = '$ecol'";
- $res = $this->query( $SQL, $fname );
- $count = $res ? $res->numRows() : 0;
- if ($res)
- $this->freeResult( $res );
- return $count;
+ function roleExists( $roleName ) {
+ $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
+ array( 'rolname' => $roleName ), __METHOD__ );
+ return (bool)$exists;
}
function fieldInfo( $table, $field ) {
- return PostgresField::fromText($this, $table, $field);
+ return PostgresField::fromText( $this, $table, $field );
}
-
+
/**
* pg_field_type() wrapper
*/
@@ -1277,119 +894,35 @@ SQL;
return pg_field_type( $res, $index );
}
- function begin( $fname = 'DatabasePostgres::begin' ) {
- $this->query( 'BEGIN', $fname );
- $this->mTrxLevel = 1;
- }
- function immediateCommit( $fname = 'DatabasePostgres::immediateCommit' ) {
- return true;
- }
- function commit( $fname = 'DatabasePostgres::commit' ) {
- $this->query( 'COMMIT', $fname );
- $this->mTrxLevel = 0;
- }
-
/* Not even sure why this is used in the main codebase... */
function limitResultForUpdate( $sql, $num ) {
return $sql;
}
- function setup_database() {
- global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport, $wgDBuser;
-
- // Make sure that we can write to the correct schema
- // If not, Postgres will happily and silently go to the next search_path item
- $ctest = "mediawiki_test_table";
- $safeschema = $this->quote_ident($wgDBmwschema);
- if ($this->tableExists($ctest, $wgDBmwschema)) {
- $this->doQuery("DROP TABLE $safeschema.$ctest");
- }
- $SQL = "CREATE TABLE $safeschema.$ctest(a int)";
- $olde = error_reporting( 0 );
- $res = $this->doQuery($SQL);
- error_reporting( $olde );
- if (!$res) {
- print "<b>FAILED</b>. Make sure that the user \"" . htmlspecialchars( $wgDBuser ) .
- "\" can write to the schema \"" . htmlspecialchars( $wgDBmwschema ) . "\"</li>\n";
- dieout(""); # Will close the main list <ul> and finish the page.
- }
- $this->doQuery("DROP TABLE $safeschema.$ctest");
-
- $res = $this->sourceFile( "../maintenance/postgres/tables.sql" );
- if ($res === true) {
- print " done.</li>\n";
- } else {
- print " <b>FAILED</b></li>\n";
- dieout( htmlspecialchars( $res ) );
- }
-
- ## Update version information
- $mwv = $this->addQuotes($wgVersion);
- $pgv = $this->addQuotes($this->getServerVersion());
- $pgu = $this->addQuotes($this->mUser);
- $mws = $this->addQuotes($wgDBmwschema);
- $tss = $this->addQuotes($wgDBts2schema);
- $pgp = $this->addQuotes($wgDBport);
- $dbn = $this->addQuotes($this->mDBname);
- $ctype = $this->addQuotes( pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0) );
-
- $SQL = "UPDATE mediawiki_version SET mw_version=$mwv, pg_version=$pgv, pg_user=$pgu, ".
- "mw_schema = $mws, ts2_schema = $tss, pg_port=$pgp, pg_dbname=$dbn, ".
- "ctype = $ctype ".
- "WHERE type = 'Creation'";
- $this->query($SQL);
-
- echo "<li>Populating interwiki table... ";
-
- ## Avoid the non-standard "REPLACE INTO" syntax
- $f = fopen( "../maintenance/interwiki.sql", 'r' );
- if ($f == false ) {
- print "<b>FAILED</b></li>";
- dieout( "Could not find the interwiki.sql file" );
- }
- ## We simply assume it is already empty as we have just created it
- $SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
- while ( ! feof( $f ) ) {
- $line = fgets($f,1024);
- $matches = array();
- if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) {
- continue;
- }
- $this->query("$SQL $matches[1],$matches[2])");
- }
- print " successfully populated.</li>\n";
-
- $this->doQuery("COMMIT");
- }
-
function encodeBlob( $b ) {
- return new Blob ( pg_escape_bytea( $b ) ) ;
+ return new Blob( pg_escape_bytea( $this->mConn, $b ) );
}
function decodeBlob( $b ) {
- if ($b instanceof Blob) {
+ if ( $b instanceof Blob ) {
$b = $b->fetch();
}
return pg_unescape_bytea( $b );
}
- function strencode( $s ) { ## Should not be called by us
- return pg_escape_string( $s );
+ function strencode( $s ) { # Should not be called by us
+ return pg_escape_string( $this->mConn, $s );
}
function addQuotes( $s ) {
if ( is_null( $s ) ) {
return 'NULL';
- } else if ( is_bool( $s ) ) {
+ } elseif ( is_bool( $s ) ) {
return intval( $s );
- } else if ($s instanceof Blob) {
- return "'".$s->fetch($s)."'";
+ } elseif ( $s instanceof Blob ) {
+ return "'" . $s->fetch( $s ) . "'";
}
- return "'" . pg_escape_string($s) . "'";
- }
-
- function quote_ident( $s ) {
- return '"' . preg_replace( '/"/', '""', $s) . '"';
+ return "'" . pg_escape_string( $this->mConn, $s ) . "'";
}
/**
@@ -1403,15 +936,14 @@ SQL;
* @return string SQL string
*/
protected function replaceVars( $ins ) {
-
$ins = parent::replaceVars( $ins );
- if ($this->numeric_version >= 8.3) {
+ if ( $this->numeric_version >= 8.3 ) {
// Thanks for not providing backwards-compatibility, 8.3
$ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
}
- if ($this->numeric_version <= 8.1) { // Our minimum version
+ if ( $this->numeric_version <= 8.1 ) { // Our minimum version
$ins = str_replace( 'USING gin', 'USING gist', $ins );
}
@@ -1438,33 +970,35 @@ SQL;
}
}
- if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY " . $options['GROUP BY'];
- if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
- if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY " . $options['ORDER BY'];
+ if ( isset( $options['GROUP BY'] ) ) {
+ $preLimitTail .= ' GROUP BY ' . $options['GROUP BY'];
+ }
+ if ( isset( $options['HAVING'] ) ) {
+ $preLimitTail .= " HAVING {$options['HAVING']}";
+ }
+ if ( isset( $options['ORDER BY'] ) ) {
+ $preLimitTail .= ' ORDER BY ' . $options['ORDER BY'];
+ }
- //if (isset($options['LIMIT'])) {
- // $tailOpts .= $this->limitResult('', $options['LIMIT'],
- // isset($options['OFFSET']) ? $options['OFFSET']
- // : false);
+ //if ( isset( $options['LIMIT'] ) ) {
+ // $tailOpts .= $this->limitResult( '', $options['LIMIT'],
+ // isset( $options['OFFSET'] ) ? $options['OFFSET']
+ // : false );
//}
- if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
- if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
+ if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+ $postLimitTail .= ' FOR UPDATE';
+ }
+ if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
+ $postLimitTail .= ' LOCK IN SHARE MODE';
+ }
+ if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
+ $startOpts .= 'DISTINCT';
+ }
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
- /**
- * How lagged is this slave?
- *
- */
- public function getLag() {
- # Not implemented for PostgreSQL
- return false;
- }
-
- function setFakeSlaveLag( $lag ) {}
function setFakeMaster( $enabled = true ) {}
function getDBname() {
@@ -1480,6 +1014,6 @@ SQL;
}
public function getSearchEngine() {
- return "SearchPostgres";
+ return 'SearchPostgres';
}
} // end DatabasePostgres class
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index c149cf04..503ebdf6 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -1,10 +1,10 @@
<?php
/**
- * This script is the SQLite database abstraction layer
- *
+ * This is the SQLite database abstraction layer.
* See maintenance/sqlite/README for development notes and other specific information
- * @ingroup Database
+ *
* @file
+ * @ingroup Database
*/
/**
@@ -12,6 +12,8 @@
*/
class DatabaseSqlite extends DatabaseBase {
+ private static $fulltextEnabled = null;
+
var $mAffectedRows;
var $mLastResult;
var $mDatabaseFile;
@@ -21,11 +23,16 @@ class DatabaseSqlite extends DatabaseBase {
* Constructor.
* Parameters $server, $user and $password are not used.
*/
- function __construct( $server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0 ) {
- $this->mFailFunction = $failFunction;
- $this->mFlags = $flags;
+ function __construct( $server = false, $user = false, $password = false, $dbName = false, $flags = 0 ) {
$this->mName = $dbName;
- $this->open( $server, $user, $password, $dbName );
+ parent::__construct( $server, $user, $password, $dbName, $flags );
+ // parent doesn't open when $user is false, but we can work with $dbName
+ if( !$user && $dbName ) {
+ global $wgSharedDB;
+ if( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) {
+ $this->attachDatabase( $wgSharedDB );
+ }
+ }
}
function getType() {
@@ -37,8 +44,8 @@ class DatabaseSqlite extends DatabaseBase {
*/
function implicitGroupby() { return false; }
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 ) {
- return new DatabaseSqlite( $server, $user, $password, $dbName, $failFunction, $flags );
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
+ return new DatabaseSqlite( $server, $user, $password, $dbName, $flags );
}
/** Open an SQLite database and return a resource handle to it
@@ -49,7 +56,8 @@ class DatabaseSqlite extends DatabaseBase {
$fileName = self::generateFileName( $wgSQLiteDataDir, $dbName );
if ( !is_readable( $fileName ) ) {
- throw new DBConnectionError( $this, "SQLite database not accessible" ); $this->mConn = false;
+ $this->mConn = false;
+ throw new DBConnectionError( $this, "SQLite database not accessible" );
}
$this->openFile( $fileName );
return $this->mConn;
@@ -71,14 +79,9 @@ class DatabaseSqlite extends DatabaseBase {
} catch ( PDOException $e ) {
$err = $e->getMessage();
}
- if ( $this->mConn === false ) {
+ if ( !$this->mConn ) {
wfDebug( "DB connection error: $err\n" );
- if ( !$this->mFailFunction ) {
- throw new DBConnectionError( $this, $err );
- } else {
- return false;
- }
-
+ throw new DBConnectionError( $this, $err );
}
$this->mOpened = !!$this->mConn;
# set error codes only, don't raise exceptions
@@ -111,17 +114,64 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
+ * Check if the searchindext table is FTS enabled.
+ * @returns false if not enabled.
+ */
+ function checkForEnabledSearch() {
+ if ( self::$fulltextEnabled === null ) {
+ self::$fulltextEnabled = false;
+ $table = $this->tableName( 'searchindex' );
+ $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
+ if ( $res ) {
+ $row = $res->fetchRow();
+ self::$fulltextEnabled = stristr($row['sql'], 'fts' ) !== false;
+ }
+ }
+ return self::$fulltextEnabled;
+ }
+
+ /**
* Returns version of currently supported SQLite fulltext search module or false if none present.
* @return String
*/
- function getFulltextSearchModule() {
+ static function getFulltextSearchModule() {
+ static $cachedResult = null;
+ if ( $cachedResult !== null ) {
+ return $cachedResult;
+ }
+ $cachedResult = false;
$table = 'dummy_search_test';
- $this->query( "DROP TABLE IF EXISTS $table", __METHOD__ );
- if ( $this->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
- $this->query( "DROP TABLE IF EXISTS $table", __METHOD__ );
- return 'FTS3';
+
+ $db = new DatabaseSqliteStandalone( ':memory:' );
+
+ if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
+ $cachedResult = 'FTS3';
}
- return false;
+ $db->close();
+ return $cachedResult;
+ }
+
+ /**
+ * Attaches external database to our connection, see http://sqlite.org/lang_attach.html
+ * for details.
+ * @param $name String: database name to be used in queries like SELECT foo FROM dbname.table
+ * @param $file String: database file name. If omitted, will be generated using $name and $wgSQLiteDataDir
+ * @param $fname String: calling function name
+ */
+ function attachDatabase( $name, $file = false, $fname = 'DatabaseSqlite::attachDatabase' ) {
+ global $wgSQLiteDataDir;
+ if ( !$file ) {
+ $file = self::generateFileName( $wgSQLiteDataDir, $name );
+ }
+ $file = $this->addQuotes( $file );
+ return $this->query( "ATTACH DATABASE $file AS $name", $fname );
+ }
+
+ /**
+ * @see DatabaseBase::isWriteQuery()
+ */
+ function isWriteQuery( $sql ) {
+ return parent::isWriteQuery( $sql ) && !preg_match( '/^ATTACH\b/i', $sql );
}
/**
@@ -140,25 +190,29 @@ class DatabaseSqlite extends DatabaseBase {
}
function freeResult( $res ) {
- if ( $res instanceof ResultWrapper )
+ if ( $res instanceof ResultWrapper ) {
$res->result = null;
- else
+ } else {
$res = null;
+ }
}
function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper )
+ if ( $res instanceof ResultWrapper ) {
$r =& $res->result;
- else
+ } else {
$r =& $res;
+ }
$cur = current( $r );
if ( is_array( $cur ) ) {
next( $r );
$obj = new stdClass;
- foreach ( $cur as $k => $v )
- if ( !is_numeric( $k ) )
+ foreach ( $cur as $k => $v ) {
+ if ( !is_numeric( $k ) ) {
$obj->$k = $v;
+ }
+ }
return $obj;
}
@@ -166,11 +220,11 @@ class DatabaseSqlite extends DatabaseBase {
}
function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper )
+ if ( $res instanceof ResultWrapper ) {
$r =& $res->result;
- else
+ } else {
$r =& $res;
-
+ }
$cur = current( $r );
if ( is_array( $cur ) ) {
next( $r );
@@ -205,6 +259,8 @@ class DatabaseSqlite extends DatabaseBase {
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
*/
function tableName( $name ) {
+ // table names starting with sqlite_ are reserved
+ if ( strpos( $name, 'sqlite_' ) === 0 ) return $name;
return str_replace( '`', '', parent::tableName( $name ) );
}
@@ -223,19 +279,23 @@ class DatabaseSqlite extends DatabaseBase {
}
function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper )
+ if ( $res instanceof ResultWrapper ) {
$r =& $res->result;
- else
+ } else {
$r =& $res;
+ }
reset( $r );
- if ( $row > 0 )
- for ( $i = 0; $i < $row; $i++ )
+ if ( $row > 0 ) {
+ for ( $i = 0; $i < $row; $i++ ) {
next( $r );
+ }
+ }
}
function lastError() {
- if ( !is_object( $this->mConn ) )
+ if ( !is_object( $this->mConn ) ) {
return "Cannot return last error, no db connection";
+ }
$e = $this->mConn->errorInfo();
return isset( $e[2] ) ? $e[2] : '';
}
@@ -298,9 +358,11 @@ class DatabaseSqlite extends DatabaseBase {
* Filter the options used in SELECT statements
*/
function makeSelectOptions( $options ) {
- foreach ( $options as $k => $v )
- if ( is_numeric( $k ) && $v == 'FOR UPDATE' )
+ foreach ( $options as $k => $v ) {
+ if ( is_numeric( $k ) && $v == 'FOR UPDATE' ) {
$options[$k] = '';
+ }
+ }
return parent::makeSelectOptions( $options );
}
@@ -308,20 +370,28 @@ class DatabaseSqlite extends DatabaseBase {
* Based on generic method (parent) with some prior SQLite-sepcific adjustments
*/
function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) {
- if ( !count( $a ) ) return true;
- if ( !is_array( $options ) ) $options = array( $options );
+ if ( !count( $a ) ) {
+ return true;
+ }
+ if ( !is_array( $options ) ) {
+ $options = array( $options );
+ }
# SQLite uses OR IGNORE not just IGNORE
- foreach ( $options as $k => $v )
- if ( $v == 'IGNORE' )
+ foreach ( $options as $k => $v ) {
+ if ( $v == 'IGNORE' ) {
$options[$k] = 'OR IGNORE';
+ }
+ }
# SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
if ( isset( $a[0] ) && is_array( $a[0] ) ) {
$ret = true;
- foreach ( $a as $k => $v )
- if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) )
+ foreach ( $a as $v ) {
+ if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) {
$ret = false;
+ }
+ }
} else {
$ret = parent::insert( $table, $a, "$fname/single-row", $options );
}
@@ -331,13 +401,15 @@ class DatabaseSqlite extends DatabaseBase {
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseSqlite::replace' ) {
if ( !count( $rows ) ) return true;
-
+
# SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
$ret = true;
- foreach ( $rows as $k => $v )
- if ( !parent::replace( $table, $uniqueIndexes, $v, "$fname/multi-row" ) )
+ foreach ( $rows as $v ) {
+ if ( !parent::replace( $table, $uniqueIndexes, $v, "$fname/multi-row" ) ) {
$ret = false;
+ }
+ }
} else {
$ret = parent::replace( $table, $uniqueIndexes, $rows, "$fname/single-row" );
}
@@ -362,6 +434,10 @@ class DatabaseSqlite extends DatabaseBase {
return implode( $glue, $sqls );
}
+ public function unixTimestamp( $field ) {
+ return $field;
+ }
+
function wasDeadlock() {
return $this->lastErrno() == 5; // SQLITE_BUSY
}
@@ -377,7 +453,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* @return string wikitext of a link to the server software's web site
*/
- function getSoftwareLink() {
+ public static function getSoftwareLink() {
return "[http://sqlite.org/ SQLite]";
}
@@ -390,11 +466,10 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * Query whether a given column exists in the mediawiki schema
+ * @return string User-friendly database information
*/
- function fieldExists( $table, $field, $fname = '' ) {
- $info = $this->fieldInfo( $table, $field );
- return (bool)$info;
+ public function getServerInfo() {
+ return wfMsg( self::getFulltextSearchModule() ? 'sqlite-has-fts' : 'sqlite-no-fts', $this->getServerVersion() );
}
/**
@@ -458,10 +533,6 @@ class DatabaseSqlite extends DatabaseBase {
}
}
- function quote_ident( $s ) {
- return $s;
- }
-
function buildLike() {
$params = func_get_args();
if ( count( $params ) > 0 && is_array( $params[0] ) ) {
@@ -470,43 +541,6 @@ class DatabaseSqlite extends DatabaseBase {
return parent::buildLike( $params ) . "ESCAPE '\' ";
}
- /**
- * How lagged is this slave?
- */
- public function getLag() {
- return 0;
- }
-
- /**
- * Called by the installer script (when modified according to the MediaWikiLite installation instructions)
- * - this is the same way PostgreSQL works, MySQL reads in tables.sql and interwiki.sql using dbsource (which calls db->sourceFile)
- */
- public function setup_database() {
- global $IP;
-
- # Process common MySQL/SQLite table definitions
- $err = $this->sourceFile( "$IP/maintenance/tables.sql" );
- if ( $err !== true ) {
- echo " <b>FAILED</b></li>";
- dieout( htmlspecialchars( $err ) );
- }
- echo " done.</li>";
-
- # Use DatabasePostgres's code to populate interwiki from MySQL template
- $f = fopen( "$IP/maintenance/interwiki.sql", 'r' );
- if ( $f == false ) {
- dieout( "Could not find the interwiki.sql file." );
- }
-
- $sql = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
- while ( !feof( $f ) ) {
- $line = fgets( $f, 1024 );
- $matches = array();
- if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) continue;
- $this->query( "$sql $matches[1],$matches[2])" );
- }
- }
-
public function getSearchEngine() {
return "SearchSqlite";
}
@@ -530,9 +564,11 @@ class DatabaseSqlite extends DatabaseBase {
// no such thing as unsigned
$s = preg_replace( '/\b(un)?signed\b/i', '', $s );
// INT -> INTEGER
- $s = preg_replace( '/\b(tiny|small|medium|big|)int(\([\s\d]*\)|\b)/i', 'INTEGER', $s );
+ $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
+ // floating point types -> REAL
+ $s = preg_replace( '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i', 'REAL', $s );
// varchar -> TEXT
- $s = preg_replace( '/\bvarchar\(\d+\)/i', 'TEXT', $s );
+ $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
// TEXT normalization
$s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
// BLOB normalization
@@ -542,13 +578,15 @@ class DatabaseSqlite extends DatabaseBase {
// DATETIME -> TEXT
$s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s );
// No ENUM type
- $s = preg_replace( '/enum\([^)]*\)/i', 'BLOB', $s );
+ $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s );
// binary collation type -> nothing
$s = preg_replace( '/\bbinary\b/i', '', $s );
// auto_increment -> autoincrement
$s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
// No explicit options
$s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
+ // AUTOINCREMENT should immedidately follow PRIMARY KEY
+ $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s );
} elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
// No truncated indexes
$s = preg_replace( '/\(\d+\)/', '', $s );
@@ -585,6 +623,7 @@ class DatabaseSqlite extends DatabaseBase {
class DatabaseSqliteStandalone extends DatabaseSqlite {
public function __construct( $fileName, $flags = 0 ) {
$this->mFlags = $flags;
+ $this->tablePrefix( null );
$this->openFile( $fileName );
}
}
@@ -592,7 +631,7 @@ class DatabaseSqliteStandalone extends DatabaseSqlite {
/**
* @ingroup Database
*/
-class SQLiteField {
+class SQLiteField implements Field {
private $info, $tableName;
function __construct( $info, $tableName ) {
$this->info = $info;
@@ -617,18 +656,10 @@ class SQLiteField {
return $this->info->dflt_value;
}
- function maxLength() {
- return -1;
+ function isNullable() {
+ return !$this->info->notnull;
}
- function nullable() {
- // SQLite dynamic types are always nullable
- return true;
- }
-
- # isKey(), isMultipleKey() not implemented, MySQL-specific concept.
- # Suggest removal from base class [TS]
-
function type() {
return $this->info->type;
}
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index 10c87133..f84a70e5 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Generator of database load balancing objects
+ *
* @file
* @ingroup Database
*/
@@ -12,6 +14,23 @@ abstract class LBFactory {
static $instance;
/**
+ * Disables all access to the load balancer, will cause all database access
+ * to throw a DBAccessError
+ */
+ public static function disableBackend() {
+ global $wgLBFactoryConf;
+ self::$instance = new LBFactory_Fake( $wgLBFactoryConf );
+ }
+
+ /**
+ * Resets the singleton for use if it's been disabled. Does nothing otherwise
+ */
+ public static function enableBackend() {
+ if( self::$instance instanceof LBFactory_Fake )
+ self::$instance = null;
+ }
+
+ /**
* Get an LBFactory instance
*/
static function &singleton() {
@@ -36,6 +55,14 @@ abstract class LBFactory {
}
/**
+ * Set the instance to be the given object
+ */
+ static function setInstance( $instance ) {
+ self::destroyInstance();
+ self::$instance = $instance;
+ }
+
+ /**
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
*/
abstract function __construct( $conf );
@@ -44,7 +71,7 @@ abstract class LBFactory {
* Create a new load balancer object. The resulting object will be untracked,
* not chronology-protected, and the caller is responsible for cleaning it up.
*
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @param $wiki String: wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function newMainLB( $wiki = false );
@@ -52,7 +79,7 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer object.
*
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @param $wiki String: wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function getMainLB( $wiki = false );
@@ -62,16 +89,16 @@ abstract class LBFactory {
* untracked, not chronology-protected, and the caller is responsible for
* cleaning it up.
*
- * @param string $cluster External storage cluster, or false for core
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @param $cluster String: external storage cluster, or false for core
+ * @param $wiki String: wiki ID, or false for the current wiki
*/
abstract function newExternalLB( $cluster, $wiki = false );
/*
* Get a cached (tracked) load balancer for external storage
*
- * @param string $cluster External storage cluster, or false for core
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @param $cluster String: external storage cluster, or false for core
+ * @param $wiki String: wiki ID, or false for the current wiki
*/
abstract function &getExternalLB( $cluster, $wiki = false );
@@ -198,6 +225,39 @@ class LBFactory_Simple extends LBFactory {
}
/**
+ * LBFactory class that throws an error on any attempt to use it.
+ * This will typically be done via wfGetDB().
+ * Call LBFactory::disableBackend() to start using this, and
+ * LBFactory::enableBackend() to return to normal behavior
+ */
+class LBFactory_Fake extends LBFactory {
+ function __construct( $conf ) {}
+
+ function newMainLB( $wiki = false) {
+ throw new DBAccessError;
+ }
+ function getMainLB( $wiki = false ) {
+ throw new DBAccessError;
+ }
+ function newExternalLB( $cluster, $wiki = false ) {
+ throw new DBAccessError;
+ }
+ function &getExternalLB( $cluster, $wiki = false ) {
+ throw new DBAccessError;
+ }
+ function forEachLB( $callback, $params = array() ) {}
+}
+
+/**
+ * Exception class for attempted DB access
+ */
+class DBAccessError extends MWException {
+ function __construct() {
+ parent::__construct( "Mediawiki tried to access the database via wfGetDB(). This is not allowed." );
+ }
+}
+
+/**
* Class for ensuring a consistent ordering of events as seen by the user, despite replication.
* Kind of like Hawking's [[Chronology Protection Agency]].
*/
@@ -208,7 +268,7 @@ class ChronologyProtector {
/**
* Initialise a LoadBalancer to give it appropriate chronology protection.
*
- * @param LoadBalancer $lb
+ * @param $lb LoadBalancer
*/
function initLB( $lb ) {
if ( $this->startupPos === null ) {
@@ -233,7 +293,7 @@ class ChronologyProtector {
* Notify the ChronologyProtector that the LoadBalancer is about to shut
* down. Saves replication positions.
*
- * @param LoadBalancer $lb
+ * @param $lb LoadBalancer
*/
function shutdownLB( $lb ) {
// Don't start a session, don't bother with non-replicated setups
diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactory_Multi.php
index 820aa2ea..0d411ec6 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactory_Multi.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Advanced generator of database load balancing objects for wiki farms
+ *
* @file
* @ingroup Database
*/
@@ -85,7 +87,7 @@ class LBFactory_Multi extends LBFactory {
if ( $this->lastWiki === $wiki ) {
return $this->lastSection;
}
- list( $dbName, $prefix ) = $this->getDBNameAndPrefix( $wiki );
+ list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
if ( isset( $this->sectionsByDB[$dbName] ) ) {
$section = $this->sectionsByDB[$dbName];
} else {
@@ -97,7 +99,7 @@ class LBFactory_Multi extends LBFactory {
}
function newMainLB( $wiki = false ) {
- list( $dbName, $prefix ) = $this->getDBNameAndPrefix( $wiki );
+ list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
$section = $this->getSectionForWiki( $wiki );
$groupLoads = array();
if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactory_Single.php
new file mode 100644
index 00000000..25acdc5b
--- /dev/null
+++ b/includes/db/LBFactory_Single.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * An LBFactory class that always returns a single database object.
+ */
+class LBFactory_Single extends LBFactory {
+ protected $lb;
+
+ /**
+ * @param $conf An associative array with one member:
+ * - connection: The DatabaseBase connection object
+ */
+ function __construct( $conf ) {
+ $this->lb = new LoadBalancer_Single( $conf );
+ }
+
+ function newMainLB( $wiki = false ) {
+ return $this->lb;
+ }
+
+ function getMainLB( $wiki = false ) {
+ return $this->lb;
+ }
+
+ function newExternalLB( $cluster, $wiki = false ) {
+ return $this->lb;
+ }
+
+ function &getExternalLB( $cluster, $wiki = false ) {
+ return $this->lb;
+ }
+
+ function forEachLB( $callback, $params = array() ) {
+ call_user_func_array( $callback, array_merge( array( $this->lb ), $params ) );
+ }
+}
+
+/**
+ * Helper class for LBFactory_Single.
+ */
+class LoadBalancer_Single extends LoadBalancer {
+ var $db;
+
+ function __construct( $params ) {
+ $this->db = $params['connection'];
+ parent::__construct( array( 'servers' => array( array(
+ 'type' => $this->db->getType(),
+ 'host' => $this->db->getServer(),
+ 'dbname' => $this->db->getDBname(),
+ 'load' => 1,
+ ) ) ) );
+ }
+
+ function reallyOpenConnection( $server, $dbNameOverride = false ) {
+ return $this->db;
+ }
+}
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index 083b70b3..d899ce07 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Database load balancing
+ *
* @file
* @ingroup Database
*/
@@ -12,7 +14,7 @@
*/
class LoadBalancer {
/* private */ var $mServers, $mConns, $mLoads, $mGroupLoads;
- /* private */ var $mFailFunction, $mErrorConnection;
+ /* private */ var $mErrorConnection;
/* private */ var $mReadIndex, $mAllowLagged;
/* private */ var $mWaitForPos, $mWaitTimeout;
/* private */ var $mLaggedSlaveMode, $mLastError = 'Unknown error';
@@ -20,9 +22,8 @@ class LoadBalancer {
/* private */ var $mLoadMonitorClass, $mLoadMonitor;
/**
- * @param array $params Array with keys:
+ * @param $params Array with keys:
* servers Required. Array of server info structures.
- * failFunction Deprecated, use exceptions instead.
* masterWaitTimeout Replication lag wait timeout
* loadMonitor Name of a class used to fetch server lag and load.
*/
@@ -33,11 +34,6 @@ class LoadBalancer {
}
$this->mServers = $params['servers'];
- if ( isset( $params['failFunction'] ) ) {
- $this->mFailFunction = $params['failFunction'];
- } else {
- $this->mFailFunction = false;
- }
if ( isset( $params['waitTimeout'] ) ) {
$this->mWaitTimeout = $params['waitTimeout'];
} else {
@@ -54,7 +50,7 @@ class LoadBalancer {
$this->mWaitForPos = false;
$this->mLaggedSlaveMode = false;
$this->mErrorConnection = false;
- $this->mAllowLag = false;
+ $this->mAllowLagged = false;
$this->mLoadMonitorClass = isset( $params['loadMonitor'] )
? $params['loadMonitor'] : 'LoadMonitor_MySQL';
@@ -71,11 +67,6 @@ class LoadBalancer {
}
}
- static function newFromParams( $servers, $failFunction = false, $waitTimeout = 10 )
- {
- return new LoadBalancer( $servers, $failFunction, $waitTimeout );
- }
-
/**
* Get a LoadMonitor instance
*/
@@ -129,11 +120,11 @@ class LoadBalancer {
# Unset excessively lagged servers
$lags = $this->getLagTimes( $wiki );
foreach ( $lags as $i => $lag ) {
- if ( $i != 0 && isset( $this->mServers[$i]['max lag'] ) ) {
+ if ( $i != 0 ) {
if ( $lag === false ) {
wfDebug( "Server #$i is not replicating\n" );
unset( $loads[$i] );
- } elseif ( $lag > $this->mServers[$i]['max lag'] ) {
+ } elseif ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) {
wfDebug( "Server #$i is excessively lagged ($lag seconds)\n" );
unset( $loads[$i] );
}
@@ -214,8 +205,6 @@ class LoadBalancer {
# Scale the configured load ratios according to the dynamic load (if the load monitor supports it)
$this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $wiki );
- $i = false;
- $found = false;
$laggedSlaveMode = false;
# First try quickly looking through the available servers for a server that
@@ -231,7 +220,8 @@ class LoadBalancer {
$i = $this->getRandomNonLagged( $currentLoads, $wiki );
if ( $i === false && count( $currentLoads ) != 0 ) {
# All slaves lagged. Switch to read-only mode
- $wgReadOnly = wfMsgNoDBForContent( 'readonly_lag' );
+ $wgReadOnly = 'The database has been automatically locked ' .
+ 'while the slave database servers catch up to the master';
$i = $this->pickRandom( $currentLoads );
$laggedSlaveMode = true;
}
@@ -332,14 +322,6 @@ class LoadBalancer {
}
/**
- * Get a random server to use in a query group
- * @deprecated use getReaderIndex
- */
- function getGroupIndex( $group ) {
- return $this->getReaderIndex( $group );
- }
-
- /**
* 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
@@ -357,13 +339,25 @@ class LoadBalancer {
}
wfProfileOut( __METHOD__ );
}
+
+ /**
+ * Set the master wait position and wait for ALL slaves to catch up to it
+ */
+ public function waitForAll( $pos ) {
+ wfProfileIn( __METHOD__ );
+ $this->mWaitForPos = $pos;
+ for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
+ $this->doWait( $i );
+ }
+ wfProfileOut( __METHOD__ );
+ }
/**
* Get any open connection to a given server index, local or foreign
* Returns false if there is no connection open
*/
function getAnyOpenConnection( $i ) {
- foreach ( $this->mConns as $type => $conns ) {
+ foreach ( $this->mConns as $conns ) {
if ( !empty( $conns[$i] ) ) {
return reset( $conns[$i] );
}
@@ -398,12 +392,14 @@ class LoadBalancer {
/**
* Get a connection by index
* This is the main entry point for this class.
- * @param int $i Database
- * @param array $groups Query groups
- * @param string $wiki Wiki ID
+ *
+ * @param $i Integer: server index
+ * @param $groups Array: query groups
+ * @param $wiki String: wiki ID
+ *
+ * @return DatabaseBase
*/
public function &getConnection( $i, $groups = array(), $wiki = false ) {
- global $wgDBtype;
wfProfileIn( __METHOD__ );
if ( $i == DB_LAST ) {
@@ -445,6 +441,7 @@ class LoadBalancer {
if ( $i === false ) {
$this->mLastError = 'No working slave server: ' . $this->mLastError;
$this->reportConnectionError( $this->mErrorConnection );
+ wfProfileOut( __METHOD__ );
return false;
}
}
@@ -509,9 +506,9 @@ class LoadBalancer {
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
- * @param integer $i Server index
- * @param string $wiki Wiki ID to open
- * @return Database
+ * @param $i Integer: server index
+ * @param $wiki String: wiki ID to open
+ * @return DatabaseBase
*
* @access private
*/
@@ -554,9 +551,9 @@ class LoadBalancer {
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
- * @param integer $i Server index
- * @param string $wiki Wiki ID to open
- * @return Database
+ * @param $i Integer: server index
+ * @param $wiki String: wiki ID to open
+ * @return DatabaseBase
*/
function openForeignConnection( $i, $wiki ) {
wfProfileIn(__METHOD__);
@@ -615,6 +612,8 @@ class LoadBalancer {
/**
* Test if the specified index represents an open connection
+ *
+ * @param $index Integer: server index
* @access private
*/
function isOpen( $index ) {
@@ -634,21 +633,26 @@ class LoadBalancer {
throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' );
}
- extract( $server );
+ $host = $server['host'];
+ $dbname = $server['dbname'];
+
if ( $dbNameOverride !== false ) {
- $dbname = $dbNameOverride;
+ $server['dbname'] = $dbname = $dbNameOverride;
}
- # Get class for this database type
- $class = 'Database' . ucfirst( $type );
-
# Create object
wfDebug( "Connecting to $host $dbname...\n" );
- $db = new $class( $host, $user, $password, $dbname, 1, $flags );
+ try {
+ $db = DatabaseBase::newFromType( $server['type'], $server );
+ } catch ( DBConnectionError $e ) {
+ // FIXME: This is probably the ugliest thing I have ever done to
+ // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
+ $db = $e->db;
+ }
if ( $db->isOpen() ) {
- wfDebug( "Connected\n" );
+ wfDebug( "Connected to $host $dbname.\n" );
} else {
- wfDebug( "Failed\n" );
+ wfDebug( "Connection failed to $host $dbname.\n" );
}
$db->setLBInfo( $server );
if ( isset( $server['fakeSlaveLag'] ) ) {
@@ -667,19 +671,9 @@ class LoadBalancer {
// No last connection, probably due to all servers being too busy
wfLogDBError( "LB failure with no last connection\n" );
$conn = new Database;
- if ( $this->mFailFunction ) {
- $conn->failFunction( $this->mFailFunction );
- $conn->reportConnectionError( $this->mLastError );
- } else {
- // If all servers were busy, mLastError will contain something sensible
- throw new DBConnectionError( $conn, $this->mLastError );
- }
+ // If all servers were busy, mLastError will contain something sensible
+ throw new DBConnectionError( $conn, $this->mLastError );
} else {
- if ( $this->mFailFunction ) {
- $conn->failFunction( $this->mFailFunction );
- } else {
- $conn->failFunction( false );
- }
$server = $conn->getProperty( 'mServer' );
wfLogDBError( "Connection error: {$this->mLastError} ({$server})\n" );
$conn->reportConnectionError( "{$this->mLastError} ({$server})" );
@@ -779,11 +773,20 @@ class LoadBalancer {
}
/**
+ * Deprecated function, typo in function name
+ */
+ function closeConnecton( $conn ) {
+ $this->closeConnection( $conn );
+ }
+
+ /**
* Close a connection
* Using this function makes sure the LoadBalancer knows the connection is closed.
* If you use $conn->close() directly, the load balancer won't update its state.
+ * @param $conn
+ * @return void
*/
- function closeConnecton( $conn ) {
+ function closeConnection( $conn ) {
$done = false;
foreach ( $this->mConns as $i1 => $conns2 ) {
foreach ( $conns2 as $i2 => $conns3 ) {
@@ -819,7 +822,7 @@ class LoadBalancer {
function commitMasterChanges() {
// Always 0, but who knows.. :)
$masterIndex = $this->getWriterIndex();
- foreach ( $this->mConns as $type => $conns2 ) {
+ foreach ( $this->mConns as $conns2 ) {
if ( empty( $conns2[$masterIndex] ) ) {
continue;
}
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 929ab2b9..9b959728 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -1,9 +1,16 @@
<?php
+/**
+ * Database load monitoring
+ *
+ * @file
+ * @ingroup Database
+ */
/**
* An interface for database load monitoring
+ *
+ * @ingroup Database
*/
-
interface LoadMonitor {
/**
* Construct a new LoadMonitor with a given LoadBalancer parent
@@ -12,9 +19,9 @@ interface LoadMonitor {
/**
* Perform pre-connection load ratio adjustment.
- * @param array $loads
- * @param string $group The selected query group
- * @param string $wiki
+ * @param $loads Array
+ * @param $group String: the selected query group
+ * @param $wiki String
*/
function scaleLoads( &$loads, $group = false, $wiki = false );
@@ -31,8 +38,8 @@ interface LoadMonitor {
* to the running thread count. The threshold may be false, which indicates
* that the sysadmin has not configured this feature.
*
- * @param Database $conn
- * @param float $threshold
+ * @param $conn DatabaseBase
+ * @param $threshold Float
*/
function postConnectionBackoff( $conn, $threshold );
@@ -46,8 +53,9 @@ interface LoadMonitor {
/**
* Basic MySQL load monitor with no external dependencies
* Uses memcached to cache the replication lag for a short time
+ *
+ * @ingroup Database
*/
-
class LoadMonitor_MySQL implements LoadMonitor {
var $parent; // LoadBalancer
diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php
index 184d1fc2..edf35a92 100644
--- a/includes/diff/DifferenceEngine.php
+++ b/includes/diff/DifferenceEngine.php
@@ -1,1240 +1,1090 @@
<?php
/**
- * @defgroup DifferenceEngine DifferenceEngine
- */
-
-// A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
-//
-// Copyright (C) 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
-// You may copy this code freely under the conditions of the GPL.
-//
-
-define('USE_ASSERTS', function_exists('assert'));
-
-/**
- * @todo document
- * @private
+ * User interface for the difference engine
+ *
+ * @file
* @ingroup DifferenceEngine
*/
-class _DiffOp {
- var $type;
- var $orig;
- var $closing;
-
- function reverse() {
- trigger_error('pure virtual', E_USER_ERROR);
- }
-
- function norig() {
- return $this->orig ? sizeof($this->orig) : 0;
- }
-
- function nclosing() {
- return $this->closing ? sizeof($this->closing) : 0;
- }
-}
/**
- * @todo document
- * @private
- * @ingroup DifferenceEngine
+ * Constant to indicate diff cache compatibility.
+ * Bump this when changing the diff formatting in a way that
+ * fixes important bugs or such to force cached diff views to
+ * clear.
*/
-class _DiffOp_Copy extends _DiffOp {
- var $type = 'copy';
-
- function _DiffOp_Copy ($orig, $closing = false) {
- if (!is_array($closing))
- $closing = $orig;
- $this->orig = $orig;
- $this->closing = $closing;
- }
-
- function reverse() {
- return new _DiffOp_Copy($this->closing, $this->orig);
- }
-}
+define( 'MW_DIFF_VERSION', '1.11a' );
/**
* @todo document
- * @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Delete extends _DiffOp {
- var $type = 'delete';
+class DifferenceEngine {
+ /**#@+
+ * @private
+ */
+ var $mOldid, $mNewid, $mTitle;
+ var $mOldtitle, $mNewtitle, $mPagetitle;
+ var $mOldtext, $mNewtext;
+ var $mOldPage, $mNewPage;
+ var $mRcidMarkPatrolled;
+ var $mOldRev, $mNewRev;
+ var $mRevisionsLoaded = false; // Have the revisions been loaded
+ var $mTextLoaded = 0; // How many text blobs have been loaded, 0, 1 or 2?
+ var $mCacheHit = false; // Was the diff fetched from cache?
- function _DiffOp_Delete ($lines) {
- $this->orig = $lines;
- $this->closing = false;
- }
+ /**
+ * Set this to true to add debug info to the HTML output.
+ * Warning: this may cause RSS readers to spuriously mark articles as "new"
+ * (bug 20601)
+ */
+ var $enableDebugComment = false;
- function reverse() {
- return new _DiffOp_Add($this->orig);
- }
-}
+ // If true, line X is not displayed when X is 1, for example to increase
+ // readability and conserve space with many small diffs.
+ protected $mReducedLineNumbers = false;
-/**
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class _DiffOp_Add extends _DiffOp {
- var $type = 'add';
+ protected $unhide = false; # show rev_deleted content if allowed
+ /**#@-*/
- function _DiffOp_Add ($lines) {
- $this->closing = $lines;
- $this->orig = false;
+ /**
+ * Constructor
+ * @param $titleObj Title object that the diff is associated with
+ * @param $old Integer: old ID we want to show and diff with.
+ * @param $new String: either 'prev' or 'next'.
+ * @param $rcid Integer: ??? FIXME (default 0)
+ * @param $refreshCache boolean If set, refreshes the diff cache
+ * @param $unhide boolean If set, allow viewing deleted revs
+ */
+ function __construct( $titleObj = null, $old = 0, $new = 0, $rcid = 0,
+ $refreshCache = false, $unhide = false )
+ {
+ if ( $titleObj ) {
+ $this->mTitle = $titleObj;
+ } else {
+ global $wgTitle;
+ $this->mTitle = $wgTitle;
+ }
+ wfDebug( "DifferenceEngine old '$old' new '$new' rcid '$rcid'\n" );
+
+ if ( 'prev' === $new ) {
+ # Show diff between revision $old and the previous one.
+ # Get previous one from DB.
+ $this->mNewid = intval( $old );
+ $this->mOldid = $this->mTitle->getPreviousRevisionID( $this->mNewid );
+ } elseif ( 'next' === $new ) {
+ # Show diff between revision $old and the next one.
+ # Get next one from DB.
+ $this->mOldid = intval( $old );
+ $this->mNewid = $this->mTitle->getNextRevisionID( $this->mOldid );
+ if ( false === $this->mNewid ) {
+ # if no result, NewId points to the newest old revision. The only newer
+ # revision is cur, which is "0".
+ $this->mNewid = 0;
+ }
+ } else {
+ $this->mOldid = intval( $old );
+ $this->mNewid = intval( $new );
+ wfRunHooks( 'NewDifferenceEngine', array( &$titleObj, &$this->mOldid, &$this->mNewid, $old, $new ) );
+ }
+ $this->mRcidMarkPatrolled = intval( $rcid ); # force it to be an integer
+ $this->mRefreshCache = $refreshCache;
+ $this->unhide = $unhide;
}
- function reverse() {
- return new _DiffOp_Delete($this->closing);
+ function setReducedLineNumbers( $value = true ) {
+ $this->mReducedLineNumbers = $value;
}
-}
-/**
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class _DiffOp_Change extends _DiffOp {
- var $type = 'change';
-
- function _DiffOp_Change ($orig, $closing) {
- $this->orig = $orig;
- $this->closing = $closing;
+ function getTitle() {
+ return $this->mTitle;
}
- function reverse() {
- return new _DiffOp_Change($this->closing, $this->orig);
+ function wasCacheHit() {
+ return $this->mCacheHit;
}
-}
-/**
- * Class used internally by Diff to actually compute the diffs.
- *
- * The algorithm used here is mostly lifted from the perl module
- * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
- * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
- *
- * 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
- * diffutils-2.7, which can be found at:
- * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
- *
- * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
- * are my own.
- *
- * Line length limits for robustness added by Tim Starling, 2005-08-31
- * Alternative implementation added by Guy Van den Broeck, 2008-07-30
- *
- * @author Geoffrey T. Dairiki, Tim Starling, Guy Van den Broeck
- * @private
- * @ingroup DifferenceEngine
- */
-class _DiffEngine {
+ function getOldid() {
+ return $this->mOldid;
+ }
- const MAX_XREF_LENGTH = 10000;
+ function getNewid() {
+ return $this->mNewid;
+ }
- function diff ($from_lines, $to_lines){
+ function showDiffPage( $diffOnly = false ) {
+ global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
wfProfileIn( __METHOD__ );
- // Diff and store locally
- $this->diff_local($from_lines, $to_lines);
-
- // Merge edits when possible
- $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
- $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
-
- // Compute the edit operations.
- $n_from = sizeof($from_lines);
- $n_to = sizeof($to_lines);
-
- $edits = array();
- $xi = $yi = 0;
- while ($xi < $n_from || $yi < $n_to) {
- USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
- USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
-
- // Skip matching "snake".
- $copy = array();
- while ( $xi < $n_from && $yi < $n_to
- && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
- $copy[] = $from_lines[$xi++];
- ++$yi;
- }
- if ($copy)
- $edits[] = new _DiffOp_Copy($copy);
+ # Allow frames except in certain special cases
+ $wgOut->allowClickjacking();
+
+ # If external diffs are enabled both globally and for the user,
+ # we'll use the application/x-external-editor interface to call
+ # an external diff tool like kompare, kdiff3, etc.
+ if ( $wgUseExternalEditor && $wgUser->getOption( 'externaldiff' ) ) {
+ global $wgInputEncoding, $wgServer, $wgScript, $wgLang;
+ $wgOut->disable();
+ header ( "Content-type: application/x-external-editor; charset=" . $wgInputEncoding );
+ $url1 = $this->mTitle->getFullURL( array(
+ 'action' => 'raw',
+ 'oldid' => $this->mOldid
+ ) );
+ $url2 = $this->mTitle->getFullURL( array(
+ 'action' => 'raw',
+ 'oldid' => $this->mNewid
+ ) );
+ $special = $wgLang->getNsText( NS_SPECIAL );
+ $control = <<<CONTROL
+ [Process]
+ Type=Diff text
+ Engine=MediaWiki
+ Script={$wgServer}{$wgScript}
+ Special namespace={$special}
+
+ [File]
+ Extension=wiki
+ URL=$url1
+
+ [File 2]
+ Extension=wiki
+ URL=$url2
+CONTROL;
+ echo( $control );
+
+ wfProfileOut( __METHOD__ );
+ return;
+ }
- // Find deletes & adds.
- $delete = array();
- while ($xi < $n_from && $this->xchanged[$xi])
- $delete[] = $from_lines[$xi++];
+ $wgOut->setArticleFlag( false );
+ if ( !$this->loadRevisionData() ) {
+ $t = $this->mTitle->getPrefixedText();
+ $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ), $this->mOldid, $this->mNewid );
+ $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
+ $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", $d );
+ wfProfileOut( __METHOD__ );
+ return;
+ }
- $add = array();
- while ($yi < $n_to && $this->ychanged[$yi])
- $add[] = $to_lines[$yi++];
+ wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
- if ($delete && $add)
- $edits[] = new _DiffOp_Change($delete, $add);
- elseif ($delete)
- $edits[] = new _DiffOp_Delete($delete);
- elseif ($add)
- $edits[] = new _DiffOp_Add($add);
+ if ( $this->mNewRev->isCurrent() ) {
+ $wgOut->setArticleFlag( true );
}
- wfProfileOut( __METHOD__ );
- return $edits;
- }
-
- function diff_local ($from_lines, $to_lines) {
- global $wgExternalDiffEngine;
- wfProfileIn( __METHOD__);
-
- if($wgExternalDiffEngine == 'wikidiff3'){
- // wikidiff3
- $wikidiff3 = new WikiDiff3();
- $wikidiff3->diff($from_lines, $to_lines);
- $this->xchanged = $wikidiff3->removed;
- $this->ychanged = $wikidiff3->added;
- unset($wikidiff3);
- }else{
- // old diff
- $n_from = sizeof($from_lines);
- $n_to = sizeof($to_lines);
- $this->xchanged = $this->ychanged = array();
- $this->xv = $this->yv = array();
- $this->xind = $this->yind = array();
- unset($this->seq);
- unset($this->in_seq);
- unset($this->lcs);
-
- // Skip leading common lines.
- for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
- if ($from_lines[$skip] !== $to_lines[$skip])
- break;
- $this->xchanged[$skip] = $this->ychanged[$skip] = false;
- }
- // Skip trailing common lines.
- $xi = $n_from; $yi = $n_to;
- for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
- if ($from_lines[$xi] !== $to_lines[$yi])
- break;
- $this->xchanged[$xi] = $this->ychanged[$yi] = false;
- }
- // Ignore lines which do not exist in both files.
- for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
- $xhash[$this->_line_hash($from_lines[$xi])] = 1;
- }
+ # mOldid is false if the difference engine is called with a "vague" query for
+ # a diff between a version V and its previous version V' AND the version V
+ # is the first version of that article. In that case, V' does not exist.
+ if ( $this->mOldid === false ) {
+ $this->showFirstRevision();
+ $this->renderNewRevision(); // should we respect $diffOnly here or not?
+ wfProfileOut( __METHOD__ );
+ return;
+ }
- for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
- $line = $to_lines[$yi];
- if ( ($this->ychanged[$yi] = empty($xhash[$this->_line_hash($line)])) )
- continue;
- $yhash[$this->_line_hash($line)] = 1;
- $this->yv[] = $line;
- $this->yind[] = $yi;
- }
- for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
- $line = $from_lines[$xi];
- if ( ($this->xchanged[$xi] = empty($yhash[$this->_line_hash($line)])) )
- continue;
- $this->xv[] = $line;
- $this->xind[] = $xi;
- }
+ $wgOut->suppressQuickbar();
- // Find the LCS.
- $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv));
+ $oldTitle = $this->mOldPage->getPrefixedText();
+ $newTitle = $this->mNewPage->getPrefixedText();
+ if ( $oldTitle == $newTitle ) {
+ $wgOut->setPageTitle( $newTitle );
+ } else {
+ $wgOut->setPageTitle( $oldTitle . ', ' . $newTitle );
+ }
+ if ( $this->mNewPage->equals( $this->mOldPage ) ) {
+ $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) );
+ } else {
+ $wgOut->setSubtitle( wfMsgExt( 'difference-multipage', array( 'parseinline' ) ) );
+ }
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
+
+ if ( !$this->mOldPage->userCanRead() || !$this->mNewPage->userCanRead() ) {
+ $wgOut->loginToUse();
+ $wgOut->output();
+ $wgOut->disable();
+ wfProfileOut( __METHOD__ );
+ return;
}
- wfProfileOut( __METHOD__ );
- }
- /**
- * Returns the whole line if it's small enough, or the MD5 hash otherwise
- */
- function _line_hash( $line ) {
- if ( strlen( $line ) > self::MAX_XREF_LENGTH ) {
- return md5( $line );
+ $sk = $wgUser->getSkin();
+
+ // Check if page is editable
+ $editable = $this->mNewRev->getTitle()->userCan( 'edit' );
+ if ( $editable && $this->mNewRev->isCurrent() && $wgUser->isAllowed( 'rollback' ) ) {
+ $wgOut->preventClickjacking();
+ $rollback = '&#160;&#160;&#160;' . $sk->generateRollback( $this->mNewRev );
} else {
- return $line;
+ $rollback = '';
}
- }
- /* Divide the Largest Common Subsequence (LCS) of the sequences
- * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
- * sized segments.
- *
- * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an
- * array of NCHUNKS+1 (X, Y) indexes giving the diving points between
- * sub sequences. The first sub-sequence is contained in [X0, X1),
- * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note
- * that (X0, Y0) == (XOFF, YOFF) and
- * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).
- *
- * This function assumes that the first lines of the specified portions
- * of the two files do not match, and likewise that the last lines do not
- * match. The caller must trim matching lines from the beginning and end
- * of the portions it is going to specify.
- */
- function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) {
- $flip = false;
-
- if ($xlim - $xoff > $ylim - $yoff) {
- // Things seems faster (I'm not sure I understand why)
- // when the shortest sequence in X.
- $flip = true;
- list ($xoff, $xlim, $yoff, $ylim)
- = array( $yoff, $ylim, $xoff, $xlim);
- }
-
- if ($flip)
- for ($i = $ylim - 1; $i >= $yoff; $i--)
- $ymatches[$this->xv[$i]][] = $i;
- else
- for ($i = $ylim - 1; $i >= $yoff; $i--)
- $ymatches[$this->yv[$i]][] = $i;
-
- $this->lcs = 0;
- $this->seq[0]= $yoff - 1;
- $this->in_seq = array();
- $ymids[0] = array();
-
- $numer = $xlim - $xoff + $nchunks - 1;
- $x = $xoff;
- for ($chunk = 0; $chunk < $nchunks; $chunk++) {
- if ($chunk > 0)
- for ($i = 0; $i <= $this->lcs; $i++)
- $ymids[$i][$chunk-1] = $this->seq[$i];
-
- $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
- for ( ; $x < $x1; $x++) {
- $line = $flip ? $this->yv[$x] : $this->xv[$x];
- if (empty($ymatches[$line]))
- continue;
- $matches = $ymatches[$line];
- reset($matches);
- while (list ($junk, $y) = each($matches))
- if (empty($this->in_seq[$y])) {
- $k = $this->_lcs_pos($y);
- USE_ASSERTS && assert($k > 0);
- $ymids[$k] = $ymids[$k-1];
- break;
- }
- while (list ( /* $junk */, $y) = each($matches)) {
- if ($y > $this->seq[$k-1]) {
- USE_ASSERTS && assert($y < $this->seq[$k]);
- // Optimization: this is a common case:
- // next match is just replacing previous match.
- $this->in_seq[$this->seq[$k]] = false;
- $this->seq[$k] = $y;
- $this->in_seq[$y] = 1;
- } else if (empty($this->in_seq[$y])) {
- $k = $this->_lcs_pos($y);
- USE_ASSERTS && assert($k > 0);
- $ymids[$k] = $ymids[$k-1];
- }
+ // Prepare a change patrol link, if applicable
+ if ( $wgUseRCPatrol && $this->mTitle->userCan( 'patrol' ) ) {
+ // If we've been given an explicit change identifier, use it; saves time
+ if ( $this->mRcidMarkPatrolled ) {
+ $rcid = $this->mRcidMarkPatrolled;
+ $rc = RecentChange::newFromId( $rcid );
+ // Already patrolled?
+ $rcid = is_object( $rc ) && !$rc->getAttribute( 'rc_patrolled' ) ? $rcid : 0;
+ } else {
+ // Look for an unpatrolled change corresponding to this diff
+ $db = wfGetDB( DB_SLAVE );
+ $change = RecentChange::newFromConds(
+ array(
+ // Redundant user,timestamp condition so we can use the existing index
+ 'rc_user_text' => $this->mNewRev->getRawUserText(),
+ 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
+ 'rc_this_oldid' => $this->mNewid,
+ 'rc_last_oldid' => $this->mOldid,
+ 'rc_patrolled' => 0
+ ),
+ __METHOD__
+ );
+ if ( $change instanceof RecentChange ) {
+ $rcid = $change->mAttribs['rc_id'];
+ $this->mRcidMarkPatrolled = $rcid;
+ } else {
+ // None found
+ $rcid = 0;
}
}
+ // Build the link
+ if ( $rcid ) {
+ $wgOut->preventClickjacking();
+ $token = $wgUser->editToken( $rcid );
+ $patrol = ' <span class="patrollink">[' . $sk->link(
+ $this->mTitle,
+ wfMsgHtml( 'markaspatrolleddiff' ),
+ array(),
+ array(
+ 'action' => 'markpatrolled',
+ 'rcid' => $rcid,
+ 'token' => $token,
+ ),
+ array(
+ 'known',
+ 'noclasses'
+ )
+ ) . ']</span>';
+ } else {
+ $patrol = '';
+ }
+ } else {
+ $patrol = '';
}
- $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
- $ymid = $ymids[$this->lcs];
- for ($n = 0; $n < $nchunks - 1; $n++) {
- $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);
- $y1 = $ymid[$n] + 1;
- $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
+ # Carry over 'diffonly' param via navigation links
+ if ( $diffOnly != $wgUser->getBoolOption( 'diffonly' ) ) {
+ $query['diffonly'] = $diffOnly;
}
- $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
- return array($this->lcs, $seps);
- }
-
- function _lcs_pos ($ypos) {
- $end = $this->lcs;
- if ($end == 0 || $ypos > $this->seq[$end]) {
- $this->seq[++$this->lcs] = $ypos;
- $this->in_seq[$ypos] = 1;
- return $this->lcs;
+ # Make "previous revision link"
+ $query['diff'] = 'prev';
+ $query['oldid'] = $this->mOldid;
+ # Cascade unhide param in links for easy deletion browsing
+ if ( $this->unhide ) {
+ $query['unhide'] = 1;
+ }
+ if ( !$this->mOldRev->getPrevious() ) {
+ $prevlink = '&#160;';
+ } else {
+ $prevlink = $sk->link(
+ $this->mTitle,
+ wfMsgHtml( 'previousdiff' ),
+ array(
+ 'id' => 'differences-prevlink'
+ ),
+ $query,
+ array(
+ 'known',
+ 'noclasses'
+ )
+ );
}
- $beg = 1;
- while ($beg < $end) {
- $mid = (int)(($beg + $end) / 2);
- if ( $ypos > $this->seq[$mid] )
- $beg = $mid + 1;
- else
- $end = $mid;
+ # Make "next revision link"
+ $query['diff'] = 'next';
+ $query['oldid'] = $this->mNewid;
+ # Skip next link on the top revision
+ if ( $this->mNewRev->isCurrent() ) {
+ $nextlink = '&#160;';
+ } else {
+ $nextlink = $sk->link(
+ $this->mTitle,
+ wfMsgHtml( 'nextdiff' ),
+ array(
+ 'id' => 'differences-nextlink'
+ ),
+ $query,
+ array(
+ 'known',
+ 'noclasses'
+ )
+ );
}
- USE_ASSERTS && assert($ypos != $this->seq[$end]);
+ $oldminor = '';
+ $newminor = '';
- $this->in_seq[$this->seq[$end]] = false;
- $this->seq[$end] = $ypos;
- $this->in_seq[$ypos] = 1;
- return $end;
- }
+ if ( $this->mOldRev->isMinor() ) {
+ $oldminor = ChangesList::flag( 'minor' );
+ }
+ if ( $this->mNewRev->isMinor() ) {
+ $newminor = ChangesList::flag( 'minor' );
+ }
- /* Find LCS of two sequences.
- *
- * The results are recorded in the vectors $this->{x,y}changed[], by
- * storing a 1 in the element for each line that is an insertion
- * or deletion (ie. is not in the LCS).
- *
- * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
- *
- * Note that XLIM, YLIM are exclusive bounds.
- * All line numbers are origin-0 and discarded lines are not counted.
- */
- function _compareseq ($xoff, $xlim, $yoff, $ylim) {
- // Slide down the bottom initial diagonal.
- while ($xoff < $xlim && $yoff < $ylim
- && $this->xv[$xoff] == $this->yv[$yoff]) {
- ++$xoff;
- ++$yoff;
- }
-
- // Slide up the top initial diagonal.
- while ($xlim > $xoff && $ylim > $yoff
- && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
- --$xlim;
- --$ylim;
- }
-
- if ($xoff == $xlim || $yoff == $ylim)
- $lcs = 0;
- else {
- // This is ad hoc but seems to work well.
- //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
- //$nchunks = max(2,min(8,(int)$nchunks));
- $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
- list ($lcs, $seps)
- = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
- }
-
- if ($lcs == 0) {
- // X and Y sequences have no common subsequence:
- // mark all changed.
- while ($yoff < $ylim)
- $this->ychanged[$this->yind[$yoff++]] = 1;
- while ($xoff < $xlim)
- $this->xchanged[$this->xind[$xoff++]] = 1;
+ # Handle RevisionDelete links...
+ $ldel = $this->revisionDeleteLink( $this->mOldRev );
+ $rdel = $this->revisionDeleteLink( $this->mNewRev );
+
+ $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $this->mOldtitle . '</strong></div>' .
+ '<div id="mw-diff-otitle2">' .
+ $sk->revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
+ '<div id="mw-diff-otitle3">' . $oldminor .
+ $sk->revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
+ '<div id="mw-diff-otitle4">' . $prevlink . '</div>';
+ $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $this->mNewtitle . '</strong></div>' .
+ '<div id="mw-diff-ntitle2">' . $sk->revUserTools( $this->mNewRev, !$this->unhide ) .
+ " $rollback</div>" .
+ '<div id="mw-diff-ntitle3">' . $newminor .
+ $sk->revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
+ '<div id="mw-diff-ntitle4">' . $nextlink . $patrol . '</div>';
+
+ # Check if this user can see the revisions
+ $allowed = $this->mOldRev->userCan( Revision::DELETED_TEXT )
+ && $this->mNewRev->userCan( Revision::DELETED_TEXT );
+ # Check if one of the revisions is deleted/suppressed
+ $deleted = $suppressed = false;
+ if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $deleted = true; // old revisions text is hidden
+ if ( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $suppressed = true; // also suppressed
+ }
+ if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $deleted = true; // new revisions text is hidden
+ if ( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $suppressed = true; // also suppressed
+ }
+ # If the diff cannot be shown due to a deleted revision, then output
+ # the diff header and links to unhide (if available)...
+ if ( $deleted && ( !$this->unhide || !$allowed ) ) {
+ $this->showDiffStyle();
+ $multi = $this->getMultiNotice();
+ $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) );
+ if ( !$allowed ) {
+ $msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff';
+ # Give explanation for why revision is not visible
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
+ array( $msg ) );
+ } else {
+ # Give explanation and add a link to view the diff...
+ $link = $this->mTitle->getFullUrl( array(
+ 'diff' => $this->mNewid,
+ 'oldid' => $this->mOldid,
+ 'unhide' => 1
+ ) );
+ $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", array( $msg, $link ) );
+ }
+ # Otherwise, output a regular diff...
} else {
- // Use the partitions to split this problem into subproblems.
- reset($seps);
- $pt1 = $seps[0];
- while ($pt2 = next($seps)) {
- $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
- $pt1 = $pt2;
+ # Add deletion notice if the user is viewing deleted content
+ $notice = '';
+ if ( $deleted ) {
+ $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view';
+ $notice = "<div class='mw-warning plainlinks'>\n" . wfMsgExt( $msg, 'parseinline' ) . "</div>\n";
+ }
+ $this->showDiff( $oldHeader, $newHeader, $notice );
+ if ( !$diffOnly ) {
+ $this->renderNewRevision();
}
}
+ wfProfileOut( __METHOD__ );
}
- /* Adjust inserts/deletes of identical lines to join changes
- * as much as possible.
- *
- * We do something when a run of changed lines include a
- * line at one end and has an excluded, identical line at the other.
- * We are free to choose which identical line is included.
- * `compareseq' usually chooses the one at the beginning,
- * but usually it is cleaner to consider the following identical line
- * to be the "change".
- *
- * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
- */
- function _shift_boundaries ($lines, &$changed, $other_changed) {
- wfProfileIn( __METHOD__ );
- $i = 0;
- $j = 0;
-
- USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)');
- $len = sizeof($lines);
- $other_len = sizeof($other_changed);
-
- while (1) {
- /*
- * Scan forwards to find beginning of another run of changes.
- * Also keep track of the corresponding point in the other file.
- *
- * Throughout this code, $i and $j are adjusted together so that
- * the first $i elements of $changed and the first $j elements
- * of $other_changed both contain the same number of zeros
- * (unchanged lines).
- * Furthermore, $j is always kept so that $j == $other_len or
- * $other_changed[$j] == false.
- */
- while ($j < $other_len && $other_changed[$j])
- $j++;
-
- while ($i < $len && ! $changed[$i]) {
- USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
- $i++; $j++;
- while ($j < $other_len && $other_changed[$j])
- $j++;
- }
-
- if ($i == $len)
- break;
-
- $start = $i;
-
- // Find the end of this run of changes.
- while (++$i < $len && $changed[$i])
- continue;
-
- do {
- /*
- * Record the length of this run of changes, so that
- * we can later determine whether the run has grown.
- */
- $runlength = $i - $start;
-
- /*
- * Move the changed region back, so long as the
- * previous unchanged line matches the last changed one.
- * This merges with previous changed regions.
- */
- while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
- $changed[--$start] = 1;
- $changed[--$i] = false;
- while ($start > 0 && $changed[$start - 1])
- $start--;
- USE_ASSERTS && assert('$j > 0');
- while ($other_changed[--$j])
- continue;
- USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
- }
-
- /*
- * Set CORRESPONDING to the end of the changed run, at the last
- * point where it corresponds to a changed run in the other file.
- * CORRESPONDING == LEN means no such point has been found.
- */
- $corresponding = $j < $other_len ? $i : $len;
-
- /*
- * Move the changed region forward, so long as the
- * first changed line matches the following unchanged one.
- * This merges with following changed regions.
- * Do this second, so that if there are no merges,
- * the changed region is moved forward as far as possible.
- */
- while ($i < $len && $lines[$start] == $lines[$i]) {
- $changed[$start++] = false;
- $changed[$i++] = 1;
- while ($i < $len && $changed[$i])
- $i++;
-
- USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
- $j++;
- if ($j < $other_len && $other_changed[$j]) {
- $corresponding = $i;
- while ($j < $other_len && $other_changed[$j])
- $j++;
- }
- }
- } while ($runlength != $i - $start);
-
- /*
- * If possible, move the fully-merged run of changes
- * back to a corresponding run in the other file.
- */
- while ($corresponding < $i) {
- $changed[--$start] = 1;
- $changed[--$i] = 0;
- USE_ASSERTS && assert('$j > 0');
- while ($other_changed[--$j])
- continue;
- USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
+ protected function revisionDeleteLink( $rev ) {
+ global $wgUser;
+ $link = '';
+ $canHide = $wgUser->isAllowed( 'deleterevision' );
+ // Show del/undel link if:
+ // (a) the user can delete revisions, or
+ // (b) the user can view deleted revision *and* this one is deleted
+ if ( $canHide || ( $rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) ) {
+ $sk = $wgUser->getSkin();
+ if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ $link = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
+ } else {
+ $query = array(
+ 'type' => 'revision',
+ 'target' => $rev->mTitle->getPrefixedDbkey(),
+ 'ids' => $rev->getId()
+ );
+ $link = $sk->revDeleteLink( $query,
+ $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
}
+ $link = '&#160;&#160;&#160;' . $link . ' ';
}
- wfProfileOut( __METHOD__ );
+ return $link;
}
-}
-
-/**
- * Class representing a 'diff' between two sequences of strings.
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class Diff
-{
- var $edits;
/**
- * Constructor.
- * Computes diff between sequences of strings.
- *
- * @param $from_lines array An array of strings.
- * (Typically these are lines from a file.)
- * @param $to_lines array An array of strings.
+ * Show the new revision of the page.
*/
- function Diff($from_lines, $to_lines) {
- $eng = new _DiffEngine;
- $this->edits = $eng->diff($from_lines, $to_lines);
- //$this->_check($from_lines, $to_lines);
- }
+ function renderNewRevision() {
+ global $wgOut, $wgUser;
+ wfProfileIn( __METHOD__ );
- /**
- * Compute reversed Diff.
- *
- * SYNOPSIS:
- *
- * $diff = new Diff($lines1, $lines2);
- * $rev = $diff->reverse();
- * @return object A Diff object representing the inverse of the
- * original diff.
- */
- function reverse () {
- $rev = $this;
- $rev->edits = array();
- foreach ($this->edits as $edit) {
- $rev->edits[] = $edit->reverse();
+ $wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
+ # Add deleted rev tag if needed
+ if ( !$this->mNewRev->userCan( Revision::DELETED_TEXT ) ) {
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
+ } else if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
}
- return $rev;
- }
- /**
- * Check for empty diff.
- *
- * @return bool True iff two sequences were identical.
- */
- function isEmpty () {
- foreach ($this->edits as $edit) {
- if ($edit->type != 'copy')
- return false;
+ $pCache = true;
+ if ( !$this->mNewRev->isCurrent() ) {
+ $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
+ $pCache = false;
}
- return true;
- }
- /**
- * Compute the length of the Longest Common Subsequence (LCS).
- *
- * This is mostly for diagnostic purposed.
- *
- * @return int The length of the LCS.
- */
- function lcs () {
- $lcs = 0;
- foreach ($this->edits as $edit) {
- if ($edit->type == 'copy')
- $lcs += sizeof($edit->orig);
+ $this->loadNewText();
+ if ( is_object( $this->mNewRev ) ) {
+ $wgOut->setRevisionId( $this->mNewRev->getId() );
}
- return $lcs;
- }
-
- /**
- * Get the original set of lines.
- *
- * This reconstructs the $from_lines parameter passed to the
- * constructor.
- *
- * @return array The original sequence of strings.
- */
- function orig() {
- $lines = array();
- foreach ($this->edits as $edit) {
- if ($edit->orig)
- array_splice($lines, sizeof($lines), 0, $edit->orig);
+ if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
+ // Stolen from Article::view --AG 2007-10-11
+ // Give hooks a chance to customise the output
+ if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mTitle, $wgOut ) ) ) {
+ // Wrap the whole lot in a <pre> and don't parse
+ $m = array();
+ preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
+ $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
+ $wgOut->addHTML( htmlspecialchars( $this->mNewtext ) );
+ $wgOut->addHTML( "\n</pre>\n" );
+ }
+ } elseif ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $wgOut ) ) ) {
+ if ( $pCache ) {
+ $article = new Article( $this->mTitle, 0 );
+ $pOutput = ParserCache::singleton()->get( $article, $wgOut->parserOptions() );
+ if ( $pOutput ) {
+ $wgOut->addParserOutput( $pOutput );
+ } else {
+ $article->doViewParse();
+ }
+ } else {
+ $wgOut->addWikiTextTidy( $this->mNewtext );
+ }
}
- return $lines;
- }
- /**
- * Get the closing set of lines.
- *
- * This reconstructs the $to_lines parameter passed to the
- * constructor.
- *
- * @return array The sequence of strings.
- */
- function closing() {
- $lines = array();
+ if ( is_object( $this->mNewRev ) && !$this->mNewRev->isCurrent() ) {
+ $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
+ }
- foreach ($this->edits as $edit) {
- if ($edit->closing)
- array_splice($lines, sizeof($lines), 0, $edit->closing);
+ # Add redundant patrol link on bottom...
+ if ( $this->mRcidMarkPatrolled && $this->mTitle->quickUserCan( 'patrol' ) ) {
+ $sk = $wgUser->getSkin();
+ $token = $wgUser->editToken( $this->mRcidMarkPatrolled );
+ $wgOut->preventClickjacking();
+ $wgOut->addHTML(
+ "<div class='patrollink'>[" . $sk->link(
+ $this->mTitle,
+ wfMsgHtml( 'markaspatrolleddiff' ),
+ array(),
+ array(
+ 'action' => 'markpatrolled',
+ 'rcid' => $this->mRcidMarkPatrolled,
+ 'token' => $token,
+ )
+ ) . ']</div>'
+ );
}
- return $lines;
+
+ wfProfileOut( __METHOD__ );
}
/**
- * Check a Diff for validity.
- *
- * This is here only for debugging purposes.
+ * Show the first revision of an article. Uses normal diff headers in
+ * contrast to normal "old revision" display style.
*/
- function _check ($from_lines, $to_lines) {
+ function showFirstRevision() {
+ global $wgOut, $wgUser;
wfProfileIn( __METHOD__ );
- if (serialize($from_lines) != serialize($this->orig()))
- trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
- if (serialize($to_lines) != serialize($this->closing()))
- trigger_error("Reconstructed closing doesn't match", E_USER_ERROR);
- $rev = $this->reverse();
- if (serialize($to_lines) != serialize($rev->orig()))
- trigger_error("Reversed original doesn't match", E_USER_ERROR);
- if (serialize($from_lines) != serialize($rev->closing()))
- trigger_error("Reversed closing doesn't match", E_USER_ERROR);
+ # Get article text from the DB
+ #
+ if ( ! $this->loadNewText() ) {
+ $t = $this->mTitle->getPrefixedText();
+ $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ), $this->mOldid, $this->mNewid );
+ $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
+ $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", $d );
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+ if ( $this->mNewRev->isCurrent() ) {
+ $wgOut->setArticleFlag( true );
+ }
+
+ # Check if user is allowed to look at this page. If not, bail out.
+ #
+ if ( !$this->mTitle->userCanRead() ) {
+ $wgOut->loginToUse();
+ $wgOut->output();
+ wfProfileOut( __METHOD__ );
+ throw new MWException( "Permission Error: you do not have access to view this page" );
+ }
+ # Prepare the header box
+ #
+ $sk = $wgUser->getSkin();
- $prevtype = 'none';
- foreach ($this->edits as $edit) {
- if ( $prevtype == $edit->type )
- trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
- $prevtype = $edit->type;
+ $next = $this->mTitle->getNextRevisionID( $this->mNewid );
+ if ( !$next ) {
+ $nextlink = '';
+ } else {
+ $nextlink = '<br />' . $sk->link(
+ $this->mTitle,
+ wfMsgHtml( 'nextdiff' ),
+ array(
+ 'id' => 'differences-nextlink'
+ ),
+ array(
+ 'diff' => 'next',
+ 'oldid' => $this->mNewid,
+ ),
+ array(
+ 'known',
+ 'noclasses'
+ )
+ );
}
+ $header = "<div class=\"firstrevisionheader\" style=\"text-align: center\">" .
+ $sk->revUserTools( $this->mNewRev ) . "<br />" . $sk->revComment( $this->mNewRev ) . $nextlink . "</div>\n";
+
+ $wgOut->addHTML( $header );
+
+ $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $lcs = $this->lcs();
- trigger_error('Diff okay: LCS = '.$lcs, E_USER_NOTICE);
wfProfileOut( __METHOD__ );
}
-}
-/**
- * @todo document, bad name.
- * @private
- * @ingroup DifferenceEngine
- */
-class MappedDiff extends Diff
-{
/**
- * Constructor.
- *
- * Computes diff between sequences of strings.
- *
- * This can be used to compute things like
- * case-insensitve diffs, or diffs which ignore
- * changes in white-space.
- *
- * @param $from_lines array An array of strings.
- * (Typically these are lines from a file.)
- *
- * @param $to_lines array An array of strings.
- *
- * @param $mapped_from_lines array This array should
- * have the same size number of elements as $from_lines.
- * The elements in $mapped_from_lines and
- * $mapped_to_lines are what is actually compared
- * when computing the diff.
- *
- * @param $mapped_to_lines array This array should
- * have the same number of elements as $to_lines.
+ * Get the diff text, send it to $wgOut
+ * Returns false if the diff could not be generated, otherwise returns true
*/
- function MappedDiff($from_lines, $to_lines,
- $mapped_from_lines, $mapped_to_lines) {
- wfProfileIn( __METHOD__ );
-
- assert(sizeof($from_lines) == sizeof($mapped_from_lines));
- assert(sizeof($to_lines) == sizeof($mapped_to_lines));
-
- $this->Diff($mapped_from_lines, $mapped_to_lines);
-
- $xi = $yi = 0;
- for ($i = 0; $i < sizeof($this->edits); $i++) {
- $orig = &$this->edits[$i]->orig;
- if (is_array($orig)) {
- $orig = array_slice($from_lines, $xi, sizeof($orig));
- $xi += sizeof($orig);
- }
-
- $closing = &$this->edits[$i]->closing;
- if (is_array($closing)) {
- $closing = array_slice($to_lines, $yi, sizeof($closing));
- $yi += sizeof($closing);
- }
+ function showDiff( $otitle, $ntitle, $notice = '' ) {
+ global $wgOut;
+ $diff = $this->getDiff( $otitle, $ntitle, $notice );
+ if ( $diff === false ) {
+ $wgOut->addWikiMsg( 'missing-article', "<nowiki>(fixme, bug)</nowiki>", '' );
+ return false;
+ } else {
+ $this->showDiffStyle();
+ $wgOut->addHTML( $diff );
+ return true;
}
- wfProfileOut( __METHOD__ );
}
-}
-/**
- * A class to format Diffs
- *
- * This class formats the diff in classic diff format.
- * It is intended that this class be customized via inheritance,
- * to obtain fancier outputs.
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class DiffFormatter {
/**
- * Number of leading context "lines" to preserve.
- *
- * This should be left at zero for this class, but subclasses
- * may want to set this to other values.
+ * Add style sheets and supporting JS for diff display.
*/
- var $leading_context_lines = 0;
+ function showDiffStyle() {
+ global $wgOut;
+ $wgOut->addModuleStyles( 'mediawiki.legacy.diff' );
+ $wgOut->addModuleScripts( 'mediawiki.legacy.diff' );
+ }
/**
- * Number of trailing context "lines" to preserve.
+ * Get complete diff table, including header
*
- * This should be left at zero for this class, but subclasses
- * may want to set this to other values.
+ * @param $otitle Title: old title
+ * @param $ntitle Title: new title
+ * @param $notice String: HTML between diff header and body
+ * @return mixed
*/
- var $trailing_context_lines = 0;
+ function getDiff( $otitle, $ntitle, $notice = '' ) {
+ $body = $this->getDiffBody();
+ if ( $body === false ) {
+ return false;
+ } else {
+ $multi = $this->getMultiNotice();
+ return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
+ }
+ }
/**
- * Format a diff.
+ * Get the diff table body, without header
*
- * @param $diff object A Diff object.
- * @return string The formatted output.
+ * @return mixed (string/false)
*/
- function format($diff) {
+ public function getDiffBody() {
+ global $wgMemc;
wfProfileIn( __METHOD__ );
-
- $xi = $yi = 1;
- $block = false;
- $context = array();
-
- $nlead = $this->leading_context_lines;
- $ntrail = $this->trailing_context_lines;
-
- $this->_start_diff();
-
- foreach ($diff->edits as $edit) {
- if ($edit->type == 'copy') {
- if (is_array($block)) {
- if (sizeof($edit->orig) <= $nlead + $ntrail) {
- $block[] = $edit;
- }
- else{
- if ($ntrail) {
- $context = array_slice($edit->orig, 0, $ntrail);
- $block[] = new _DiffOp_Copy($context);
- }
- $this->_block($x0, $ntrail + $xi - $x0,
- $y0, $ntrail + $yi - $y0,
- $block);
- $block = false;
- }
- }
- $context = $edit->orig;
- }
- else {
- if (! is_array($block)) {
- $context = array_slice($context, sizeof($context) - $nlead);
- $x0 = $xi - sizeof($context);
- $y0 = $yi - sizeof($context);
- $block = array();
- if ($context)
- $block[] = new _DiffOp_Copy($context);
+ $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 ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ } elseif ( $this->mNewRev && !$this->mNewRev->userCan( Revision::DELETED_TEXT ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ // Short-circuit
+ if ( $this->mOldRev && $this->mNewRev
+ && $this->mOldRev->getID() == $this->mNewRev->getID() )
+ {
+ wfProfileOut( __METHOD__ );
+ return '';
+ }
+ // Cacheable?
+ $key = false;
+ if ( $this->mOldid && $this->mNewid ) {
+ $key = wfMemcKey( 'diff', 'version', MW_DIFF_VERSION,
+ 'oldid', $this->mOldid, 'newid', $this->mNewid );
+ // Try cache
+ if ( !$this->mRefreshCache ) {
+ $difftext = $wgMemc->get( $key );
+ if ( $difftext ) {
+ wfIncrStats( 'diff_cache_hit' );
+ $difftext = $this->localiseLineNumbers( $difftext );
+ $difftext .= "\n<!-- diff cache key $key -->\n";
+ wfProfileOut( __METHOD__ );
+ return $difftext;
}
- $block[] = $edit;
- }
+ } // don't try to load but save the result
+ }
+ $this->mCacheHit = false;
- if ($edit->orig)
- $xi += sizeof($edit->orig);
- if ($edit->closing)
- $yi += sizeof($edit->closing);
+ // Loadtext is permission safe, this just clears out the diff
+ if ( !$this->loadText() ) {
+ wfProfileOut( __METHOD__ );
+ return false;
}
- if (is_array($block))
- $this->_block($x0, $xi - $x0,
- $y0, $yi - $y0,
- $block);
+ $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
- $end = $this->_end_diff();
+ // Save to cache for 7 days
+ if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
+ wfIncrStats( 'diff_uncacheable' );
+ } elseif ( $key !== false && $difftext !== false ) {
+ wfIncrStats( 'diff_cache_miss' );
+ $wgMemc->set( $key, $difftext, 7 * 86400 );
+ } else {
+ wfIncrStats( 'diff_uncacheable' );
+ }
+ // Replace line numbers with the text in the user's language
+ if ( $difftext !== false ) {
+ $difftext = $this->localiseLineNumbers( $difftext );
+ }
wfProfileOut( __METHOD__ );
- return $end;
+ return $difftext;
}
- function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
- wfProfileIn( __METHOD__ );
- $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
- foreach ($edits as $edit) {
- if ($edit->type == 'copy')
- $this->_context($edit->orig);
- elseif ($edit->type == 'add')
- $this->_added($edit->closing);
- elseif ($edit->type == 'delete')
- $this->_deleted($edit->orig);
- elseif ($edit->type == 'change')
- $this->_changed($edit->orig, $edit->closing);
- else
- trigger_error('Unknown edit type', E_USER_ERROR);
- }
- $this->_end_block();
- wfProfileOut( __METHOD__ );
+ /**
+ * Make sure the proper modules are loaded before we try to
+ * make the diff
+ */
+ private function initDiffEngines() {
+ global $wgExternalDiffEngine;
+ if ( $wgExternalDiffEngine == 'wikidiff' && !function_exists( 'wikidiff_do_diff' ) ) {
+ wfProfileIn( __METHOD__ . '-php_wikidiff.so' );
+ wfSuppressWarnings();
+ dl( 'php_wikidiff.so' );
+ wfRestoreWarnings();
+ wfProfileOut( __METHOD__ . '-php_wikidiff.so' );
+ }
+ else if ( $wgExternalDiffEngine == 'wikidiff2' && !function_exists( 'wikidiff2_do_diff' ) ) {
+ wfProfileIn( __METHOD__ . '-php_wikidiff2.so' );
+ wfSuppressWarnings();
+ wfDl( 'wikidiff2' );
+ wfRestoreWarnings();
+ wfProfileOut( __METHOD__ . '-php_wikidiff2.so' );
+ }
}
- function _start_diff() {
- ob_start();
- }
+ /**
+ * Generate a diff, no caching
+ *
+ * @param $otext String: old text, must be already segmented
+ * @param $ntext String: new text, must be already segmented
+ */
+ function generateDiffBody( $otext, $ntext ) {
+ global $wgExternalDiffEngine, $wgContLang;
- function _end_diff() {
- $val = ob_get_contents();
- ob_end_clean();
- return $val;
- }
+ $otext = str_replace( "\r\n", "\n", $otext );
+ $ntext = str_replace( "\r\n", "\n", $ntext );
- function _block_header($xbeg, $xlen, $ybeg, $ylen) {
- if ($xlen > 1)
- $xbeg .= "," . ($xbeg + $xlen - 1);
- if ($ylen > 1)
- $ybeg .= "," . ($ybeg + $ylen - 1);
+ $this->initDiffEngines();
- return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
- }
+ if ( $wgExternalDiffEngine == 'wikidiff' && function_exists( 'wikidiff_do_diff' ) ) {
+ # For historical reasons, external diff engine expects
+ # input text to be HTML-escaped already
+ $otext = htmlspecialchars ( $wgContLang->segmentForDiff( $otext ) );
+ $ntext = htmlspecialchars ( $wgContLang->segmentForDiff( $ntext ) );
+ return $wgContLang->unsegmentForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) .
+ $this->debug( 'wikidiff1' );
+ }
- function _start_block($header) {
- echo $header . "\n";
- }
+ 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' );
+ return $text;
+ }
+ if ( $wgExternalDiffEngine != 'wikidiff3' && $wgExternalDiffEngine !== false ) {
+ # Diff via the shell
+ global $wgTmpDirectory;
+ $tempName1 = tempnam( $wgTmpDirectory, 'diff_' );
+ $tempName2 = tempnam( $wgTmpDirectory, 'diff_' );
+
+ $tempFile1 = fopen( $tempName1, "w" );
+ if ( !$tempFile1 ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $tempFile2 = fopen( $tempName2, "w" );
+ if ( !$tempFile2 ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ fwrite( $tempFile1, $otext );
+ fwrite( $tempFile2, $ntext );
+ 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;
+ }
- function _end_block() {
+ # Native PHP diff
+ $ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) );
+ $nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
+ $diffs = new Diff( $ota, $nta );
+ $formatter = new TableDiffFormatter();
+ $difftext = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) ) .
+ wfProfileOut( __METHOD__ );
+ return $difftext;
+ $this->debug();
}
- function _lines($lines, $prefix = ' ') {
- foreach ($lines as $line)
- echo "$prefix $line\n";
+ /**
+ * Generate a debug comment indicating diff generating time,
+ * server node, and generator backend.
+ */
+ protected function debug( $generator = "internal" ) {
+ global $wgShowHostnames;
+ if ( !$this->enableDebugComment ) {
+ return '';
+ }
+ $data = array( $generator );
+ if ( $wgShowHostnames ) {
+ $data[] = wfHostname();
+ }
+ $data[] = wfTimestamp( TS_DB );
+ return "<!-- diff generator: " .
+ implode( " ",
+ array_map(
+ "htmlspecialchars",
+ $data ) ) .
+ " -->\n";
}
- function _context($lines) {
- $this->_lines($lines);
+ /**
+ * Replace line numbers with the text in the user's language
+ */
+ function localiseLineNumbers( $text ) {
+ return preg_replace_callback( '/<!--LINE (\d+)-->/',
+ array( &$this, 'localiseLineNumbersCb' ), $text );
}
- function _added($lines) {
- $this->_lines($lines, '>');
- }
- function _deleted($lines) {
- $this->_lines($lines, '<');
+ function localiseLineNumbersCb( $matches ) {
+ global $wgLang;
+ if ( $matches[1] === '1' && $this->mReducedLineNumbers ) return '';
+ return wfMsgExt( 'lineno', 'escape', $wgLang->formatNum( $matches[1] ) );
}
- function _changed($orig, $closing) {
- $this->_deleted($orig);
- echo "---\n";
- $this->_added($closing);
- }
-}
-/**
- * A formatter that outputs unified diffs
- * @ingroup DifferenceEngine
- */
+ /**
+ * If there are revisions between the ones being compared, return a note saying so.
+ * @return string
+ */
+ function getMultiNotice() {
+ if ( !is_object( $this->mOldRev ) || !is_object( $this->mNewRev ) ) {
+ return '';
+ } elseif ( !$this->mOldPage->equals( $this->mNewPage ) ) {
+ // Comparing two different pages? Count would be meaningless.
+ return '';
+ }
-class UnifiedDiffFormatter extends DiffFormatter {
- var $leading_context_lines = 2;
- var $trailing_context_lines = 2;
+ $oldid = $this->mOldRev->getId();
+ $newid = $this->mNewRev->getId();
+ if ( $oldid > $newid ) {
+ $tmp = $oldid; $oldid = $newid; $newid = $tmp;
+ }
- function _added($lines) {
- $this->_lines($lines, '+');
- }
- function _deleted($lines) {
- $this->_lines($lines, '-');
- }
- function _changed($orig, $closing) {
- $this->_deleted($orig);
- $this->_added($closing);
- }
- function _block_header($xbeg, $xlen, $ybeg, $ylen) {
- return "@@ -$xbeg,$xlen +$ybeg,$ylen @@";
+ $nEdits = $this->mTitle->countRevisionsBetween( $oldid, $newid );
+ if ( $nEdits > 0 ) {
+ $limit = 100;
+ // We use ($limit + 1) so we can detect if there are > 100 authors
+ // in a given revision range. In that case, diff-multi-manyusers is used.
+ $numUsers = $this->mTitle->countAuthorsBetween( $oldid, $newid, $limit + 1 );
+ return self::intermediateEditsMsg( $nEdits, $numUsers, $limit );
+ }
+ return ''; // nothing
}
-}
-/**
- * A pseudo-formatter that just passes along the Diff::$edits array
- * @ingroup DifferenceEngine
- */
-class ArrayDiffFormatter extends DiffFormatter {
- function format($diff) {
- $oldline = 1;
- $newline = 1;
- $retval = array();
- foreach($diff->edits as $edit)
- switch($edit->type) {
- case 'add':
- foreach($edit->closing as $l) {
- $retval[] = array(
- 'action' => 'add',
- 'new'=> $l,
- 'newline' => $newline++
- );
- }
- break;
- case 'delete':
- foreach($edit->orig as $l) {
- $retval[] = array(
- 'action' => 'delete',
- 'old' => $l,
- 'oldline' => $oldline++,
- );
- }
- break;
- case 'change':
- foreach($edit->orig as $i => $l) {
- $retval[] = array(
- 'action' => 'change',
- 'old' => $l,
- 'new' => @$edit->closing[$i],
- 'oldline' => $oldline++,
- 'newline' => $newline++,
- );
- }
- break;
- case 'copy':
- $oldline += count($edit->orig);
- $newline += count($edit->orig);
+ /**
+ * Get a notice about how many intermediate edits and users there are
+ * @param $numEdits int
+ * @param $numUsers int
+ * @param $limit int
+ * @return string
+ */
+ public static function intermediateEditsMsg( $numEdits, $numUsers, $limit ) {
+ global $wgLang;
+ if ( $numUsers > $limit ) {
+ $msg = 'diff-multi-manyusers';
+ $numUsers = $limit;
+ } else {
+ $msg = 'diff-multi';
}
- return $retval;
+ return wfMsgExt( $msg, 'parseinline',
+ $wgLang->formatnum( $numEdits ), $wgLang->formatnum( $numUsers ) );
}
-}
-/**
- * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
- *
- */
-
-define('NBSP', '&#160;'); // iso-8859-x non-breaking space.
-
-/**
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class _HWLDF_WordAccumulator {
- function _HWLDF_WordAccumulator () {
- $this->_lines = array();
- $this->_line = '';
- $this->_group = '';
- $this->_tag = '';
- }
+ /**
+ * Add the header to a diff body
+ */
+ static function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
+ $header = "<table class='diff'>";
+ if ( $diff ) { // Safari/Chrome show broken output if cols not used
+ $header .= "
+ <col class='diff-marker' />
+ <col class='diff-content' />
+ <col class='diff-marker' />
+ <col class='diff-content' />";
+ $colspan = 2;
+ $multiColspan = 4;
+ } else {
+ $colspan = 1;
+ $multiColspan = 2;
+ }
+ $header .= "
+ <tr valign='top'>
+ <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
+ <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
+ </tr>";
+
+ if ( $multi != '' ) {
+ $header .= "<tr><td colspan='{$multiColspan}' align='center' class='diff-multi'>{$multi}</td></tr>";
+ }
+ if ( $notice != '' ) {
+ $header .= "<tr><td colspan='{$multiColspan}' align='center'>{$notice}</td></tr>";
+ }
- function _flushGroup ($new_tag) {
- if ($this->_group !== '') {
- if ($this->_tag == 'ins')
- $this->_line .= '<ins class="diffchange diffchange-inline">' .
- htmlspecialchars ( $this->_group ) . '</ins>';
- elseif ($this->_tag == 'del')
- $this->_line .= '<del class="diffchange diffchange-inline">' .
- htmlspecialchars ( $this->_group ) . '</del>';
- else
- $this->_line .= htmlspecialchars ( $this->_group );
- }
- $this->_group = '';
- $this->_tag = $new_tag;
+ return $header . $diff . "</table>";
}
- function _flushLine ($new_tag) {
- $this->_flushGroup($new_tag);
- if ($this->_line != '')
- array_push ( $this->_lines, $this->_line );
- else
- # make empty lines visible by inserting an NBSP
- array_push ( $this->_lines, NBSP );
- $this->_line = '';
+ /**
+ * Use specified text instead of loading from the database
+ */
+ function setText( $oldText, $newText ) {
+ $this->mOldtext = $oldText;
+ $this->mNewtext = $newText;
+ $this->mTextLoaded = 2;
+ $this->mRevisionsLoaded = true;
}
- function addWords ($words, $tag = '') {
- if ($tag != $this->_tag)
- $this->_flushGroup($tag);
-
- foreach ($words as $word) {
- // new-line should only come as first char of word.
- if ($word == '')
- continue;
- if ($word[0] == "\n") {
- $this->_flushLine($tag);
- $word = substr($word, 1);
- }
- assert(!strstr($word, "\n"));
- $this->_group .= $word;
+ /**
+ * Load revision metadata for the specified articles. If newid is 0, then compare
+ * the old article in oldid to the current article; if oldid is 0, then
+ * compare the current article to the immediately previous one (ignoring the
+ * value of newid).
+ *
+ * If oldid is false, leave the corresponding revision object set
+ * to false. This is impossible via ordinary user input, and is provided for
+ * API convenience.
+ */
+ function loadRevisionData() {
+ global $wgLang, $wgUser;
+ if ( $this->mRevisionsLoaded ) {
+ return true;
+ } else {
+ // Whether it succeeds or fails, we don't want to try again
+ $this->mRevisionsLoaded = true;
}
- }
- function getLines() {
- $this->_flushLine('~done');
- return $this->_lines;
- }
-}
-
-/**
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class WordLevelDiff extends MappedDiff {
- const MAX_LINE_LENGTH = 10000;
-
- function WordLevelDiff ($orig_lines, $closing_lines) {
- wfProfileIn( __METHOD__ );
-
- list ($orig_words, $orig_stripped) = $this->_split($orig_lines);
- list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
-
- $this->MappedDiff($orig_words, $closing_words,
- $orig_stripped, $closing_stripped);
- wfProfileOut( __METHOD__ );
- }
+ // Load the new revision object
+ $this->mNewRev = $this->mNewid
+ ? Revision::newFromId( $this->mNewid )
+ : Revision::newFromTitle( $this->mTitle );
+ if ( !$this->mNewRev instanceof Revision )
+ return false;
- function _split($lines) {
- wfProfileIn( __METHOD__ );
+ // Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
+ $this->mNewid = $this->mNewRev->getId();
+
+ // Check if page is editable
+ $editable = $this->mNewRev->getTitle()->userCan( 'edit' );
+
+ // Set assorted variables
+ $timestamp = $wgLang->timeanddate( $this->mNewRev->getTimestamp(), true );
+ $dateofrev = $wgLang->date( $this->mNewRev->getTimestamp(), true );
+ $timeofrev = $wgLang->time( $this->mNewRev->getTimestamp(), true );
+ $this->mNewPage = $this->mNewRev->getTitle();
+ if ( $this->mNewRev->isCurrent() ) {
+ $newLink = $this->mNewPage->escapeLocalUrl( array(
+ 'oldid' => $this->mNewid
+ ) );
+ $this->mPagetitle = htmlspecialchars( wfMsg(
+ 'currentrev-asof',
+ $timestamp,
+ $dateofrev,
+ $timeofrev
+ ) );
+ $newEdit = $this->mNewPage->escapeLocalUrl( array(
+ 'action' => 'edit'
+ ) );
+
+ $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>";
+ $this->mNewtitle .= " (<a href='$newEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
+ } else {
+ $newLink = $this->mNewPage->escapeLocalUrl( array(
+ 'oldid' => $this->mNewid
+ ) );
+ $newEdit = $this->mNewPage->escapeLocalUrl( array(
+ 'action' => 'edit',
+ 'oldid' => $this->mNewid
+ ) );
+ $this->mPagetitle = htmlspecialchars( wfMsg(
+ 'revisionasof',
+ $timestamp,
+ $dateofrev,
+ $timeofrev
+ ) );
+
+ $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>";
+ $this->mNewtitle .= " (<a href='$newEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
+ }
+ if ( !$this->mNewRev->userCan( Revision::DELETED_TEXT ) ) {
+ $this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
+ } else if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $this->mNewtitle = "<span class='history-deleted'>{$this->mNewtitle}</span>";
+ }
- $words = array();
- $stripped = array();
- $first = true;
- foreach ( $lines as $line ) {
- # If the line is too long, just pretend the entire line is one big word
- # This prevents resource exhaustion problems
- if ( $first ) {
- $first = false;
+ // Load the old revision object
+ $this->mOldRev = false;
+ if ( $this->mOldid ) {
+ $this->mOldRev = Revision::newFromId( $this->mOldid );
+ } elseif ( $this->mOldid === 0 ) {
+ $rev = $this->mNewRev->getPrevious();
+ if ( $rev ) {
+ $this->mOldid = $rev->getId();
+ $this->mOldRev = $rev;
} else {
- $words[] = "\n";
- $stripped[] = "\n";
+ // No previous revision; mark to show as first-version only.
+ $this->mOldid = false;
+ $this->mOldRev = false;
}
- if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
- $words[] = $line;
- $stripped[] = $line;
- } else {
- $m = array();
- if (preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
- $line, $m))
- {
- $words = array_merge( $words, $m[0] );
- $stripped = array_merge( $stripped, $m[1] );
- }
- }
- }
- wfProfileOut( __METHOD__ );
- return array($words, $stripped);
- }
-
- function orig () {
- wfProfileIn( __METHOD__ );
- $orig = new _HWLDF_WordAccumulator;
+ } /* elseif ( $this->mOldid === false ) leave mOldRev false; */
- foreach ($this->edits as $edit) {
- if ($edit->type == 'copy')
- $orig->addWords($edit->orig);
- elseif ($edit->orig)
- $orig->addWords($edit->orig, 'del');
+ if ( is_null( $this->mOldRev ) ) {
+ return false;
}
- $lines = $orig->getLines();
- wfProfileOut( __METHOD__ );
- return $lines;
- }
- function closing () {
- wfProfileIn( __METHOD__ );
- $closing = new _HWLDF_WordAccumulator;
+ if ( $this->mOldRev ) {
+ $this->mOldPage = $this->mOldRev->getTitle();
+
+ $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
+ $dateofrev = $wgLang->date( $this->mOldRev->getTimestamp(), true );
+ $timeofrev = $wgLang->time( $this->mOldRev->getTimestamp(), true );
+ $oldLink = $this->mOldPage->escapeLocalUrl( array(
+ 'oldid' => $this->mOldid
+ ) );
+ $oldEdit = $this->mOldPage->escapeLocalUrl( array(
+ 'action' => 'edit',
+ 'oldid' => $this->mOldid
+ ) );
+ $this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t, $dateofrev, $timeofrev ) );
+
+ $this->mOldtitle = "<a href='$oldLink'>{$this->mOldPagetitle}</a>"
+ . " (<a href='$oldEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
+ // Add an "undo" link
+ $newUndo = $this->mNewPage->escapeLocalUrl( array(
+ 'action' => 'edit',
+ 'undoafter' => $this->mOldid,
+ 'undo' => $this->mNewid
+ ) );
+ $htmlLink = htmlspecialchars( wfMsg( 'editundo' ) );
+ $htmlTitle = Xml::expandAttributes( array( 'title' => $wgUser->getSkin()->titleAttrib( 'undo' ) ) );
+ if ( $editable && !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $this->mNewtitle .= " (<a href='$newUndo' $htmlTitle>" . $htmlLink . "</a>)";
+ }
- foreach ($this->edits as $edit) {
- if ($edit->type == 'copy')
- $closing->addWords($edit->closing);
- elseif ($edit->closing)
- $closing->addWords($edit->closing, 'ins');
+ if ( !$this->mOldRev->userCan( Revision::DELETED_TEXT ) ) {
+ $this->mOldtitle = '<span class="history-deleted">' . $this->mOldPagetitle . '</span>';
+ } else if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $this->mOldtitle = '<span class="history-deleted">' . $this->mOldtitle . '</span>';
+ }
}
- $lines = $closing->getLines();
- wfProfileOut( __METHOD__ );
- return $lines;
- }
-}
-
-/**
- * Wikipedia Table style diff formatter.
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class TableDiffFormatter extends DiffFormatter {
- function TableDiffFormatter() {
- $this->leading_context_lines = 2;
- $this->trailing_context_lines = 2;
- }
-
- public static function escapeWhiteSpace( $msg ) {
- $msg = preg_replace( '/^ /m', '&nbsp; ', $msg );
- $msg = preg_replace( '/ $/m', ' &nbsp;', $msg );
- $msg = preg_replace( '/ /', '&nbsp; ', $msg );
- return $msg;
- }
- function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
- $r = '<tr><td colspan="2" class="diff-lineno"><!--LINE '.$xbeg."--></td>\n" .
- '<td colspan="2" class="diff-lineno"><!--LINE '.$ybeg."--></td></tr>\n";
- return $r;
- }
-
- function _start_block( $header ) {
- echo $header;
- }
-
- function _end_block() {
- }
-
- function _lines( $lines, $prefix=' ', $color='white' ) {
- }
-
- # HTML-escape parameter before calling this
- function addedLine( $line ) {
- return $this->wrapLine( '+', 'diff-addedline', $line );
- }
-
- # HTML-escape parameter before calling this
- function deletedLine( $line ) {
- return $this->wrapLine( '-', 'diff-deletedline', $line );
- }
-
- # HTML-escape parameter before calling this
- function contextLine( $line ) {
- return $this->wrapLine( ' ', 'diff-context', $line );
+ return true;
}
- private function wrapLine( $marker, $class, $line ) {
- if( $line !== '' ) {
- // The <div> wrapper is needed for 'overflow: auto' style to scroll properly
- $line = Xml::tags( 'div', null, $this->escapeWhiteSpace( $line ) );
+ /**
+ * Load the text of the revisions, as well as revision data.
+ */
+ function loadText() {
+ if ( $this->mTextLoaded == 2 ) {
+ return true;
+ } else {
+ // Whether it succeeds or fails, we don't want to try again
+ $this->mTextLoaded = 2;
}
- return "<td class='diff-marker'>$marker</td><td class='$class'>$line</td>";
- }
- function emptyLine() {
- return '<td colspan="2">&nbsp;</td>';
- }
-
- function _added( $lines ) {
- foreach ($lines as $line) {
- echo '<tr>' . $this->emptyLine() .
- $this->addedLine( '<ins class="diffchange">' .
- htmlspecialchars ( $line ) . '</ins>' ) . "</tr>\n";
+ if ( !$this->loadRevisionData() ) {
+ return false;
}
- }
-
- function _deleted($lines) {
- foreach ($lines as $line) {
- echo '<tr>' . $this->deletedLine( '<del class="diffchange">' .
- htmlspecialchars ( $line ) . '</del>' ) .
- $this->emptyLine() . "</tr>\n";
+ if ( $this->mOldRev ) {
+ $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER );
+ if ( $this->mOldtext === false ) {
+ return false;
+ }
}
- }
-
- function _context( $lines ) {
- foreach ($lines as $line) {
- echo '<tr>' .
- $this->contextLine( htmlspecialchars ( $line ) ) .
- $this->contextLine( htmlspecialchars ( $line ) ) . "</tr>\n";
+ if ( $this->mNewRev ) {
+ $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
+ if ( $this->mNewtext === false ) {
+ return false;
+ }
}
+ return true;
}
- function _changed( $orig, $closing ) {
- wfProfileIn( __METHOD__ );
-
- $diff = new WordLevelDiff( $orig, $closing );
- $del = $diff->orig();
- $add = $diff->closing();
-
- # Notice that WordLevelDiff returns HTML-escaped output.
- # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
-
- while ( $line = array_shift( $del ) ) {
- $aline = array_shift( $add );
- echo '<tr>' . $this->deletedLine( $line ) .
- $this->addedLine( $aline ) . "</tr>\n";
+ /**
+ * Load the text of the new revision, not the old one
+ */
+ function loadNewText() {
+ if ( $this->mTextLoaded >= 1 ) {
+ return true;
+ } else {
+ $this->mTextLoaded = 1;
}
- foreach ($add as $line) { # If any leftovers
- echo '<tr>' . $this->emptyLine() .
- $this->addedLine( $line ) . "</tr>\n";
+ if ( !$this->loadRevisionData() ) {
+ return false;
}
- wfProfileOut( __METHOD__ );
+ $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
+ return true;
}
}
diff --git a/includes/diff/DifferenceInterface.php b/includes/diff/DifferenceInterface.php
deleted file mode 100644
index 0e9ca9f6..00000000
--- a/includes/diff/DifferenceInterface.php
+++ /dev/null
@@ -1,1024 +0,0 @@
-<?php
-/**
- * @defgroup DifferenceEngine DifferenceEngine
- */
-
-/**
- * Constant to indicate diff cache compatibility.
- * Bump this when changing the diff formatting in a way that
- * fixes important bugs or such to force cached diff views to
- * clear.
- */
-define( 'MW_DIFF_VERSION', '1.11a' );
-
-/**
- * @todo document
- * @ingroup DifferenceEngine
- */
-class DifferenceEngine {
- /**#@+
- * @private
- */
- var $mOldid, $mNewid, $mTitle;
- var $mOldtitle, $mNewtitle, $mPagetitle;
- var $mOldtext, $mNewtext;
- var $mOldPage, $mNewPage;
- var $mRcidMarkPatrolled;
- var $mOldRev, $mNewRev;
- var $mRevisionsLoaded = false; // Have the revisions been loaded
- var $mTextLoaded = 0; // How many text blobs have been loaded, 0, 1 or 2?
- var $mCacheHit = false; // Was the diff fetched from cache?
-
- /**
- * Set this to true to add debug info to the HTML output.
- * Warning: this may cause RSS readers to spuriously mark articles as "new"
- * (bug 20601)
- */
- var $enableDebugComment = false;
-
- // If true, line X is not displayed when X is 1, for example to increase
- // readability and conserve space with many small diffs.
- protected $mReducedLineNumbers = false;
-
- protected $unhide = false;
- /**#@-*/
-
- /**
- * Constructor
- * @param $titleObj Title object that the diff is associated with
- * @param $old Integer: old ID we want to show and diff with.
- * @param $new String: either 'prev' or 'next'.
- * @param $rcid Integer: ??? FIXME (default 0)
- * @param $refreshCache boolean If set, refreshes the diff cache
- * @param $unhide boolean If set, allow viewing deleted revs
- */
- function __construct( $titleObj = null, $old = 0, $new = 0, $rcid = 0,
- $refreshCache = false, $unhide = false )
- {
- if ( $titleObj ) {
- $this->mTitle = $titleObj;
- } else {
- global $wgTitle;
- $this->mTitle = $wgTitle;
- }
- wfDebug("DifferenceEngine old '$old' new '$new' rcid '$rcid'\n");
-
- if ( 'prev' === $new ) {
- # Show diff between revision $old and the previous one.
- # Get previous one from DB.
- $this->mNewid = intval($old);
- $this->mOldid = $this->mTitle->getPreviousRevisionID( $this->mNewid );
- } elseif ( 'next' === $new ) {
- # Show diff between revision $old and the next one.
- # Get next one from DB.
- $this->mOldid = intval($old);
- $this->mNewid = $this->mTitle->getNextRevisionID( $this->mOldid );
- if ( false === $this->mNewid ) {
- # if no result, NewId points to the newest old revision. The only newer
- # revision is cur, which is "0".
- $this->mNewid = 0;
- }
- } else {
- $this->mOldid = intval($old);
- $this->mNewid = intval($new);
- wfRunHooks( 'NewDifferenceEngine', array(&$titleObj, &$this->mOldid, &$this->mNewid, $old, $new) );
- }
- $this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer
- $this->mRefreshCache = $refreshCache;
- $this->unhide = $unhide;
- }
-
- function setReducedLineNumbers( $value = true ) {
- $this->mReducedLineNumbers = $value;
- }
-
- function getTitle() {
- return $this->mTitle;
- }
-
- function wasCacheHit() {
- return $this->mCacheHit;
- }
-
- function getOldid() {
- return $this->mOldid;
- }
-
- function getNewid() {
- return $this->mNewid;
- }
-
- function showDiffPage( $diffOnly = false ) {
- global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
- wfProfileIn( __METHOD__ );
-
- # Allow frames except in certain special cases
- $wgOut->allowClickjacking();
-
- # If external diffs are enabled both globally and for the user,
- # we'll use the application/x-external-editor interface to call
- # an external diff tool like kompare, kdiff3, etc.
- if($wgUseExternalEditor && $wgUser->getOption('externaldiff')) {
- global $wgInputEncoding,$wgServer,$wgScript,$wgLang;
- $wgOut->disable();
- header ( "Content-type: application/x-external-editor; charset=".$wgInputEncoding );
- $url1=$this->mTitle->getFullURL( array(
- 'action' => 'raw',
- 'oldid' => $this->mOldid
- ) );
- $url2=$this->mTitle->getFullURL( array(
- 'action' => 'raw',
- 'oldid' => $this->mNewid
- ) );
- $special=$wgLang->getNsText(NS_SPECIAL);
- $control=<<<CONTROL
- [Process]
- Type=Diff text
- Engine=MediaWiki
- Script={$wgServer}{$wgScript}
- Special namespace={$special}
-
- [File]
- Extension=wiki
- URL=$url1
-
- [File 2]
- Extension=wiki
- URL=$url2
-CONTROL;
- echo($control);
- return;
- }
-
- $wgOut->setArticleFlag( false );
- if ( !$this->loadRevisionData() ) {
- $t = $this->mTitle->getPrefixedText();
- $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ), $this->mOldid, $this->mNewid );
- $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
- $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", $d );
- wfProfileOut( __METHOD__ );
- return;
- }
-
- wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
-
- if ( $this->mNewRev->isCurrent() ) {
- $wgOut->setArticleFlag( true );
- }
-
- # mOldid is false if the difference engine is called with a "vague" query for
- # a diff between a version V and its previous version V' AND the version V
- # is the first version of that article. In that case, V' does not exist.
- if ( $this->mOldid === false ) {
- $this->showFirstRevision();
- $this->renderNewRevision(); // should we respect $diffOnly here or not?
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $wgOut->suppressQuickbar();
-
- $oldTitle = $this->mOldPage->getPrefixedText();
- $newTitle = $this->mNewPage->getPrefixedText();
- if( $oldTitle == $newTitle ) {
- $wgOut->setPageTitle( $newTitle );
- } else {
- $wgOut->setPageTitle( $oldTitle . ', ' . $newTitle );
- }
- $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
-
- if ( !$this->mOldPage->userCanRead() || !$this->mNewPage->userCanRead() ) {
- $wgOut->loginToUse();
- $wgOut->output();
- $wgOut->disable();
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $sk = $wgUser->getSkin();
-
- // Check if page is editable
- $editable = $this->mNewRev->getTitle()->userCan( 'edit' );
- if ( $editable && $this->mNewRev->isCurrent() && $wgUser->isAllowed( 'rollback' ) ) {
- $wgOut->preventClickjacking();
- $rollback = '&nbsp;&nbsp;&nbsp;' . $sk->generateRollback( $this->mNewRev );
- } else {
- $rollback = '';
- }
-
- // Prepare a change patrol link, if applicable
- if( $wgUseRCPatrol && $this->mTitle->userCan('patrol') ) {
- // If we've been given an explicit change identifier, use it; saves time
- if( $this->mRcidMarkPatrolled ) {
- $rcid = $this->mRcidMarkPatrolled;
- $rc = RecentChange::newFromId( $rcid );
- // Already patrolled?
- $rcid = is_object($rc) && !$rc->getAttribute('rc_patrolled') ? $rcid : 0;
- } else {
- // Look for an unpatrolled change corresponding to this diff
- $db = wfGetDB( DB_SLAVE );
- $change = RecentChange::newFromConds(
- array(
- // Redundant user,timestamp condition so we can use the existing index
- 'rc_user_text' => $this->mNewRev->getRawUserText(),
- 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
- 'rc_this_oldid' => $this->mNewid,
- 'rc_last_oldid' => $this->mOldid,
- 'rc_patrolled' => 0
- ),
- __METHOD__
- );
- if( $change instanceof RecentChange ) {
- $rcid = $change->mAttribs['rc_id'];
- $this->mRcidMarkPatrolled = $rcid;
- } else {
- // None found
- $rcid = 0;
- }
- }
- // Build the link
- if( $rcid ) {
- $patrol = ' <span class="patrollink">[' . $sk->link(
- $this->mTitle,
- wfMsgHtml( 'markaspatrolleddiff' ),
- array(),
- array(
- 'action' => 'markpatrolled',
- 'rcid' => $rcid
- ),
- array(
- 'known',
- 'noclasses'
- )
- ) . ']</span>';
- } else {
- $patrol = '';
- }
- } else {
- $patrol = '';
- }
-
- # Carry over 'diffonly' param via navigation links
- if( $diffOnly != $wgUser->getBoolOption('diffonly') ) {
- $query['diffonly'] = $diffOnly;
- }
-
- # Make "previous revision link"
- $query['diff'] = 'prev';
- $query['oldid'] = $this->mOldid;
- # Cascade unhide param in links for easy deletion browsing
- if( $this->unhide ) {
- $query['unhide'] = 1;
- }
- $prevlink = $sk->link(
- $this->mTitle,
- wfMsgHtml( 'previousdiff' ),
- array(
- 'id' => 'differences-prevlink'
- ),
- $query,
- array(
- 'known',
- 'noclasses'
- )
- );
-
- # Make "next revision link"
- $query['diff'] = 'next';
- $query['oldid'] = $this->mNewid;
- # Skip next link on the top revision
- if( $this->mNewRev->isCurrent() ) {
- $nextlink = '&nbsp;';
- } else {
- $nextlink = $sk->link(
- $this->mTitle,
- wfMsgHtml( 'nextdiff' ),
- array(
- 'id' => 'differences-nextlink'
- ),
- $query,
- array(
- 'known',
- 'noclasses'
- )
- );
- }
-
- $oldminor = '';
- $newminor = '';
-
- if( $this->mOldRev->isMinor() ) {
- $oldminor = ChangesList::flag( 'minor' );
- }
- if( $this->mNewRev->isMinor() ) {
- $newminor = ChangesList::flag( 'minor' );
- }
-
- # Handle RevisionDelete links...
- $ldel = $this->revisionDeleteLink( $this->mOldRev );
- $rdel = $this->revisionDeleteLink( $this->mNewRev );
-
- $oldHeader = '<div id="mw-diff-otitle1"><strong>'.$this->mOldtitle.'</strong></div>' .
- '<div id="mw-diff-otitle2">' .
- $sk->revUserTools( $this->mOldRev, !$this->unhide ).'</div>' .
- '<div id="mw-diff-otitle3">' . $oldminor .
- $sk->revComment( $this->mOldRev, !$diffOnly, !$this->unhide ).$ldel.'</div>' .
- '<div id="mw-diff-otitle4">' . $prevlink .'</div>';
- $newHeader = '<div id="mw-diff-ntitle1"><strong>'.$this->mNewtitle.'</strong></div>' .
- '<div id="mw-diff-ntitle2">' . $sk->revUserTools( $this->mNewRev, !$this->unhide ) .
- " $rollback</div>" .
- '<div id="mw-diff-ntitle3">' . $newminor .
- $sk->revComment( $this->mNewRev, !$diffOnly, !$this->unhide ).$rdel.'</div>' .
- '<div id="mw-diff-ntitle4">' . $nextlink . $patrol . '</div>';
-
- # Check if this user can see the revisions
- $allowed = $this->mOldRev->userCan(Revision::DELETED_TEXT)
- && $this->mNewRev->userCan(Revision::DELETED_TEXT);
- # Check if one of the revisions is deleted/suppressed
- $deleted = $suppressed = false;
- if( $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
- $deleted = true; // old revisions text is hidden
- if( $this->mOldRev->isDeleted(Revision::DELETED_RESTRICTED) )
- $suppressed = true; // also suppressed
- }
- if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
- $deleted = true; // new revisions text is hidden
- if( $this->mNewRev->isDeleted(Revision::DELETED_RESTRICTED) )
- $suppressed = true; // also suppressed
- }
- # If the diff cannot be shown due to a deleted revision, then output
- # the diff header and links to unhide (if available)...
- if( $deleted && (!$this->unhide || !$allowed) ) {
- $this->showDiffStyle();
- $multi = $this->getMultiNotice();
- $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) );
- if( !$allowed ) {
- $msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff';
- # Give explanation for why revision is not visible
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n",
- array( $msg ) );
- } else {
- # Give explanation and add a link to view the diff...
- $link = $this->mTitle->getFullUrl( array(
- 'diff' => $this->mNewid,
- 'oldid' => $this->mOldid,
- 'unhide' => 1
- ) );
- $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", array( $msg, $link ) );
- }
- # Otherwise, output a regular diff...
- } else {
- # Add deletion notice if the user is viewing deleted content
- $notice = '';
- if( $deleted ) {
- $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view';
- $notice = "<div class='mw-warning plainlinks'>\n".wfMsgExt($msg,'parseinline')."</div>\n";
- }
- $this->showDiff( $oldHeader, $newHeader, $notice );
- if( !$diffOnly ) {
- $this->renderNewRevision();
- }
- }
- wfProfileOut( __METHOD__ );
- }
-
- protected function revisionDeleteLink( $rev ) {
- global $wgUser;
- $link = '';
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- // Show del/undel link if:
- // (a) the user can delete revisions, or
- // (b) the user can view deleted revision *and* this one is deleted
- if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' )) ) {
- $sk = $wgUser->getSkin();
- if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
- $link = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
- } else {
- $query = array(
- 'type' => 'revision',
- 'target' => $rev->mTitle->getPrefixedDbkey(),
- 'ids' => $rev->getId()
- );
- $link = $sk->revDeleteLink( $query,
- $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
- }
- $link = '&nbsp;&nbsp;&nbsp;' . $link . ' ';
- }
- return $link;
- }
-
- /**
- * Show the new revision of the page.
- */
- function renderNewRevision() {
- global $wgOut, $wgUser;
- wfProfileIn( __METHOD__ );
-
- $wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
- # Add deleted rev tag if needed
- if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' );
- } else if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' );
- }
-
- if( !$this->mNewRev->isCurrent() ) {
- $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
- }
-
- $this->loadNewText();
- if( is_object( $this->mNewRev ) ) {
- $wgOut->setRevisionId( $this->mNewRev->getId() );
- }
-
- if( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
- // Stolen from Article::view --AG 2007-10-11
- // Give hooks a chance to customise the output
- if( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mTitle, $wgOut ) ) ) {
- // Wrap the whole lot in a <pre> and don't parse
- $m = array();
- preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
- $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
- $wgOut->addHTML( htmlspecialchars( $this->mNewtext ) );
- $wgOut->addHTML( "\n</pre>\n" );
- }
- } else {
- $wgOut->addWikiTextTidy( $this->mNewtext );
- }
-
- if( is_object( $this->mNewRev ) && !$this->mNewRev->isCurrent() ) {
- $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
- }
- # Add redundant patrol link on bottom...
- if( $this->mRcidMarkPatrolled && $this->mTitle->quickUserCan('patrol') ) {
- $sk = $wgUser->getSkin();
- $wgOut->addHTML(
- "<div class='patrollink'>[" . $sk->link(
- $this->mTitle,
- wfMsgHtml( 'markaspatrolleddiff' ),
- array(),
- array(
- 'action' => 'markpatrolled',
- 'rcid' => $this->mRcidMarkPatrolled
- )
- ) . ']</div>'
- );
- }
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Show the first revision of an article. Uses normal diff headers in
- * contrast to normal "old revision" display style.
- */
- function showFirstRevision() {
- global $wgOut, $wgUser;
- wfProfileIn( __METHOD__ );
-
- # Get article text from the DB
- #
- if ( ! $this->loadNewText() ) {
- $t = $this->mTitle->getPrefixedText();
- $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ), $this->mOldid, $this->mNewid );
- $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
- $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", $d );
- wfProfileOut( __METHOD__ );
- return;
- }
- if ( $this->mNewRev->isCurrent() ) {
- $wgOut->setArticleFlag( true );
- }
-
- # Check if user is allowed to look at this page. If not, bail out.
- #
- if ( !$this->mTitle->userCanRead() ) {
- $wgOut->loginToUse();
- $wgOut->output();
- wfProfileOut( __METHOD__ );
- throw new MWException("Permission Error: you do not have access to view this page");
- }
-
- # Prepare the header box
- #
- $sk = $wgUser->getSkin();
-
- $next = $this->mTitle->getNextRevisionID( $this->mNewid );
- if( !$next ) {
- $nextlink = '';
- } else {
- $nextlink = '<br />' . $sk->link(
- $this->mTitle,
- wfMsgHtml( 'nextdiff' ),
- array(
- 'id' => 'differences-nextlink'
- ),
- array(
- 'diff' => 'next',
- 'oldid' => $this->mNewid,
- ),
- array(
- 'known',
- 'noclasses'
- )
- );
- }
- $header = "<div class=\"firstrevisionheader\" style=\"text-align: center\">" .
- $sk->revUserTools( $this->mNewRev ) . "<br />" . $sk->revComment( $this->mNewRev ) . $nextlink . "</div>\n";
-
- $wgOut->addHTML( $header );
-
- $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Get the diff text, send it to $wgOut
- * Returns false if the diff could not be generated, otherwise returns true
- */
- function showDiff( $otitle, $ntitle, $notice = '' ) {
- global $wgOut;
- $diff = $this->getDiff( $otitle, $ntitle, $notice );
- if ( $diff === false ) {
- $wgOut->addWikiMsg( 'missing-article', "<nowiki>(fixme, bug)</nowiki>", '' );
- return false;
- } else {
- $this->showDiffStyle();
- $wgOut->addHTML( $diff );
- return true;
- }
- }
-
- /**
- * Add style sheets and supporting JS for diff display.
- */
- function showDiffStyle() {
- global $wgStylePath, $wgStyleVersion, $wgOut;
- $wgOut->addStyle( 'common/diff.css' );
-
- // JS is needed to detect old versions of Mozilla to work around an annoyance bug.
- $wgOut->addScript( "<script type=\"text/javascript\" src=\"$wgStylePath/common/diff.js?$wgStyleVersion\"></script>" );
- }
-
- /**
- * Get complete diff table, including header
- *
- * @param Title $otitle Old title
- * @param Title $ntitle New title
- * @param string $notice HTML between diff header and body
- * @return mixed
- */
- function getDiff( $otitle, $ntitle, $notice = '' ) {
- $body = $this->getDiffBody();
- if ( $body === false ) {
- return false;
- } else {
- $multi = $this->getMultiNotice();
- return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
- }
- }
-
- /**
- * Get the diff table body, without header
- *
- * @return mixed
- */
- function getDiffBody() {
- global $wgMemc;
- wfProfileIn( __METHOD__ );
- $this->mCacheHit = true;
- // Check if the diff should be hidden from this user
- if ( !$this->loadRevisionData() )
- return '';
- if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
- return '';
- } else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
- return '';
- } else if ( $this->mOldRev && $this->mNewRev && $this->mOldRev->getID() == $this->mNewRev->getID() ) {
- return '';
- }
- // Cacheable?
- $key = false;
- if ( $this->mOldid && $this->mNewid ) {
- $key = wfMemcKey( 'diff', 'version', MW_DIFF_VERSION, 'oldid', $this->mOldid, 'newid', $this->mNewid );
- // Try cache
- if ( !$this->mRefreshCache ) {
- $difftext = $wgMemc->get( $key );
- if ( $difftext ) {
- wfIncrStats( 'diff_cache_hit' );
- $difftext = $this->localiseLineNumbers( $difftext );
- $difftext .= "\n<!-- diff cache key $key -->\n";
- wfProfileOut( __METHOD__ );
- return $difftext;
- }
- } // don't try to load but save the result
- }
- $this->mCacheHit = false;
-
- // Loadtext is permission safe, this just clears out the diff
- if ( !$this->loadText() ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
-
- // Save to cache for 7 days
- if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
- wfIncrStats( 'diff_uncacheable' );
- } else if ( $key !== false && $difftext !== false ) {
- wfIncrStats( 'diff_cache_miss' );
- $wgMemc->set( $key, $difftext, 7*86400 );
- } else {
- wfIncrStats( 'diff_uncacheable' );
- }
- // Replace line numbers with the text in the user's language
- if ( $difftext !== false ) {
- $difftext = $this->localiseLineNumbers( $difftext );
- }
- wfProfileOut( __METHOD__ );
- return $difftext;
- }
-
- /**
- * Make sure the proper modules are loaded before we try to
- * make the diff
- */
- private function initDiffEngines() {
- global $wgExternalDiffEngine;
- if ( $wgExternalDiffEngine == 'wikidiff' && !function_exists( 'wikidiff_do_diff' ) ) {
- wfProfileIn( __METHOD__ . '-php_wikidiff.so' );
- wfSuppressWarnings();
- dl( 'php_wikidiff.so' );
- wfRestoreWarnings();
- wfProfileOut( __METHOD__ . '-php_wikidiff.so' );
- }
- else if ( $wgExternalDiffEngine == 'wikidiff2' && !function_exists( 'wikidiff2_do_diff' ) ) {
- wfProfileIn( __METHOD__ . '-php_wikidiff2.so' );
- wfSuppressWarnings();
- dl( 'php_wikidiff2.so' );
- wfRestoreWarnings();
- wfProfileOut( __METHOD__ . '-php_wikidiff2.so' );
- }
- }
-
- /**
- * Generate a diff, no caching
- * $otext and $ntext must be already segmented
- */
- function generateDiffBody( $otext, $ntext ) {
- global $wgExternalDiffEngine, $wgContLang;
-
- $otext = str_replace( "\r\n", "\n", $otext );
- $ntext = str_replace( "\r\n", "\n", $ntext );
-
- $this->initDiffEngines();
-
- if ( $wgExternalDiffEngine == 'wikidiff' && function_exists( 'wikidiff_do_diff' ) ) {
- # For historical reasons, external diff engine expects
- # input text to be HTML-escaped already
- $otext = htmlspecialchars ( $wgContLang->segmentForDiff( $otext ) );
- $ntext = htmlspecialchars ( $wgContLang->segmentForDiff( $ntext ) );
- return $wgContLang->unsegementForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) .
- $this->debug( 'wikidiff1' );
- }
-
- 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' );
- return $text;
- }
- if ( $wgExternalDiffEngine != 'wikidiff3' && $wgExternalDiffEngine !== false ) {
- # Diff via the shell
- global $wgTmpDirectory;
- $tempName1 = tempnam( $wgTmpDirectory, 'diff_' );
- $tempName2 = tempnam( $wgTmpDirectory, 'diff_' );
-
- $tempFile1 = fopen( $tempName1, "w" );
- if ( !$tempFile1 ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- $tempFile2 = fopen( $tempName2, "w" );
- if ( !$tempFile2 ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- fwrite( $tempFile1, $otext );
- fwrite( $tempFile2, $ntext );
- 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 );
- return $difftext;
- }
-
- # Native PHP diff
- $ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) );
- $nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
- $diffs = new Diff( $ota, $nta );
- $formatter = new TableDiffFormatter();
- return $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) ) .
- $this->debug();
- }
-
- /**
- * Generate a debug comment indicating diff generating time,
- * server node, and generator backend.
- */
- protected function debug( $generator="internal" ) {
- global $wgShowHostnames;
- if ( !$this->enableDebugComment ) {
- return '';
- }
- $data = array( $generator );
- if( $wgShowHostnames ) {
- $data[] = wfHostname();
- }
- $data[] = wfTimestamp( TS_DB );
- return "<!-- diff generator: " .
- implode( " ",
- array_map(
- "htmlspecialchars",
- $data ) ) .
- " -->\n";
- }
-
- /**
- * Replace line numbers with the text in the user's language
- */
- function localiseLineNumbers( $text ) {
- return preg_replace_callback( '/<!--LINE (\d+)-->/',
- array( &$this, 'localiseLineNumbersCb' ), $text );
- }
-
- function localiseLineNumbersCb( $matches ) {
- global $wgLang;
- if ( $matches[1] === '1' && $this->mReducedLineNumbers ) return '';
- return wfMsgExt( 'lineno', 'escape', $wgLang->formatNum( $matches[1] ) );
- }
-
-
- /**
- * If there are revisions between the ones being compared, return a note saying so.
- */
- function getMultiNotice() {
- if ( !is_object($this->mOldRev) || !is_object($this->mNewRev) )
- return '';
-
- if( !$this->mOldPage->equals( $this->mNewPage ) ) {
- // Comparing two different pages? Count would be meaningless.
- return '';
- }
-
- $oldid = $this->mOldRev->getId();
- $newid = $this->mNewRev->getId();
- if ( $oldid > $newid ) {
- $tmp = $oldid; $oldid = $newid; $newid = $tmp;
- }
-
- $n = $this->mTitle->countRevisionsBetween( $oldid, $newid );
- if ( !$n )
- return '';
-
- return wfMsgExt( 'diff-multi', array( 'parseinline' ), $n );
- }
-
-
- /**
- * Add the header to a diff body
- */
- static function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
- $header = "<table class='diff'>";
- if( $diff ) { // Safari/Chrome show broken output if cols not used
- $header .= "
- <col class='diff-marker' />
- <col class='diff-content' />
- <col class='diff-marker' />
- <col class='diff-content' />";
- $colspan = 2;
- $multiColspan = 4;
- } else {
- $colspan = 1;
- $multiColspan = 2;
- }
- $header .= "
- <tr valign='top'>
- <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
- <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
- </tr>";
-
- if ( $multi != '' ) {
- $header .= "<tr><td colspan='{$multiColspan}' align='center' class='diff-multi'>{$multi}</td></tr>";
- }
- if ( $notice != '' ) {
- $header .= "<tr><td colspan='{$multiColspan}' align='center'>{$notice}</td></tr>";
- }
-
- return $header . $diff . "</table>";
- }
-
- /**
- * Use specified text instead of loading from the database
- */
- function setText( $oldText, $newText ) {
- $this->mOldtext = $oldText;
- $this->mNewtext = $newText;
- $this->mTextLoaded = 2;
- $this->mRevisionsLoaded = true;
- }
-
- /**
- * Load revision metadata for the specified articles. If newid is 0, then compare
- * the old article in oldid to the current article; if oldid is 0, then
- * compare the current article to the immediately previous one (ignoring the
- * value of newid).
- *
- * If oldid is false, leave the corresponding revision object set
- * to false. This is impossible via ordinary user input, and is provided for
- * API convenience.
- */
- function loadRevisionData() {
- global $wgLang, $wgUser;
- if ( $this->mRevisionsLoaded ) {
- return true;
- } else {
- // Whether it succeeds or fails, we don't want to try again
- $this->mRevisionsLoaded = true;
- }
-
- // Load the new revision object
- $this->mNewRev = $this->mNewid
- ? Revision::newFromId( $this->mNewid )
- : Revision::newFromTitle( $this->mTitle );
- if( !$this->mNewRev instanceof Revision )
- return false;
-
- // Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
- $this->mNewid = $this->mNewRev->getId();
-
- // Check if page is editable
- $editable = $this->mNewRev->getTitle()->userCan( 'edit' );
-
- // Set assorted variables
- $timestamp = $wgLang->timeanddate( $this->mNewRev->getTimestamp(), true );
- $dateofrev = $wgLang->date( $this->mNewRev->getTimestamp(), true );
- $timeofrev = $wgLang->time( $this->mNewRev->getTimestamp(), true );
- $this->mNewPage = $this->mNewRev->getTitle();
- if( $this->mNewRev->isCurrent() ) {
- $newLink = $this->mNewPage->escapeLocalUrl( array(
- 'oldid' => $this->mNewid
- ) );
- $this->mPagetitle = htmlspecialchars( wfMsg(
- 'currentrev-asof',
- $timestamp,
- $dateofrev,
- $timeofrev
- ) );
- $newEdit = $this->mNewPage->escapeLocalUrl( array(
- 'action' => 'edit'
- ) );
-
- $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>";
- $this->mNewtitle .= " (<a href='$newEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
- } else {
- $newLink = $this->mNewPage->escapeLocalUrl( array(
- 'oldid' => $this->mNewid
- ) );
- $newEdit = $this->mNewPage->escapeLocalUrl( array(
- 'action' => 'edit',
- 'oldid' => $this->mNewid
- ) );
- $this->mPagetitle = htmlspecialchars( wfMsg(
- 'revisionasof',
- $timestamp,
- $dateofrev,
- $timeofrev
- ) );
-
- $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>";
- $this->mNewtitle .= " (<a href='$newEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
- }
- if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
- $this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
- } else if ( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
- $this->mNewtitle = "<span class='history-deleted'>{$this->mNewtitle}</span>";
- }
-
- // Load the old revision object
- $this->mOldRev = false;
- if( $this->mOldid ) {
- $this->mOldRev = Revision::newFromId( $this->mOldid );
- } elseif ( $this->mOldid === 0 ) {
- $rev = $this->mNewRev->getPrevious();
- if( $rev ) {
- $this->mOldid = $rev->getId();
- $this->mOldRev = $rev;
- } else {
- // No previous revision; mark to show as first-version only.
- $this->mOldid = false;
- $this->mOldRev = false;
- }
- }/* elseif ( $this->mOldid === false ) leave mOldRev false; */
-
- if( is_null( $this->mOldRev ) ) {
- return false;
- }
-
- if ( $this->mOldRev ) {
- $this->mOldPage = $this->mOldRev->getTitle();
-
- $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
- $dateofrev = $wgLang->date( $this->mOldRev->getTimestamp(), true );
- $timeofrev = $wgLang->time( $this->mOldRev->getTimestamp(), true );
- $oldLink = $this->mOldPage->escapeLocalUrl( array(
- 'oldid' => $this->mOldid
- ) );
- $oldEdit = $this->mOldPage->escapeLocalUrl( array(
- 'action' => 'edit',
- 'oldid' => $this->mOldid
- ) );
- $this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t, $dateofrev, $timeofrev ) );
-
- $this->mOldtitle = "<a href='$oldLink'>{$this->mOldPagetitle}</a>"
- . " (<a href='$oldEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
- // Add an "undo" link
- $newUndo = $this->mNewPage->escapeLocalUrl( array(
- 'action' => 'edit',
- 'undoafter' => $this->mOldid,
- 'undo' => $this->mNewid
- ) );
- $htmlLink = htmlspecialchars( wfMsg( 'editundo' ) );
- $htmlTitle = $wgUser->getSkin()->tooltip( 'undo' );
- if( $editable && !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
- $this->mNewtitle .= " (<a href='$newUndo' $htmlTitle>" . $htmlLink . "</a>)";
- }
-
- if( !$this->mOldRev->userCan( Revision::DELETED_TEXT ) ) {
- $this->mOldtitle = '<span class="history-deleted">' . $this->mOldPagetitle . '</span>';
- } else if( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
- $this->mOldtitle = '<span class="history-deleted">' . $this->mOldtitle . '</span>';
- }
- }
-
- return true;
- }
-
- /**
- * Load the text of the revisions, as well as revision data.
- */
- function loadText() {
- if ( $this->mTextLoaded == 2 ) {
- return true;
- } else {
- // Whether it succeeds or fails, we don't want to try again
- $this->mTextLoaded = 2;
- }
-
- if ( !$this->loadRevisionData() ) {
- return false;
- }
- if ( $this->mOldRev ) {
- $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER );
- if ( $this->mOldtext === false ) {
- return false;
- }
- }
- if ( $this->mNewRev ) {
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
- if ( $this->mNewtext === false ) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Load the text of the new revision, not the old one
- */
- function loadNewText() {
- if ( $this->mTextLoaded >= 1 ) {
- return true;
- } else {
- $this->mTextLoaded = 1;
- }
- if ( !$this->loadRevisionData() ) {
- return false;
- }
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
- return true;
- }
-}
diff --git a/includes/diff/WikiDiff.php b/includes/diff/WikiDiff.php
new file mode 100644
index 00000000..2d904c96
--- /dev/null
+++ b/includes/diff/WikiDiff.php
@@ -0,0 +1,1241 @@
+<?php
+/**
+ * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
+ *
+ * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * You may copy this code freely under the conditions of the GPL.
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ * @defgroup DifferenceEngine DifferenceEngine
+ */
+
+/**
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class _DiffOp {
+ var $type;
+ var $orig;
+ var $closing;
+
+ function reverse() {
+ trigger_error( 'pure virtual', E_USER_ERROR );
+ }
+
+ function norig() {
+ return $this->orig ? sizeof( $this->orig ) : 0;
+ }
+
+ function nclosing() {
+ return $this->closing ? sizeof( $this->closing ) : 0;
+ }
+}
+
+/**
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class _DiffOp_Copy extends _DiffOp {
+ var $type = 'copy';
+
+ function __construct ( $orig, $closing = false ) {
+ if ( !is_array( $closing ) )
+ $closing = $orig;
+ $this->orig = $orig;
+ $this->closing = $closing;
+ }
+
+ function reverse() {
+ return new _DiffOp_Copy( $this->closing, $this->orig );
+ }
+}
+
+/**
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class _DiffOp_Delete extends _DiffOp {
+ var $type = 'delete';
+
+ function __construct ( $lines ) {
+ $this->orig = $lines;
+ $this->closing = false;
+ }
+
+ function reverse() {
+ return new _DiffOp_Add( $this->orig );
+ }
+}
+
+/**
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class _DiffOp_Add extends _DiffOp {
+ var $type = 'add';
+
+ function __construct ( $lines ) {
+ $this->closing = $lines;
+ $this->orig = false;
+ }
+
+ function reverse() {
+ return new _DiffOp_Delete( $this->closing );
+ }
+}
+
+/**
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class _DiffOp_Change extends _DiffOp {
+ var $type = 'change';
+
+ function __construct ( $orig, $closing ) {
+ $this->orig = $orig;
+ $this->closing = $closing;
+ }
+
+ function reverse() {
+ return new _DiffOp_Change( $this->closing, $this->orig );
+ }
+}
+
+/**
+ * Class used internally by Diff to actually compute the diffs.
+ *
+ * The algorithm used here is mostly lifted from the perl module
+ * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
+ * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
+ *
+ * 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
+ * diffutils-2.7, which can be found at:
+ * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
+ *
+ * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
+ * are my own.
+ *
+ * Line length limits for robustness added by Tim Starling, 2005-08-31
+ * Alternative implementation added by Guy Van den Broeck, 2008-07-30
+ *
+ * @author Geoffrey T. Dairiki, Tim Starling, Guy Van den Broeck
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class _DiffEngine {
+
+ const MAX_XREF_LENGTH = 10000;
+
+ function diff ( $from_lines, $to_lines ) {
+ wfProfileIn( __METHOD__ );
+
+ // Diff and store locally
+ $this->diff_local( $from_lines, $to_lines );
+
+ // Merge edits when possible
+ $this->_shift_boundaries( $from_lines, $this->xchanged, $this->ychanged );
+ $this->_shift_boundaries( $to_lines, $this->ychanged, $this->xchanged );
+
+ // Compute the edit operations.
+ $n_from = sizeof( $from_lines );
+ $n_to = sizeof( $to_lines );
+
+ $edits = array();
+ $xi = $yi = 0;
+ while ( $xi < $n_from || $yi < $n_to ) {
+ assert( $yi < $n_to || $this->xchanged[$xi] );
+ assert( $xi < $n_from || $this->ychanged[$yi] );
+
+ // Skip matching "snake".
+ $copy = array();
+ while ( $xi < $n_from && $yi < $n_to
+ && !$this->xchanged[$xi] && !$this->ychanged[$yi] ) {
+ $copy[] = $from_lines[$xi++];
+ ++$yi;
+ }
+ if ( $copy )
+ $edits[] = new _DiffOp_Copy( $copy );
+
+ // Find deletes & adds.
+ $delete = array();
+ while ( $xi < $n_from && $this->xchanged[$xi] )
+ $delete[] = $from_lines[$xi++];
+
+ $add = array();
+ while ( $yi < $n_to && $this->ychanged[$yi] )
+ $add[] = $to_lines[$yi++];
+
+ if ( $delete && $add )
+ $edits[] = new _DiffOp_Change( $delete, $add );
+ elseif ( $delete )
+ $edits[] = new _DiffOp_Delete( $delete );
+ elseif ( $add )
+ $edits[] = new _DiffOp_Add( $add );
+ }
+ wfProfileOut( __METHOD__ );
+ return $edits;
+ }
+
+ function diff_local ( $from_lines, $to_lines ) {
+ global $wgExternalDiffEngine;
+ wfProfileIn( __METHOD__ );
+
+ if ( $wgExternalDiffEngine == 'wikidiff3' ) {
+ // wikidiff3
+ $wikidiff3 = new WikiDiff3();
+ $wikidiff3->diff( $from_lines, $to_lines );
+ $this->xchanged = $wikidiff3->removed;
+ $this->ychanged = $wikidiff3->added;
+ unset( $wikidiff3 );
+ } else {
+ // old diff
+ $n_from = sizeof( $from_lines );
+ $n_to = sizeof( $to_lines );
+ $this->xchanged = $this->ychanged = array();
+ $this->xv = $this->yv = array();
+ $this->xind = $this->yind = array();
+ unset( $this->seq );
+ unset( $this->in_seq );
+ unset( $this->lcs );
+
+ // Skip leading common lines.
+ for ( $skip = 0; $skip < $n_from && $skip < $n_to; $skip++ ) {
+ if ( $from_lines[$skip] !== $to_lines[$skip] )
+ break;
+ $this->xchanged[$skip] = $this->ychanged[$skip] = false;
+ }
+ // Skip trailing common lines.
+ $xi = $n_from; $yi = $n_to;
+ for ( $endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++ ) {
+ if ( $from_lines[$xi] !== $to_lines[$yi] )
+ break;
+ $this->xchanged[$xi] = $this->ychanged[$yi] = false;
+ }
+
+ // Ignore lines which do not exist in both files.
+ for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) {
+ $xhash[$this->_line_hash( $from_lines[$xi] )] = 1;
+ }
+
+ for ( $yi = $skip; $yi < $n_to - $endskip; $yi++ ) {
+ $line = $to_lines[$yi];
+ if ( ( $this->ychanged[$yi] = empty( $xhash[$this->_line_hash( $line )] ) ) )
+ continue;
+ $yhash[$this->_line_hash( $line )] = 1;
+ $this->yv[] = $line;
+ $this->yind[] = $yi;
+ }
+ for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) {
+ $line = $from_lines[$xi];
+ if ( ( $this->xchanged[$xi] = empty( $yhash[$this->_line_hash( $line )] ) ) )
+ continue;
+ $this->xv[] = $line;
+ $this->xind[] = $xi;
+ }
+
+ // Find the LCS.
+ $this->_compareseq( 0, sizeof( $this->xv ), 0, sizeof( $this->yv ) );
+ }
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Returns the whole line if it's small enough, or the MD5 hash otherwise
+ */
+ function _line_hash( $line ) {
+ if ( strlen( $line ) > self::MAX_XREF_LENGTH ) {
+ return md5( $line );
+ } else {
+ return $line;
+ }
+ }
+
+ /* Divide the Largest Common Subsequence (LCS) of the sequences
+ * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
+ * sized segments.
+ *
+ * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an
+ * array of NCHUNKS+1 (X, Y) indexes giving the diving points between
+ * sub sequences. The first sub-sequence is contained in [X0, X1),
+ * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note
+ * that (X0, Y0) == (XOFF, YOFF) and
+ * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).
+ *
+ * This function assumes that the first lines of the specified portions
+ * of the two files do not match, and likewise that the last lines do not
+ * match. The caller must trim matching lines from the beginning and end
+ * of the portions it is going to specify.
+ */
+ function _diag ( $xoff, $xlim, $yoff, $ylim, $nchunks ) {
+ $flip = false;
+
+ if ( $xlim - $xoff > $ylim - $yoff ) {
+ // Things seems faster (I'm not sure I understand why)
+ // when the shortest sequence in X.
+ $flip = true;
+ list ( $xoff, $xlim, $yoff, $ylim )
+ = array( $yoff, $ylim, $xoff, $xlim );
+ }
+
+ if ( $flip )
+ for ( $i = $ylim - 1; $i >= $yoff; $i-- )
+ $ymatches[$this->xv[$i]][] = $i;
+ else
+ for ( $i = $ylim - 1; $i >= $yoff; $i-- )
+ $ymatches[$this->yv[$i]][] = $i;
+
+ $this->lcs = 0;
+ $this->seq[0] = $yoff - 1;
+ $this->in_seq = array();
+ $ymids[0] = array();
+
+ $numer = $xlim - $xoff + $nchunks - 1;
+ $x = $xoff;
+ for ( $chunk = 0; $chunk < $nchunks; $chunk++ ) {
+ if ( $chunk > 0 )
+ for ( $i = 0; $i <= $this->lcs; $i++ )
+ $ymids[$i][$chunk -1] = $this->seq[$i];
+
+ $x1 = $xoff + (int)( ( $numer + ( $xlim -$xoff ) * $chunk ) / $nchunks );
+ for ( ; $x < $x1; $x++ ) {
+ $line = $flip ? $this->yv[$x] : $this->xv[$x];
+ if ( empty( $ymatches[$line] ) ) {
+ continue;
+ }
+ $matches = $ymatches[$line];
+ reset( $matches );
+ while ( list( , $y ) = each( $matches ) ) {
+ if ( empty( $this->in_seq[$y] ) ) {
+ $k = $this->_lcs_pos( $y );
+ assert( $k > 0 );
+ $ymids[$k] = $ymids[$k -1];
+ break;
+ }
+ }
+ while ( list ( , $y ) = each( $matches ) ) {
+ if ( $y > $this->seq[$k -1] ) {
+ assert( $y < $this->seq[$k] );
+ // Optimization: this is a common case:
+ // next match is just replacing previous match.
+ $this->in_seq[$this->seq[$k]] = false;
+ $this->seq[$k] = $y;
+ $this->in_seq[$y] = 1;
+ } else if ( empty( $this->in_seq[$y] ) ) {
+ $k = $this->_lcs_pos( $y );
+ assert( $k > 0 );
+ $ymids[$k] = $ymids[$k -1];
+ }
+ }
+ }
+ }
+
+ $seps[] = $flip ? array( $yoff, $xoff ) : array( $xoff, $yoff );
+ $ymid = $ymids[$this->lcs];
+ for ( $n = 0; $n < $nchunks - 1; $n++ ) {
+ $x1 = $xoff + (int)( ( $numer + ( $xlim - $xoff ) * $n ) / $nchunks );
+ $y1 = $ymid[$n] + 1;
+ $seps[] = $flip ? array( $y1, $x1 ) : array( $x1, $y1 );
+ }
+ $seps[] = $flip ? array( $ylim, $xlim ) : array( $xlim, $ylim );
+
+ return array( $this->lcs, $seps );
+ }
+
+ function _lcs_pos ( $ypos ) {
+ $end = $this->lcs;
+ if ( $end == 0 || $ypos > $this->seq[$end] ) {
+ $this->seq[++$this->lcs] = $ypos;
+ $this->in_seq[$ypos] = 1;
+ return $this->lcs;
+ }
+
+ $beg = 1;
+ while ( $beg < $end ) {
+ $mid = (int)( ( $beg + $end ) / 2 );
+ if ( $ypos > $this->seq[$mid] )
+ $beg = $mid + 1;
+ else
+ $end = $mid;
+ }
+
+ assert( $ypos != $this->seq[$end] );
+
+ $this->in_seq[$this->seq[$end]] = false;
+ $this->seq[$end] = $ypos;
+ $this->in_seq[$ypos] = 1;
+ return $end;
+ }
+
+ /* Find LCS of two sequences.
+ *
+ * The results are recorded in the vectors $this->{x,y}changed[], by
+ * storing a 1 in the element for each line that is an insertion
+ * or deletion (ie. is not in the LCS).
+ *
+ * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
+ *
+ * Note that XLIM, YLIM are exclusive bounds.
+ * All line numbers are origin-0 and discarded lines are not counted.
+ */
+ function _compareseq ( $xoff, $xlim, $yoff, $ylim ) {
+ // Slide down the bottom initial diagonal.
+ while ( $xoff < $xlim && $yoff < $ylim
+ && $this->xv[$xoff] == $this->yv[$yoff] ) {
+ ++$xoff;
+ ++$yoff;
+ }
+
+ // Slide up the top initial diagonal.
+ while ( $xlim > $xoff && $ylim > $yoff
+ && $this->xv[$xlim - 1] == $this->yv[$ylim - 1] ) {
+ --$xlim;
+ --$ylim;
+ }
+
+ if ( $xoff == $xlim || $yoff == $ylim )
+ $lcs = 0;
+ else {
+ // This is ad hoc but seems to work well.
+ // $nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
+ // $nchunks = max(2,min(8,(int)$nchunks));
+ $nchunks = min( 7, $xlim - $xoff, $ylim - $yoff ) + 1;
+ list ( $lcs, $seps )
+ = $this->_diag( $xoff, $xlim, $yoff, $ylim, $nchunks );
+ }
+
+ if ( $lcs == 0 ) {
+ // X and Y sequences have no common subsequence:
+ // mark all changed.
+ while ( $yoff < $ylim )
+ $this->ychanged[$this->yind[$yoff++]] = 1;
+ while ( $xoff < $xlim )
+ $this->xchanged[$this->xind[$xoff++]] = 1;
+ } else {
+ // Use the partitions to split this problem into subproblems.
+ reset( $seps );
+ $pt1 = $seps[0];
+ while ( $pt2 = next( $seps ) ) {
+ $this->_compareseq ( $pt1[0], $pt2[0], $pt1[1], $pt2[1] );
+ $pt1 = $pt2;
+ }
+ }
+ }
+
+ /* Adjust inserts/deletes of identical lines to join changes
+ * as much as possible.
+ *
+ * We do something when a run of changed lines include a
+ * line at one end and has an excluded, identical line at the other.
+ * We are free to choose which identical line is included.
+ * `compareseq' usually chooses the one at the beginning,
+ * but usually it is cleaner to consider the following identical line
+ * to be the "change".
+ *
+ * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
+ */
+ function _shift_boundaries ( $lines, &$changed, $other_changed ) {
+ wfProfileIn( __METHOD__ );
+ $i = 0;
+ $j = 0;
+
+ assert( 'sizeof($lines) == sizeof($changed)' );
+ $len = sizeof( $lines );
+ $other_len = sizeof( $other_changed );
+
+ while ( 1 ) {
+ /*
+ * Scan forwards to find beginning of another run of changes.
+ * Also keep track of the corresponding point in the other file.
+ *
+ * Throughout this code, $i and $j are adjusted together so that
+ * the first $i elements of $changed and the first $j elements
+ * of $other_changed both contain the same number of zeros
+ * (unchanged lines).
+ * Furthermore, $j is always kept so that $j == $other_len or
+ * $other_changed[$j] == false.
+ */
+ while ( $j < $other_len && $other_changed[$j] )
+ $j++;
+
+ while ( $i < $len && ! $changed[$i] ) {
+ assert( '$j < $other_len && ! $other_changed[$j]' );
+ $i++; $j++;
+ while ( $j < $other_len && $other_changed[$j] )
+ $j++;
+ }
+
+ if ( $i == $len )
+ break;
+
+ $start = $i;
+
+ // Find the end of this run of changes.
+ while ( ++$i < $len && $changed[$i] )
+ continue;
+
+ do {
+ /*
+ * Record the length of this run of changes, so that
+ * we can later determine whether the run has grown.
+ */
+ $runlength = $i - $start;
+
+ /*
+ * Move the changed region back, so long as the
+ * previous unchanged line matches the last changed one.
+ * This merges with previous changed regions.
+ */
+ while ( $start > 0 && $lines[$start - 1] == $lines[$i - 1] ) {
+ $changed[--$start] = 1;
+ $changed[--$i] = false;
+ while ( $start > 0 && $changed[$start - 1] )
+ $start--;
+ assert( '$j > 0' );
+ while ( $other_changed[--$j] )
+ continue;
+ assert( '$j >= 0 && !$other_changed[$j]' );
+ }
+
+ /*
+ * Set CORRESPONDING to the end of the changed run, at the last
+ * point where it corresponds to a changed run in the other file.
+ * CORRESPONDING == LEN means no such point has been found.
+ */
+ $corresponding = $j < $other_len ? $i : $len;
+
+ /*
+ * Move the changed region forward, so long as the
+ * first changed line matches the following unchanged one.
+ * This merges with following changed regions.
+ * Do this second, so that if there are no merges,
+ * the changed region is moved forward as far as possible.
+ */
+ while ( $i < $len && $lines[$start] == $lines[$i] ) {
+ $changed[$start++] = false;
+ $changed[$i++] = 1;
+ while ( $i < $len && $changed[$i] )
+ $i++;
+
+ assert( '$j < $other_len && ! $other_changed[$j]' );
+ $j++;
+ if ( $j < $other_len && $other_changed[$j] ) {
+ $corresponding = $i;
+ while ( $j < $other_len && $other_changed[$j] )
+ $j++;
+ }
+ }
+ } while ( $runlength != $i - $start );
+
+ /*
+ * If possible, move the fully-merged run of changes
+ * back to a corresponding run in the other file.
+ */
+ while ( $corresponding < $i ) {
+ $changed[--$start] = 1;
+ $changed[--$i] = 0;
+ assert( '$j > 0' );
+ while ( $other_changed[--$j] )
+ continue;
+ assert( '$j >= 0 && !$other_changed[$j]' );
+ }
+ }
+ wfProfileOut( __METHOD__ );
+ }
+}
+
+/**
+ * Class representing a 'diff' between two sequences of strings.
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class Diff
+{
+ var $edits;
+
+ /**
+ * Constructor.
+ * Computes diff between sequences of strings.
+ *
+ * @param $from_lines array An array of strings.
+ * (Typically these are lines from a file.)
+ * @param $to_lines array An array of strings.
+ */
+ function __construct( $from_lines, $to_lines ) {
+ $eng = new _DiffEngine;
+ $this->edits = $eng->diff( $from_lines, $to_lines );
+ // $this->_check($from_lines, $to_lines);
+ }
+
+ /**
+ * Compute reversed Diff.
+ *
+ * SYNOPSIS:
+ *
+ * $diff = new Diff($lines1, $lines2);
+ * $rev = $diff->reverse();
+ * @return object A Diff object representing the inverse of the
+ * original diff.
+ */
+ function reverse () {
+ $rev = $this;
+ $rev->edits = array();
+ foreach ( $this->edits as $edit ) {
+ $rev->edits[] = $edit->reverse();
+ }
+ return $rev;
+ }
+
+ /**
+ * Check for empty diff.
+ *
+ * @return bool True iff two sequences were identical.
+ */
+ function isEmpty () {
+ foreach ( $this->edits as $edit ) {
+ if ( $edit->type != 'copy' )
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Compute the length of the Longest Common Subsequence (LCS).
+ *
+ * This is mostly for diagnostic purposed.
+ *
+ * @return int The length of the LCS.
+ */
+ function lcs () {
+ $lcs = 0;
+ foreach ( $this->edits as $edit ) {
+ if ( $edit->type == 'copy' )
+ $lcs += sizeof( $edit->orig );
+ }
+ return $lcs;
+ }
+
+ /**
+ * Get the original set of lines.
+ *
+ * This reconstructs the $from_lines parameter passed to the
+ * constructor.
+ *
+ * @return array The original sequence of strings.
+ */
+ function orig() {
+ $lines = array();
+
+ foreach ( $this->edits as $edit ) {
+ if ( $edit->orig )
+ array_splice( $lines, sizeof( $lines ), 0, $edit->orig );
+ }
+ return $lines;
+ }
+
+ /**
+ * Get the closing set of lines.
+ *
+ * This reconstructs the $to_lines parameter passed to the
+ * constructor.
+ *
+ * @return array The sequence of strings.
+ */
+ function closing() {
+ $lines = array();
+
+ foreach ( $this->edits as $edit ) {
+ if ( $edit->closing )
+ array_splice( $lines, sizeof( $lines ), 0, $edit->closing );
+ }
+ return $lines;
+ }
+
+ /**
+ * Check a Diff for validity.
+ *
+ * This is here only for debugging purposes.
+ */
+ function _check ( $from_lines, $to_lines ) {
+ wfProfileIn( __METHOD__ );
+ if ( serialize( $from_lines ) != serialize( $this->orig() ) )
+ trigger_error( "Reconstructed original doesn't match", E_USER_ERROR );
+ if ( serialize( $to_lines ) != serialize( $this->closing() ) )
+ trigger_error( "Reconstructed closing doesn't match", E_USER_ERROR );
+
+ $rev = $this->reverse();
+ if ( serialize( $to_lines ) != serialize( $rev->orig() ) )
+ trigger_error( "Reversed original doesn't match", E_USER_ERROR );
+ if ( serialize( $from_lines ) != serialize( $rev->closing() ) )
+ trigger_error( "Reversed closing doesn't match", E_USER_ERROR );
+
+
+ $prevtype = 'none';
+ foreach ( $this->edits as $edit ) {
+ if ( $prevtype == $edit->type )
+ trigger_error( "Edit sequence is non-optimal", E_USER_ERROR );
+ $prevtype = $edit->type;
+ }
+
+ $lcs = $this->lcs();
+ trigger_error( 'Diff okay: LCS = ' . $lcs, E_USER_NOTICE );
+ wfProfileOut( __METHOD__ );
+ }
+}
+
+/**
+ * @todo document, bad name.
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class MappedDiff extends Diff
+{
+ /**
+ * Constructor.
+ *
+ * Computes diff between sequences of strings.
+ *
+ * This can be used to compute things like
+ * case-insensitve diffs, or diffs which ignore
+ * changes in white-space.
+ *
+ * @param $from_lines array An array of strings.
+ * (Typically these are lines from a file.)
+ *
+ * @param $to_lines array An array of strings.
+ *
+ * @param $mapped_from_lines array This array should
+ * have the same size number of elements as $from_lines.
+ * The elements in $mapped_from_lines and
+ * $mapped_to_lines are what is actually compared
+ * when computing the diff.
+ *
+ * @param $mapped_to_lines array This array should
+ * have the same number of elements as $to_lines.
+ */
+ function __construct( $from_lines, $to_lines,
+ $mapped_from_lines, $mapped_to_lines ) {
+ wfProfileIn( __METHOD__ );
+
+ assert( sizeof( $from_lines ) == sizeof( $mapped_from_lines ) );
+ assert( sizeof( $to_lines ) == sizeof( $mapped_to_lines ) );
+
+ parent::__construct( $mapped_from_lines, $mapped_to_lines );
+
+ $xi = $yi = 0;
+ for ( $i = 0; $i < sizeof( $this->edits ); $i++ ) {
+ $orig = &$this->edits[$i]->orig;
+ if ( is_array( $orig ) ) {
+ $orig = array_slice( $from_lines, $xi, sizeof( $orig ) );
+ $xi += sizeof( $orig );
+ }
+
+ $closing = &$this->edits[$i]->closing;
+ if ( is_array( $closing ) ) {
+ $closing = array_slice( $to_lines, $yi, sizeof( $closing ) );
+ $yi += sizeof( $closing );
+ }
+ }
+ wfProfileOut( __METHOD__ );
+ }
+}
+
+/**
+ * A class to format Diffs
+ *
+ * This class formats the diff in classic diff format.
+ * It is intended that this class be customized via inheritance,
+ * to obtain fancier outputs.
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class DiffFormatter {
+ /**
+ * Number of leading context "lines" to preserve.
+ *
+ * This should be left at zero for this class, but subclasses
+ * may want to set this to other values.
+ */
+ var $leading_context_lines = 0;
+
+ /**
+ * Number of trailing context "lines" to preserve.
+ *
+ * This should be left at zero for this class, but subclasses
+ * may want to set this to other values.
+ */
+ var $trailing_context_lines = 0;
+
+ /**
+ * Format a diff.
+ *
+ * @param $diff object A Diff object.
+ * @return string The formatted output.
+ */
+ function format( $diff ) {
+ wfProfileIn( __METHOD__ );
+
+ $xi = $yi = 1;
+ $block = false;
+ $context = array();
+
+ $nlead = $this->leading_context_lines;
+ $ntrail = $this->trailing_context_lines;
+
+ $this->_start_diff();
+
+ foreach ( $diff->edits as $edit ) {
+ if ( $edit->type == 'copy' ) {
+ if ( is_array( $block ) ) {
+ if ( sizeof( $edit->orig ) <= $nlead + $ntrail ) {
+ $block[] = $edit;
+ }
+ else {
+ if ( $ntrail ) {
+ $context = array_slice( $edit->orig, 0, $ntrail );
+ $block[] = new _DiffOp_Copy( $context );
+ }
+ $this->_block( $x0, $ntrail + $xi - $x0,
+ $y0, $ntrail + $yi - $y0,
+ $block );
+ $block = false;
+ }
+ }
+ $context = $edit->orig;
+ }
+ else {
+ if ( ! is_array( $block ) ) {
+ $context = array_slice( $context, sizeof( $context ) - $nlead );
+ $x0 = $xi - sizeof( $context );
+ $y0 = $yi - sizeof( $context );
+ $block = array();
+ if ( $context )
+ $block[] = new _DiffOp_Copy( $context );
+ }
+ $block[] = $edit;
+ }
+
+ if ( $edit->orig )
+ $xi += sizeof( $edit->orig );
+ if ( $edit->closing )
+ $yi += sizeof( $edit->closing );
+ }
+
+ if ( is_array( $block ) )
+ $this->_block( $x0, $xi - $x0,
+ $y0, $yi - $y0,
+ $block );
+
+ $end = $this->_end_diff();
+ wfProfileOut( __METHOD__ );
+ return $end;
+ }
+
+ function _block( $xbeg, $xlen, $ybeg, $ylen, &$edits ) {
+ wfProfileIn( __METHOD__ );
+ $this->_start_block( $this->_block_header( $xbeg, $xlen, $ybeg, $ylen ) );
+ foreach ( $edits as $edit ) {
+ if ( $edit->type == 'copy' )
+ $this->_context( $edit->orig );
+ elseif ( $edit->type == 'add' )
+ $this->_added( $edit->closing );
+ elseif ( $edit->type == 'delete' )
+ $this->_deleted( $edit->orig );
+ elseif ( $edit->type == 'change' )
+ $this->_changed( $edit->orig, $edit->closing );
+ else
+ trigger_error( 'Unknown edit type', E_USER_ERROR );
+ }
+ $this->_end_block();
+ wfProfileOut( __METHOD__ );
+ }
+
+ function _start_diff() {
+ ob_start();
+ }
+
+ function _end_diff() {
+ $val = ob_get_contents();
+ ob_end_clean();
+ return $val;
+ }
+
+ function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
+ if ( $xlen > 1 )
+ $xbeg .= "," . ( $xbeg + $xlen - 1 );
+ if ( $ylen > 1 )
+ $ybeg .= "," . ( $ybeg + $ylen - 1 );
+
+ return $xbeg . ( $xlen ? ( $ylen ? 'c' : 'd' ) : 'a' ) . $ybeg;
+ }
+
+ function _start_block( $header ) {
+ echo $header . "\n";
+ }
+
+ function _end_block() {
+ }
+
+ function _lines( $lines, $prefix = ' ' ) {
+ foreach ( $lines as $line )
+ echo "$prefix $line\n";
+ }
+
+ function _context( $lines ) {
+ $this->_lines( $lines );
+ }
+
+ function _added( $lines ) {
+ $this->_lines( $lines, '>' );
+ }
+ function _deleted( $lines ) {
+ $this->_lines( $lines, '<' );
+ }
+
+ function _changed( $orig, $closing ) {
+ $this->_deleted( $orig );
+ echo "---\n";
+ $this->_added( $closing );
+ }
+}
+
+/**
+ * A formatter that outputs unified diffs
+ * @ingroup DifferenceEngine
+ */
+
+class UnifiedDiffFormatter extends DiffFormatter {
+ var $leading_context_lines = 2;
+ var $trailing_context_lines = 2;
+
+ function _added( $lines ) {
+ $this->_lines( $lines, '+' );
+ }
+ function _deleted( $lines ) {
+ $this->_lines( $lines, '-' );
+ }
+ function _changed( $orig, $closing ) {
+ $this->_deleted( $orig );
+ $this->_added( $closing );
+ }
+ function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
+ return "@@ -$xbeg,$xlen +$ybeg,$ylen @@";
+ }
+}
+
+/**
+ * A pseudo-formatter that just passes along the Diff::$edits array
+ * @ingroup DifferenceEngine
+ */
+class ArrayDiffFormatter extends DiffFormatter {
+ function format( $diff ) {
+ $oldline = 1;
+ $newline = 1;
+ $retval = array();
+ foreach ( $diff->edits as $edit )
+ switch( $edit->type ) {
+ case 'add':
+ foreach ( $edit->closing as $l ) {
+ $retval[] = array(
+ 'action' => 'add',
+ 'new' => $l,
+ 'newline' => $newline++
+ );
+ }
+ break;
+ case 'delete':
+ foreach ( $edit->orig as $l ) {
+ $retval[] = array(
+ 'action' => 'delete',
+ 'old' => $l,
+ 'oldline' => $oldline++,
+ );
+ }
+ break;
+ case 'change':
+ foreach ( $edit->orig as $i => $l ) {
+ $retval[] = array(
+ 'action' => 'change',
+ 'old' => $l,
+ 'new' => @$edit->closing[$i],
+ 'oldline' => $oldline++,
+ 'newline' => $newline++,
+ );
+ }
+ break;
+ case 'copy':
+ $oldline += count( $edit->orig );
+ $newline += count( $edit->orig );
+ }
+ return $retval;
+ }
+}
+
+/**
+ * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
+ *
+ */
+
+define( 'NBSP', '&#160;' ); // iso-8859-x non-breaking space.
+
+/**
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class _HWLDF_WordAccumulator {
+ function __construct () {
+ $this->_lines = array();
+ $this->_line = '';
+ $this->_group = '';
+ $this->_tag = '';
+ }
+
+ function _flushGroup ( $new_tag ) {
+ if ( $this->_group !== '' ) {
+ if ( $this->_tag == 'ins' )
+ $this->_line .= '<ins class="diffchange diffchange-inline">' .
+ htmlspecialchars ( $this->_group ) . '</ins>';
+ elseif ( $this->_tag == 'del' )
+ $this->_line .= '<del class="diffchange diffchange-inline">' .
+ htmlspecialchars ( $this->_group ) . '</del>';
+ else
+ $this->_line .= htmlspecialchars ( $this->_group );
+ }
+ $this->_group = '';
+ $this->_tag = $new_tag;
+ }
+
+ function _flushLine ( $new_tag ) {
+ $this->_flushGroup( $new_tag );
+ if ( $this->_line != '' )
+ array_push ( $this->_lines, $this->_line );
+ else
+ # make empty lines visible by inserting an NBSP
+ array_push ( $this->_lines, NBSP );
+ $this->_line = '';
+ }
+
+ function addWords ( $words, $tag = '' ) {
+ if ( $tag != $this->_tag )
+ $this->_flushGroup( $tag );
+
+ foreach ( $words as $word ) {
+ // new-line should only come as first char of word.
+ if ( $word == '' )
+ continue;
+ if ( $word[0] == "\n" ) {
+ $this->_flushLine( $tag );
+ $word = substr( $word, 1 );
+ }
+ assert( !strstr( $word, "\n" ) );
+ $this->_group .= $word;
+ }
+ }
+
+ function getLines() {
+ $this->_flushLine( '~done' );
+ return $this->_lines;
+ }
+}
+
+/**
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class WordLevelDiff extends MappedDiff {
+ const MAX_LINE_LENGTH = 10000;
+
+ 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__ );
+ }
+
+ function _split( $lines ) {
+ wfProfileIn( __METHOD__ );
+
+ $words = array();
+ $stripped = array();
+ $first = true;
+ foreach ( $lines as $line ) {
+ # If the line is too long, just pretend the entire line is one big word
+ # This prevents resource exhaustion problems
+ if ( $first ) {
+ $first = false;
+ } else {
+ $words[] = "\n";
+ $stripped[] = "\n";
+ }
+ if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
+ $words[] = $line;
+ $stripped[] = $line;
+ } else {
+ $m = array();
+ if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
+ $line, $m ) )
+ {
+ $words = array_merge( $words, $m[0] );
+ $stripped = array_merge( $stripped, $m[1] );
+ }
+ }
+ }
+ wfProfileOut( __METHOD__ );
+ return array( $words, $stripped );
+ }
+
+ function orig () {
+ wfProfileIn( __METHOD__ );
+ $orig = new _HWLDF_WordAccumulator;
+
+ foreach ( $this->edits as $edit ) {
+ if ( $edit->type == 'copy' )
+ $orig->addWords( $edit->orig );
+ elseif ( $edit->orig )
+ $orig->addWords( $edit->orig, 'del' );
+ }
+ $lines = $orig->getLines();
+ wfProfileOut( __METHOD__ );
+ return $lines;
+ }
+
+ function closing () {
+ wfProfileIn( __METHOD__ );
+ $closing = new _HWLDF_WordAccumulator;
+
+ foreach ( $this->edits as $edit ) {
+ if ( $edit->type == 'copy' )
+ $closing->addWords( $edit->closing );
+ elseif ( $edit->closing )
+ $closing->addWords( $edit->closing, 'ins' );
+ }
+ $lines = $closing->getLines();
+ wfProfileOut( __METHOD__ );
+ return $lines;
+ }
+}
+
+/**
+ * Wikipedia Table style diff formatter.
+ * @todo document
+ * @private
+ * @ingroup DifferenceEngine
+ */
+class TableDiffFormatter extends DiffFormatter {
+ function __construct() {
+ $this->leading_context_lines = 2;
+ $this->trailing_context_lines = 2;
+ }
+
+ public static function escapeWhiteSpace( $msg ) {
+ $msg = preg_replace( '/^ /m', '&#160; ', $msg );
+ $msg = preg_replace( '/ $/m', ' &#160;', $msg );
+ $msg = preg_replace( '/ /', '&#160; ', $msg );
+ return $msg;
+ }
+
+ function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
+ $r = '<tr><td colspan="2" class="diff-lineno"><!--LINE ' . $xbeg . "--></td>\n" .
+ '<td colspan="2" class="diff-lineno"><!--LINE ' . $ybeg . "--></td></tr>\n";
+ return $r;
+ }
+
+ function _start_block( $header ) {
+ echo $header;
+ }
+
+ function _end_block() {
+ }
+
+ function _lines( $lines, $prefix = ' ', $color = 'white' ) {
+ }
+
+ # HTML-escape parameter before calling this
+ function addedLine( $line ) {
+ return $this->wrapLine( '+', 'diff-addedline', $line );
+ }
+
+ # HTML-escape parameter before calling this
+ function deletedLine( $line ) {
+ return $this->wrapLine( '&minus;', 'diff-deletedline', $line );
+ }
+
+ # HTML-escape parameter before calling this
+ function contextLine( $line ) {
+ return $this->wrapLine( '&#160;', 'diff-context', $line );
+ }
+
+ private function wrapLine( $marker, $class, $line ) {
+ if ( $line !== '' ) {
+ // The <div> wrapper is needed for 'overflow: auto' style to scroll properly
+ $line = Xml::tags( 'div', null, $this->escapeWhiteSpace( $line ) );
+ }
+ return "<td class='diff-marker'>$marker</td><td class='$class'>$line</td>";
+ }
+
+ function emptyLine() {
+ return '<td colspan="2">&#160;</td>';
+ }
+
+ function _added( $lines ) {
+ foreach ( $lines as $line ) {
+ echo '<tr>' . $this->emptyLine() .
+ $this->addedLine( '<ins class="diffchange">' .
+ htmlspecialchars ( $line ) . '</ins>' ) . "</tr>\n";
+ }
+ }
+
+ function _deleted( $lines ) {
+ foreach ( $lines as $line ) {
+ echo '<tr>' . $this->deletedLine( '<del class="diffchange">' .
+ htmlspecialchars ( $line ) . '</del>' ) .
+ $this->emptyLine() . "</tr>\n";
+ }
+ }
+
+ function _context( $lines ) {
+ foreach ( $lines as $line ) {
+ echo '<tr>' .
+ $this->contextLine( htmlspecialchars ( $line ) ) .
+ $this->contextLine( htmlspecialchars ( $line ) ) . "</tr>\n";
+ }
+ }
+
+ function _changed( $orig, $closing ) {
+ wfProfileIn( __METHOD__ );
+
+ $diff = new WordLevelDiff( $orig, $closing );
+ $del = $diff->orig();
+ $add = $diff->closing();
+
+ # Notice that WordLevelDiff returns HTML-escaped output.
+ # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
+
+ while ( $line = array_shift( $del ) ) {
+ $aline = array_shift( $add );
+ echo '<tr>' . $this->deletedLine( $line ) .
+ $this->addedLine( $aline ) . "</tr>\n";
+ }
+ foreach ( $add as $line ) { # If any leftovers
+ echo '<tr>' . $this->emptyLine() .
+ $this->addedLine( $line ) . "</tr>\n";
+ }
+ wfProfileOut( __METHOD__ );
+ }
+}
diff --git a/includes/diff/Diff.php b/includes/diff/WikiDiff3.php
index 538c2d83..8def296d 100644
--- a/includes/diff/Diff.php
+++ b/includes/diff/WikiDiff3.php
@@ -1,5 +1,8 @@
<?php
-/* Copyright (C) 2008 Guy Van den Broeck <guy@guyvdb.eu>
+/**
+ * New version of the difference engine
+ *
+ * Copyright © 2008 Guy Van den Broeck <guy@guyvdb.eu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,8 +16,11 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- * or see http://www.gnu.org/
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
*/
/**
@@ -32,7 +38,7 @@
*/
class WikiDiff3 {
- //Input variables
+ // Input variables
private $from;
private $to;
private $m;
@@ -41,117 +47,117 @@ class WikiDiff3 {
private $tooLong;
private $powLimit;
- //State variables
+ // State variables
private $maxDifferences;
private $lcsLengthCorrectedForHeuristic = false;
- //Output variables
+ // Output variables
public $length;
public $removed;
public $added;
public $heuristicUsed;
- function __construct($tooLong = 2000000, $powLimit = 1.45){
+ function __construct( $tooLong = 2000000, $powLimit = 1.45 ) {
$this->tooLong = $tooLong;
$this->powLimit = $powLimit;
}
- public function diff(/*array*/ $from, /*array*/ $to){
- //remember initial lengths
- $m = sizeof($from);
- $n = count($to);
+ public function diff( /*array*/ $from, /*array*/ $to ) {
+ // remember initial lengths
+ $m = sizeof( $from );
+ $n = count( $to );
$this->heuristicUsed = false;
- //output
- $removed = $m > 0 ? array_fill(0, $m, true) : array();
- $added = $n > 0 ? array_fill(0, $n, true) : array();
+ // output
+ $removed = $m > 0 ? array_fill( 0, $m, true ) : array();
+ $added = $n > 0 ? array_fill( 0, $n, true ) : array();
- //reduce the complexity for the next step (intentionally done twice)
- //remove common tokens at the start
+ // reduce the complexity for the next step (intentionally done twice)
+ // remove common tokens at the start
$i = 0;
- while($i < $m && $i < $n && $from[$i] === $to[$i]) {
+ while ( $i < $m && $i < $n && $from[$i] === $to[$i] ) {
$removed[$i] = $added[$i] = false;
- unset($from[$i], $to[$i]);
+ unset( $from[$i], $to[$i] );
++$i;
}
- //remove common tokens at the end
+ // remove common tokens at the end
$j = 1;
- while($i + $j <= $m && $i + $j <= $n && $from[$m - $j] === $to[$n - $j]) {
+ while ( $i + $j <= $m && $i + $j <= $n && $from[$m - $j] === $to[$n - $j] ) {
$removed[$m - $j] = $added[$n - $j] = false;
- unset($from[$m - $j], $to[$n - $j]);
+ unset( $from[$m - $j], $to[$n - $j] );
++$j;
}
$this->from = $newFromIndex = $this->to = $newToIndex = array();
- //remove tokens not in both sequences
+ // remove tokens not in both sequences
$shared = array();
- foreach( $from as $key ) {
+ foreach ( $from as $key ) {
$shared[$key] = false;
}
- foreach($to as $index => &$el) {
- if(array_key_exists($el, $shared)) {
- //keep it
+ foreach ( $to as $index => &$el ) {
+ if ( array_key_exists( $el, $shared ) ) {
+ // keep it
$this->to[] = $el;
$shared[$el] = true;
$newToIndex[] = $index;
}
}
- foreach($from as $index => &$el) {
- if($shared[$el]) {
- //keep it
+ foreach ( $from as $index => &$el ) {
+ if ( $shared[$el] ) {
+ // keep it
$this->from[] = $el;
$newFromIndex[] = $index;
}
}
- unset($shared, $from, $to);
+ unset( $shared, $from, $to );
- $this->m = count($this->from);
- $this->n = count($this->to);
+ $this->m = count( $this->from );
+ $this->n = count( $this->to );
- $this->removed = $this->m > 0 ? array_fill(0, $this->m, true) : array();
- $this->added = $this->n > 0 ? array_fill(0, $this->n, true) : array();
+ $this->removed = $this->m > 0 ? array_fill( 0, $this->m, true ) : array();
+ $this->added = $this->n > 0 ? array_fill( 0, $this->n, true ) : array();
- if ($this->m == 0 || $this->n == 0) {
+ if ( $this->m == 0 || $this->n == 0 ) {
$this->length = 0;
} else {
- $this->maxDifferences = ceil(($this->m + $this->n) / 2.0);
- if ($this->m * $this->n > $this->tooLong) {
+ $this->maxDifferences = ceil( ( $this->m + $this->n ) / 2.0 );
+ if ( $this->m * $this->n > $this->tooLong ) {
// limit complexity to D^POW_LIMIT for long sequences
- $this->maxDifferences = floor(pow($this->maxDifferences, $this->powLimit - 1.0));
- wfDebug("Limiting max number of differences to $this->maxDifferences\n");
+ $this->maxDifferences = floor( pow( $this->maxDifferences, $this->powLimit - 1.0 ) );
+ wfDebug( "Limiting max number of differences to $this->maxDifferences\n" );
}
/*
* The common prefixes and suffixes are always part of some LCS, include
* them now to reduce our search space
*/
- $max = min($this->m, $this->n);
- for ($forwardBound = 0; $forwardBound < $max
+ $max = min( $this->m, $this->n );
+ for ( $forwardBound = 0; $forwardBound < $max
&& $this->from[$forwardBound] === $this->to[$forwardBound];
- ++$forwardBound) {
+ ++$forwardBound ) {
$this->removed[$forwardBound] = $this->added[$forwardBound] = false;
}
$backBoundL1 = $this->m - 1;
$backBoundL2 = $this->n - 1;
- while ($backBoundL1 >= $forwardBound && $backBoundL2 >= $forwardBound
- && $this->from[$backBoundL1] === $this->to[$backBoundL2]) {
+ while ( $backBoundL1 >= $forwardBound && $backBoundL2 >= $forwardBound
+ && $this->from[$backBoundL1] === $this->to[$backBoundL2] ) {
$this->removed[$backBoundL1--] = $this->added[$backBoundL2--] = false;
}
- $temp = array_fill(0, $this->m + $this->n + 1, 0);
- $V = array($temp, $temp);
- $snake = array(0, 0, 0);
+ $temp = array_fill( 0, $this->m + $this->n + 1, 0 );
+ $V = array( $temp, $temp );
+ $snake = array( 0, 0, 0 );
$this->length = $forwardBound + $this->m - $backBoundL1 - 1
- + $this->lcs_rec($forwardBound, $backBoundL1,
- $forwardBound, $backBoundL2, $V, $snake);
+ + $this->lcs_rec( $forwardBound, $backBoundL1,
+ $forwardBound, $backBoundL2, $V, $snake );
}
$this->m = $m;
@@ -159,13 +165,13 @@ class WikiDiff3 {
$this->length += $i + $j - 1;
- foreach($this->removed as $key => &$removed_elem) {
- if(!$removed_elem) {
+ foreach ( $this->removed as $key => &$removed_elem ) {
+ if ( !$removed_elem ) {
$removed[$newFromIndex[$key]] = false;
}
}
- foreach($this->added as $key => &$added_elem) {
- if(!$added_elem) {
+ foreach ( $this->added as $key => &$added_elem ) {
+ if ( !$added_elem ) {
$added[$newToIndex[$key]] = false;
}
}
@@ -173,48 +179,48 @@ class WikiDiff3 {
$this->added = $added;
}
- function diff_range($from_lines, $to_lines) {
+ function diff_range( $from_lines, $to_lines ) {
// Diff and store locally
- $this->diff($from_lines, $to_lines);
- unset($from_lines, $to_lines);
+ $this->diff( $from_lines, $to_lines );
+ unset( $from_lines, $to_lines );
$ranges = array();
$xi = $yi = 0;
- while ($xi < $this->m || $yi < $this->n) {
+ while ( $xi < $this->m || $yi < $this->n ) {
// Matching "snake".
- while ($xi < $this->m && $yi < $this->n
+ while ( $xi < $this->m && $yi < $this->n
&& !$this->removed[$xi]
- && !$this->added[$yi]) {
+ && !$this->added[$yi] ) {
++$xi;
++$yi;
}
// Find deletes & adds.
$xstart = $xi;
- while ($xi < $this->m && $this->removed[$xi]) {
+ while ( $xi < $this->m && $this->removed[$xi] ) {
++$xi;
}
$ystart = $yi;
- while ($yi < $this->n && $this->added[$yi]) {
+ while ( $yi < $this->n && $this->added[$yi] ) {
++$yi;
}
- if ($xi > $xstart || $yi > $ystart) {
- $ranges[] = new RangeDifference($xstart, $xi,
- $ystart, $yi);
+ if ( $xi > $xstart || $yi > $ystart ) {
+ $ranges[] = new RangeDifference( $xstart, $xi,
+ $ystart, $yi );
}
}
return $ranges;
}
- private function lcs_rec($bottoml1, $topl1, $bottoml2, $topl2, &$V, &$snake) {
+ private function lcs_rec( $bottoml1, $topl1, $bottoml2, $topl2, &$V, &$snake ) {
// check that both sequences are non-empty
- if ($bottoml1 > $topl1 || $bottoml2 > $topl2) {
+ if ( $bottoml1 > $topl1 || $bottoml2 > $topl2 ) {
return 0;
}
- $d = $this->find_middle_snake($bottoml1, $topl1, $bottoml2,
- $topl2, $V, $snake);
+ $d = $this->find_middle_snake( $bottoml1, $topl1, $bottoml2,
+ $topl2, $V, $snake );
// need to store these so we don't lose them when they're
// overwritten by the recursion
@@ -223,24 +229,24 @@ class WikiDiff3 {
$starty = $snake[1];
// the middle snake is part of the LCS, store it
- for ($i = 0; $i < $len; ++$i) {
+ for ( $i = 0; $i < $len; ++$i ) {
$this->removed[$startx + $i] = $this->added[$starty + $i] = false;
}
- if ($d > 1) {
+ if ( $d > 1 ) {
return $len
- + $this->lcs_rec($bottoml1, $startx - 1, $bottoml2,
- $starty - 1, $V, $snake)
- + $this->lcs_rec($startx + $len, $topl1, $starty + $len,
- $topl2, $V, $snake);
- } else if ($d == 1) {
+ + $this->lcs_rec( $bottoml1, $startx - 1, $bottoml2,
+ $starty - 1, $V, $snake )
+ + $this->lcs_rec( $startx + $len, $topl1, $starty + $len,
+ $topl2, $V, $snake );
+ } else if ( $d == 1 ) {
/*
* In this case the sequences differ by exactly 1 line. We have
* already saved all the lines after the difference in the for loop
* above, now we need to save all the lines before the difference.
*/
- $max = min($startx - $bottoml1, $starty - $bottoml2);
- for ($i = 0; $i < $max; ++$i) {
+ $max = min( $startx - $bottoml1, $starty - $bottoml2 );
+ for ( $i = 0; $i < $max; ++$i ) {
$this->removed[$bottoml1 + $i] =
$this->added[$bottoml2 + $i] = false;
}
@@ -249,7 +255,7 @@ class WikiDiff3 {
return $len;
}
- private function find_middle_snake($bottoml1, $topl1, $bottoml2,$topl2, &$V, &$snake) {
+ private function find_middle_snake( $bottoml1, $topl1, $bottoml2, $topl2, &$V, &$snake ) {
$from = &$this->from;
$to = &$this->to;
$V0 = &$V[0];
@@ -257,24 +263,24 @@ class WikiDiff3 {
$snake0 = &$snake[0];
$snake1 = &$snake[1];
$snake2 = &$snake[2];
- $bottoml1_min_1 = $bottoml1-1;
- $bottoml2_min_1 = $bottoml2-1;
+ $bottoml1_min_1 = $bottoml1 -1;
+ $bottoml2_min_1 = $bottoml2 -1;
$N = $topl1 - $bottoml1_min_1;
$M = $topl2 - $bottoml2_min_1;
$delta = $N - $M;
- $maxabsx = $N+$bottoml1;
- $maxabsy = $M+$bottoml2;
- $limit = min($this->maxDifferences, ceil(($N + $M ) / 2));
+ $maxabsx = $N + $bottoml1;
+ $maxabsy = $M + $bottoml2;
+ $limit = min( $this->maxDifferences, ceil( ( $N + $M ) / 2 ) );
- //value_to_add_forward: a 0 or 1 that we add to the start
+ // value_to_add_forward: a 0 or 1 that we add to the start
// offset to make it odd/even
- if (($M & 1) == 1) {
+ if ( ( $M & 1 ) == 1 ) {
$value_to_add_forward = 1;
} else {
$value_to_add_forward = 0;
}
- if (($N & 1) == 1) {
+ if ( ( $N & 1 ) == 1 ) {
$value_to_add_backward = 1;
} else {
$value_to_add_backward = 0;
@@ -290,18 +296,18 @@ class WikiDiff3 {
$V0[$limit_plus_1] = 0;
$V1[$limit_min_1] = $N;
- $limit = min($this->maxDifferences, ceil(($N + $M ) / 2));
+ $limit = min( $this->maxDifferences, ceil( ( $N + $M ) / 2 ) );
- if (($delta & 1) == 1) {
- for ($d = 0; $d <= $limit; ++$d) {
- $start_diag = max($value_to_add_forward + $start_forward, -$d);
- $end_diag = min($end_forward, $d);
+ if ( ( $delta & 1 ) == 1 ) {
+ for ( $d = 0; $d <= $limit; ++$d ) {
+ $start_diag = max( $value_to_add_forward + $start_forward, -$d );
+ $end_diag = min( $end_forward, $d );
$value_to_add_forward = 1 - $value_to_add_forward;
// compute forward furthest reaching paths
- for ($k = $start_diag; $k <= $end_diag; $k += 2) {
- if ($k == -$d || ($k < $d
- && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k])) {
+ for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
+ if ( $k == -$d || ( $k < $d
+ && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] ) ) {
$x = $V0[$limit_plus_1 + $k];
} else {
$x = $V0[$limit_min_1 + $k] + 1;
@@ -310,36 +316,36 @@ class WikiDiff3 {
$absx = $snake0 = $x + $bottoml1;
$absy = $snake1 = $x - $k + $bottoml2;
- while ($absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy]) {
+ while ( $absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy] ) {
++$absx;
++$absy;
}
- $x = $absx-$bottoml1;
+ $x = $absx -$bottoml1;
$snake2 = $absx -$snake0;
$V0[$limit + $k] = $x;
- if ($k >= $delta - $d + 1 && $k <= $delta + $d - 1
- && $x >= $V1[$limit + $k - $delta]) {
+ if ( $k >= $delta - $d + 1 && $k <= $delta + $d - 1
+ && $x >= $V1[$limit + $k - $delta] ) {
return 2 * $d - 1;
}
// check to see if we can cut down the diagonal range
- if ($x >= $N && $end_forward > $k - 1) {
+ if ( $x >= $N && $end_forward > $k - 1 ) {
$end_forward = $k - 1;
- } else if ($absy - $bottoml2 >= $M) {
+ } else if ( $absy - $bottoml2 >= $M ) {
$start_forward = $k + 1;
$value_to_add_forward = 0;
}
}
- $start_diag = max($value_to_add_backward + $start_backward, -$d);
- $end_diag = min($end_backward, $d);
+ $start_diag = max( $value_to_add_backward + $start_backward, -$d );
+ $end_diag = min( $end_backward, $d );
$value_to_add_backward = 1 - $value_to_add_backward;
// compute backward furthest reaching paths
- for ($k = $start_diag; $k <= $end_diag; $k += 2) {
- if ($k == $d
- || ($k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k])) {
+ for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
+ if ( $k == $d
+ || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] ) ) {
$x = $V1[$limit_min_1 + $k];
} else {
$x = $V1[$limit_plus_1 + $k] - 1;
@@ -348,8 +354,8 @@ class WikiDiff3 {
$y = $x - $k - $delta;
$snake2 = 0;
- while ($x > 0 && $y > 0
- && $from[$x +$bottoml1_min_1] === $to[$y + $bottoml2_min_1]) {
+ while ( $x > 0 && $y > 0
+ && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1] ) {
--$x;
--$y;
++$snake2;
@@ -357,24 +363,24 @@ class WikiDiff3 {
$V1[$limit + $k] = $x;
// check to see if we can cut down our diagonal range
- if ($x <= 0) {
+ if ( $x <= 0 ) {
$start_backward = $k + 1;
$value_to_add_backward = 0;
- } else if ($y <= 0 && $end_backward > $k - 1) {
+ } else if ( $y <= 0 && $end_backward > $k - 1 ) {
$end_backward = $k - 1;
}
}
}
} else {
- for ($d = 0; $d <= $limit; ++$d) {
- $start_diag = max($value_to_add_forward + $start_forward, -$d);
- $end_diag = min($end_forward, $d);
+ for ( $d = 0; $d <= $limit; ++$d ) {
+ $start_diag = max( $value_to_add_forward + $start_forward, -$d );
+ $end_diag = min( $end_forward, $d );
$value_to_add_forward = 1 - $value_to_add_forward;
// compute forward furthest reaching paths
- for ($k = $start_diag; $k <= $end_diag; $k += 2) {
- if ($k == -$d
- || ($k < $d && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k])) {
+ for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
+ if ( $k == -$d
+ || ( $k < $d && $V0[$limit_min_1 + $k] < $V0[$limit_plus_1 + $k] ) ) {
$x = $V0[$limit_plus_1 + $k];
} else {
$x = $V0[$limit_min_1 + $k] + 1;
@@ -383,31 +389,31 @@ class WikiDiff3 {
$absx = $snake0 = $x + $bottoml1;
$absy = $snake1 = $x - $k + $bottoml2;
- while ($absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy]) {
+ while ( $absx < $maxabsx && $absy < $maxabsy && $from[$absx] === $to[$absy] ) {
++$absx;
++$absy;
}
- $x = $absx-$bottoml1;
+ $x = $absx -$bottoml1;
$snake2 = $absx -$snake0;
$V0[$limit + $k] = $x;
// check to see if we can cut down the diagonal range
- if ($x >= $N && $end_forward > $k - 1) {
+ if ( $x >= $N && $end_forward > $k - 1 ) {
$end_forward = $k - 1;
- } else if ($absy-$bottoml2 >= $M) {
+ } else if ( $absy -$bottoml2 >= $M ) {
$start_forward = $k + 1;
$value_to_add_forward = 0;
}
}
- $start_diag = max($value_to_add_backward + $start_backward, -$d);
- $end_diag = min($end_backward, $d);
+ $start_diag = max( $value_to_add_backward + $start_backward, -$d );
+ $end_diag = min( $end_backward, $d );
$value_to_add_backward = 1 - $value_to_add_backward;
// compute backward furthest reaching paths
- for ($k = $start_diag; $k <= $end_diag; $k += 2) {
- if ($k == $d
- || ($k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k])) {
+ for ( $k = $start_diag; $k <= $end_diag; $k += 2 ) {
+ if ( $k == $d
+ || ( $k != -$d && $V1[$limit_min_1 + $k] < $V1[$limit_plus_1 + $k] ) ) {
$x = $V1[$limit_min_1 + $k];
} else {
$x = $V1[$limit_plus_1 + $k] - 1;
@@ -416,26 +422,26 @@ class WikiDiff3 {
$y = $x - $k - $delta;
$snake2 = 0;
- while ($x > 0 && $y > 0
- && $from[$x +$bottoml1_min_1] === $to[$y + $bottoml2_min_1]) {
+ while ( $x > 0 && $y > 0
+ && $from[$x + $bottoml1_min_1] === $to[$y + $bottoml2_min_1] ) {
--$x;
--$y;
++$snake2;
}
$V1[$limit + $k] = $x;
- if ($k >= -$delta - $d && $k <= $d - $delta
- && $x <= $V0[$limit + $k + $delta]) {
+ if ( $k >= -$delta - $d && $k <= $d - $delta
+ && $x <= $V0[$limit + $k + $delta] ) {
$snake0 = $bottoml1 + $x;
$snake1 = $bottoml2 + $y;
return 2 * $d;
}
// check to see if we can cut down our diagonal range
- if ($x <= 0) {
+ if ( $x <= 0 ) {
$start_backward = $k + 1;
$value_to_add_backward = 0;
- } else if ($y <= 0 && $end_backward > $k - 1) {
+ } else if ( $y <= 0 && $end_backward > $k - 1 ) {
$end_backward = $k - 1;
}
}
@@ -447,12 +453,12 @@ class WikiDiff3 {
* there.
*/
- $most_progress = self::findMostProgress($M, $N, $limit, $V);
+ $most_progress = self::findMostProgress( $M, $N, $limit, $V );
$snake0 = $bottoml1 + $most_progress[0];
$snake1 = $bottoml2 + $most_progress[1];
$snake2 = 0;
- wfDebug("Computing the LCS is too expensive. Using a heuristic.\n");
+ wfDebug( "Computing the LCS is too expensive. Using a heuristic.\n" );
$this->heuristicUsed = true;
return 5; /*
* HACK: since we didn't really finish the LCS computation
@@ -463,48 +469,48 @@ class WikiDiff3 {
*/
}
- private static function findMostProgress($M, $N, $limit, $V) {
+ private static function findMostProgress( $M, $N, $limit, $V ) {
$delta = $N - $M;
- if (($M & 1) == ($limit & 1)) {
- $forward_start_diag = max(-$M, -$limit);
+ if ( ( $M & 1 ) == ( $limit & 1 ) ) {
+ $forward_start_diag = max( -$M, -$limit );
} else {
- $forward_start_diag = max(1 - $M, -$limit);
+ $forward_start_diag = max( 1 - $M, -$limit );
}
- $forward_end_diag = min($N, $limit);
+ $forward_end_diag = min( $N, $limit );
- if (($N & 1) == ($limit & 1)) {
- $backward_start_diag = max(-$N, -$limit);
+ if ( ( $N & 1 ) == ( $limit & 1 ) ) {
+ $backward_start_diag = max( -$N, -$limit );
} else {
- $backward_start_diag = max(1 - $N, -$limit);
+ $backward_start_diag = max( 1 - $N, -$limit );
}
- $backward_end_diag = -min($M, $limit);
+ $backward_end_diag = -min( $M, $limit );
- $temp = array(0, 0, 0);
+ $temp = array( 0, 0, 0 );
- $max_progress = array_fill(0, ceil(max($forward_end_diag - $forward_start_diag,
- $backward_end_diag - $backward_start_diag) / 2), $temp);
+ $max_progress = array_fill( 0, ceil( max( $forward_end_diag - $forward_start_diag,
+ $backward_end_diag - $backward_start_diag ) / 2 ), $temp );
$num_progress = 0; // the 1st entry is current, it is initialized
// with 0s
// first search the forward diagonals
- for ($k = $forward_start_diag; $k <= $forward_end_diag; $k += 2) {
+ for ( $k = $forward_start_diag; $k <= $forward_end_diag; $k += 2 ) {
$x = $V[0][$limit + $k];
$y = $x - $k;
- if ($x > $N || $y > $M) {
+ if ( $x > $N || $y > $M ) {
continue;
}
$progress = $x + $y;
- if ($progress > $max_progress[0][2]) {
+ if ( $progress > $max_progress[0][2] ) {
$num_progress = 0;
$max_progress[0][0] = $x;
$max_progress[0][1] = $y;
$max_progress[0][2] = $progress;
- } else if ($progress == $max_progress[0][2]) {
+ } else if ( $progress == $max_progress[0][2] ) {
++$num_progress;
$max_progress[$num_progress][0] = $x;
$max_progress[$num_progress][1] = $y;
@@ -517,21 +523,21 @@ class WikiDiff3 {
// direction
// now search the backward diagonals
- for ($k = $backward_start_diag; $k <= $backward_end_diag; $k += 2) {
+ for ( $k = $backward_start_diag; $k <= $backward_end_diag; $k += 2 ) {
$x = $V[1][$limit + $k];
$y = $x - $k - $delta;
- if ($x < 0 || $y < 0) {
+ if ( $x < 0 || $y < 0 ) {
continue;
}
$progress = $N - $x + $M - $y;
- if ($progress > $max_progress[0][2]) {
+ if ( $progress > $max_progress[0][2] ) {
$num_progress = 0;
$max_progress_forward = false;
$max_progress[0][0] = $x;
$max_progress[0][1] = $y;
$max_progress[0][2] = $progress;
- } else if ($progress == $max_progress[0][2] && !$max_progress_forward) {
+ } else if ( $progress == $max_progress[0][2] && !$max_progress_forward ) {
++$num_progress;
$max_progress[$num_progress][0] = $x;
$max_progress[$num_progress][1] = $y;
@@ -540,13 +546,13 @@ class WikiDiff3 {
}
// return the middle diagonal with maximal progress.
- return $max_progress[floor($num_progress / 2)];
+ return $max_progress[floor( $num_progress / 2 )];
}
- public function getLcsLength(){
- if($this->heuristicUsed && !$this->lcsLengthCorrectedForHeuristic){
+ public function getLcsLength() {
+ if ( $this->heuristicUsed && !$this->lcsLengthCorrectedForHeuristic ) {
$this->lcsLengthCorrectedForHeuristic = true;
- $this->length = $this->m-array_sum($this->added);
+ $this->length = $this->m -array_sum( $this->added );
}
return $this->length;
}
@@ -556,7 +562,7 @@ class WikiDiff3 {
/**
* Alternative representation of a set of changes, by the index
* ranges that are changed.
- *
+ *
* @ingroup DifferenceEngine
*/
class RangeDifference {
@@ -569,7 +575,7 @@ class RangeDifference {
public $rightend;
public $rightlength;
- function __construct($leftstart, $leftend, $rightstart, $rightend){
+ function __construct( $leftstart, $leftend, $rightstart, $rightend ) {
$this->leftstart = $leftstart;
$this->leftend = $leftend;
$this->leftlength = $leftend - $leftstart;
diff --git a/includes/extauth/Hardcoded.php b/includes/extauth/Hardcoded.php
index a9a60bea..dfb46742 100644
--- a/includes/extauth/Hardcoded.php
+++ b/includes/extauth/Hardcoded.php
@@ -1,21 +1,26 @@
<?php
-
-# Copyright (C) 2009 Aryeh Gregor
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * External authentication with hardcoded user names and passwords
+ *
+ * Copyright © 2009 Aryeh Gregor
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 class supports external authentication from a literal array dumped in
diff --git a/includes/extauth/MediaWiki.php b/includes/extauth/MediaWiki.php
index 7d6a3c71..9df4ea1f 100644
--- a/includes/extauth/MediaWiki.php
+++ b/includes/extauth/MediaWiki.php
@@ -1,21 +1,26 @@
<?php
-
-# Copyright (C) 2009 Aryeh Gregor
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * External authentication with external MediaWiki database.
+ *
+ * Copyright © 2009 Aryeh Gregor
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 class supports authentication against an external MediaWiki database,
@@ -67,15 +72,14 @@ class ExternalUser_MediaWiki extends ExternalUser {
private function initFromCond( $cond ) {
global $wgExternalAuthConf;
- $class = 'Database' . $wgExternalAuthConf['DBtype'];
- $this->mDb = new $class(
- $wgExternalAuthConf['DBserver'],
- $wgExternalAuthConf['DBuser'],
- $wgExternalAuthConf['DBpassword'],
- $wgExternalAuthConf['DBname'],
- false,
- 0,
- $wgExternalAuthConf['DBprefix']
+ $this->mDb = DatabaseBase::newFromType( $wgExternalAuthConf['DBtype'],
+ array(
+ 'server' => $wgExternalAuthConf['DBserver'],
+ 'user' => $wgExternalAuthConf['DBuser'],
+ 'password' => $wgExternalAuthConf['DBpassword'],
+ 'dbname' => $wgExternalAuthConf['DBname'],
+ 'tableprefix' => $wgExternalAuthConf['DBprefix'],
+ )
);
$row = $this->mDb->selectRow(
diff --git a/includes/extauth/vB.php b/includes/extauth/vB.php
index 23523665..860048f3 100644
--- a/includes/extauth/vB.php
+++ b/includes/extauth/vB.php
@@ -1,21 +1,26 @@
<?php
-
-# Copyright (C) 2009 Aryeh Gregor
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * External authentication with a vBulletin database.
+ *
+ * Copyright © 2009 Aryeh Gregor
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 class supports the proprietary vBulletin forum system
@@ -36,7 +41,7 @@
* @ingroup ExternalUser
*/
class ExternalUser_vB extends ExternalUser {
- private $mDb, $mRow;
+ private $mRow;
protected function initFromName( $name ) {
return $this->initFromCond( array( 'username' => $name ) );
@@ -50,13 +55,13 @@ class ExternalUser_vB extends ExternalUser {
# Try using the session table. It will only have a row if the user has
# an active session, so it might not always work, but it's a lot easier
# than trying to convince PHP to give us vB's $_SESSION.
- global $wgExternalAuthConf;
+ global $wgExternalAuthConf, $wgRequest;
if ( !isset( $wgExternalAuthConf['cookieprefix'] ) ) {
$prefix = 'bb';
} else {
$prefix = $wgExternalAuthConf['cookieprefix'];
}
- if ( !isset( $_COOKIE["{$prefix}sessionhash"] ) ) {
+ if ( $wgRequest->getCookie( 'sessionhash', $prefix ) === null ) {
return false;
}
@@ -67,7 +72,7 @@ class ExternalUser_vB extends ExternalUser {
$this->getFields(),
array(
'session.userid = user.userid',
- 'sessionhash' => $_COOKIE["{$prefix}sessionhash"]
+ 'sessionhash' => $wgRequest->getCookie( 'sessionhash', $prefix ),
),
__METHOD__
);
diff --git a/includes/filerepo/ArchivedFile.php b/includes/filerepo/ArchivedFile.php
index ffc06303..ecc09978 100644
--- a/includes/filerepo/ArchivedFile.php
+++ b/includes/filerepo/ArchivedFile.php
@@ -1,10 +1,17 @@
<?php
+/**
+ * Deleted file in the 'filearchive' table
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
- * @ingroup Media
+ * Class representing a row of the 'filearchive' table
+ *
+ * @ingroup FileRepo
*/
-class ArchivedFile
-{
+class ArchivedFile {
/**#@+
* @private
*/
@@ -29,7 +36,7 @@ class ArchivedFile
/**#@-*/
- function ArchivedFile( $title, $id=0, $key='' ) {
+ function __construct( $title, $id=0, $key='' ) {
$this->id = -1;
$this->title = false;
$this->name = false;
@@ -140,7 +147,6 @@ class ArchivedFile
$this->deleted = $row->fa_deleted;
} else {
throw new MWException( 'This title does not correspond to an image page.' );
- return;
}
$this->dataLoaded = true;
$this->exists = true;
@@ -219,7 +225,7 @@ class ArchivedFile
* Return the FileStore storage group
*/
public function getGroup() {
- return $file->group;
+ return $this->group;
}
/**
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
index 0dd9d0f7..e2251b2b 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * A repository for files accessible via the local filesystem.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A repository for files accessible via the local filesystem. Does not support
@@ -132,8 +138,8 @@ class FSRepo extends FileRepo {
/**
* Store a batch of files
*
- * @param array $triplets (src,zone,dest) triplets as per store()
- * @param integer $flags Bitwise combination of the following flags:
+ * @param $triplets Array: (src,zone,dest) triplets as per store()
+ * @param $flags Integer: bitwise combination of the following flags:
* self::DELETE_SOURCE Delete the source file after upload
* self::OVERWRITE Overwrite an existing destination file instead of failing
* self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
@@ -267,8 +273,8 @@ class FSRepo extends FileRepo {
/**
* Checks existence of specified array of files.
*
- * @param array $files URLs of files to check
- * @param integer $flags Bitwise combination of the following flags:
+ * @param $files Array: URLs of files to check
+ * @param $flags Integer: bitwise combination of the following flags:
* self::FILES_ONLY Mark file as existing only if it is a file (not directory)
* @return Either array of files and existence flags, or false
*/
@@ -307,9 +313,9 @@ class FSRepo extends FileRepo {
/**
* Pick a random name in the temp zone and store a file to it.
- * @param string $originalName The base name of the file as specified
+ * @param $originalName String: the base name of the file as specified
* by the user. The file extension will be maintained.
- * @param string $srcPath The current location of the file.
+ * @param $srcPath String: the current location of the file.
* @return FileRepoStatus object with the URL in the value.
*/
function storeTemp( $originalName, $srcPath ) {
@@ -325,8 +331,8 @@ class FSRepo extends FileRepo {
/**
* Remove a temporary file or mark it for garbage collection
- * @param string $virtualUrl The virtual URL returned by storeTemp
- * @return boolean True on success, false on failure
+ * @param $virtualUrl String: the virtual URL returned by storeTemp
+ * @return Boolean: true on success, false on failure
*/
function freeTemp( $virtualUrl ) {
$temp = "mwrepo://{$this->name}/temp";
@@ -343,8 +349,8 @@ class FSRepo extends FileRepo {
/**
* Publish a batch of files
- * @param array $triplets (source,dest,archive) triplets as per publish()
- * @param integer $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * @param $triplets Array: (source,dest,archive) triplets as per publish()
+ * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source files should be deleted if possible
*/
function publishBatch( $triplets, $flags = 0 ) {
@@ -454,7 +460,7 @@ class FSRepo extends FileRepo {
* If no valid deletion archive is configured, this may either delete the
* file or throw an exception, depending on the preference of the repository.
*
- * @param array $sourceDestPairs Array of source/destination pairs. Each element
+ * @param $sourceDestPairs Array of source/destination pairs. Each element
* is a two-element array containing the source file path relative to the
* public root in the first element, and the archive file path relative
* to the deleted zone root in the second element.
@@ -615,7 +621,7 @@ class FSRepo extends FileRepo {
/**
* Chmod a file, supressing the warnings.
- * @param String $path The path to change
+ * @param $path String: the path to change
*/
protected function chmod( $path ) {
wfSuppressWarnings();
diff --git a/includes/filerepo/File.php b/includes/filerepo/File.php
index d79a1661..192e8c8a 100644
--- a/includes/filerepo/File.php
+++ b/includes/filerepo/File.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Base code for files.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* Implements some public methods and some protected utility functions which
@@ -177,7 +183,8 @@ abstract class File {
* Return a fully-qualified URL to the file.
* Upload URL paths _may or may not_ be fully qualified, so
* we check. Local paths are assumed to belong on $wgServer.
- * @return string
+ *
+ * @return String
*/
public function getFullUrl() {
return wfExpandUrl( $this->getUrl() );
@@ -260,6 +267,19 @@ abstract class File {
}
/**
+ * Return true if the file is vectorized
+ */
+ public function isVectorized() {
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ return $handler->isVectorized( $this );
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
* Get handler-specific metadata
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
@@ -437,26 +457,21 @@ abstract class File {
/**
* Get a ThumbnailImage which is the same size as the source
*/
- function getUnscaledThumb( $page = false ) {
+ function getUnscaledThumb( $handlerParams = array() ) {
+ $hp =& $handlerParams;
+ $page = isset( $hp['page'] ) ? $hp['page'] : false;
$width = $this->getWidth( $page );
if ( !$width ) {
return $this->iconThumb();
}
- if ( $page ) {
- $params = array(
- 'page' => $page,
- 'width' => $this->getWidth( $page )
- );
- } else {
- $params = array( 'width' => $this->getWidth() );
- }
- return $this->transform( $params );
+ $hp['width'] = $width;
+ return $this->transform( $hp );
}
/**
* Return the file name of a thumbnail with the specified parameters
*
- * @param array $params Handler-specific parameters
+ * @param $params Array: handler-specific parameters
* @private -ish
*/
function thumbName( $params ) {
@@ -464,7 +479,7 @@ abstract class File {
return null;
}
$extension = $this->getExtension();
- list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType() );
+ list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType(), $params );
$thumbName = $this->handler->makeParamString( $params ) . '-' . $this->getName();
if ( $thumbExt != $extension ) {
$thumbName .= ".$thumbExt";
@@ -484,8 +499,8 @@ abstract class File {
* specified, the generated image will be no bigger than width x height,
* and will also have correct aspect ratio.
*
- * @param integer $width maximum width of the generated thumbnail
- * @param integer $height maximum height of the image (optional)
+ * @param $width Integer: maximum width of the generated thumbnail
+ * @param $height Integer: maximum height of the image (optional)
*/
public function createThumb( $width, $height = -1 ) {
$params = array( 'width' => $width );
@@ -500,19 +515,20 @@ abstract class File {
/**
* As createThumb, but returns a ThumbnailImage object. This can
* provide access to the actual file, the real size of the thumb,
- * and can produce a convenient <img> tag for you.
+ * and can produce a convenient \<img\> tag for you.
*
* For non-image formats, this may return a filetype-specific icon.
*
- * @param integer $width maximum width of the generated thumbnail
- * @param integer $height maximum height of the image (optional)
- * @param boolean $render Deprecated
+ * @param $width Integer: maximum width of the generated thumbnail
+ * @param $height Integer: maximum height of the image (optional)
+ * @param $render Integer: Deprecated
*
* @return ThumbnailImage or null on failure
*
* @deprecated use transform()
*/
public function getThumbnail( $width, $height=-1, $render = true ) {
+ wfDeprecated( __METHOD__ );
$params = array( 'width' => $width );
if ( $height != -1 ) {
$params['height'] = $height;
@@ -523,10 +539,10 @@ abstract class File {
/**
* Transform a media file
*
- * @param array $params An associative array of handler-specific parameters. Typical
- * keys are width, height and page.
- * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
- * @return MediaTransformOutput
+ * @param $params Array: an associative array of handler-specific parameters.
+ * Typical keys are width, height and page.
+ * @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering
+ * @return MediaTransformOutput | false
*/
function transform( $params, $flags = 0 ) {
global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch, $wgServer;
@@ -560,7 +576,7 @@ abstract class File {
$thumbPath = $this->getThumbPath( $thumbName );
$thumbUrl = $this->getThumbUrl( $thumbName );
- if ( $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
+ if ( $this->repo && $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
$thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
break;
}
@@ -842,19 +858,18 @@ abstract class File {
/**
* Move or copy a file to its public location. If a file exists at the
- * destination, move it to an archive. Returns the archive name on success
- * or an empty string if it was a new file, and a wikitext-formatted
- * WikiError object on failure.
+ * destination, move it to an archive. Returns a FileRepoStatus object with
+ * the archive name in the "value" member on success.
*
* The archive name should be passed through to recordUpload for database
* registration.
*
- * @param string $sourcePath Local filesystem path to the source image
- * @param integer $flags A bitwise combination of:
+ * @param $srcPath String: local filesystem path to the source image
+ * @param $flags Integer: a bitwise combination of:
* File::DELETE_SOURCE Delete the source file, i.e. move
* rather than copy
- * @return The archive name on success or an empty string if it was a new
- * file, and a wikitext-formatted WikiError object on failure.
+ * @return FileRepoStatus object. On success, the value member contains the
+ * archive name, or an empty string if it was a new file.
*
* STUB
* Overridden by LocalFile
@@ -872,6 +887,7 @@ abstract class File {
* @deprecated Use HTMLCacheUpdate, this function uses too much memory
*/
function getLinksTo( $options = array() ) {
+ wfDeprecated( __METHOD__ );
wfProfileIn( __METHOD__ );
// Note: use local DB not repo DB, we want to know local links
@@ -884,21 +900,21 @@ abstract class File {
$encName = $db->addQuotes( $this->getName() );
$res = $db->select( array( 'page', 'imagelinks'),
- array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect' ),
- array( 'page_id' => 'il_from', 'il_to' => $encName ),
+ array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ array( 'page_id=il_from', 'il_to' => $encName ),
__METHOD__,
$options );
$retVal = array();
if ( $db->numRows( $res ) ) {
- while ( $row = $db->fetchObject( $res ) ) {
- if ( $titleObj = Title::newFromRow( $row ) ) {
- $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect );
+ foreach ( $res as $row ) {
+ $titleObj = Title::newFromRow( $row );
+ if ( $titleObj ) {
+ $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect, $row->page_latest );
$retVal[] = $titleObj;
}
}
}
- $db->freeResult( $res );
wfProfileOut( __METHOD__ );
return $retVal;
}
@@ -916,7 +932,8 @@ abstract class File {
* @return bool
*/
function isLocal() {
- return $this->getRepoName() == 'local';
+ $repo = $this->getRepo();
+ return $repo && $repo->isLocal();
}
/**
@@ -992,8 +1009,8 @@ abstract class File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $reason
- * @param $suppress, hide content from sysops?
+ * @param $reason String
+ * @param $suppress Boolean: hide content from sysops?
* @return true on success, false on some kind of failure
* STUB
* Overridden by LocalFile
@@ -1010,7 +1027,7 @@ abstract class File {
*
* @param $versions set of record ids of deleted items to restore,
* or empty to restore all revisions.
- * @param $unsuppress, remove restrictions on content upon restoration?
+ * @param $unsuppress remove restrictions on content upon restoration?
* @return the number of file revisions restored if successful,
* or false on failure
* STUB
@@ -1032,7 +1049,7 @@ abstract class File {
}
/**
- * Returns the number of pages of a multipage document, or NULL for
+ * Returns the number of pages of a multipage document, or false for
* documents which aren't multipage documents
*/
function pageCount() {
@@ -1059,11 +1076,11 @@ abstract class File {
}
/**
- * Get an image size array like that returned by getimagesize(), or false if it
+ * Get an image size array like that returned by getImageSize(), or false if it
* can't be determined.
*
- * @param string $fileName The filename
- * @return array
+ * @param $fileName String: The filename
+ * @return Array
*/
function getImageSize( $fileName ) {
if ( !$this->getHandler() ) {
@@ -1156,8 +1173,8 @@ abstract class File {
* Determine if the current user is allowed to view a particular
* field of this file, if it's marked as deleted.
* STUB
- * @param int $field
- * @return bool
+ * @param $field Integer
+ * @return Boolean
*/
function userCan( $field ) {
return true;
@@ -1166,9 +1183,9 @@ abstract class File {
/**
* Get an associative array containing information about a file in the local filesystem.
*
- * @param string $path Absolute local filesystem path
- * @param mixed $ext The file extension, or true to extract it from the filename.
- * Set it to false to ignore the extension.
+ * @param $path String: absolute local filesystem path
+ * @param $ext Mixed: the file extension, or true to extract it from the filename.
+ * Set it to false to ignore the extension.
*/
static function getPropsFromPath( $path, $ext = true ) {
wfProfileIn( __METHOD__ );
@@ -1180,7 +1197,16 @@ abstract class File {
if ( $info['fileExists'] ) {
$magic = MimeMagic::singleton();
- $info['mime'] = $magic->guessMimeType( $path, $ext );
+ if ( $ext === true ) {
+ $i = strrpos( $path, '.' );
+ $ext = strtolower( $i ? substr( $path, $i + 1 ) : '' );
+ }
+
+ # mime type according to file contents
+ $info['file-mime'] = $magic->guessMimeType( $path, false );
+ # logical mime type
+ $info['mime'] = $magic->improveTypeFromExtension( $info['file-mime'], $ext );
+
list( $info['major_mime'], $info['minor_mime'] ) = self::splitMime( $info['mime'] );
$info['media_type'] = $magic->getMediaType( $path, $info['mime'] );
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index f94709b3..ff73a73c 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -1,8 +1,15 @@
<?php
+/**
+ * Base code for file repositories.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
- * Base class for file repositories
+ * Base class for file repositories.
* Do not instantiate, use a derived class.
+ *
* @ingroup FileRepo
*/
abstract class FileRepo {
@@ -12,7 +19,8 @@ abstract class FileRepo {
const OVERWRITE_SAME = 4;
var $thumbScriptUrl, $transformVia404;
- var $descBaseUrl, $scriptDirUrl, $articleUrl, $fetchDescription, $initialCapital;
+ var $descBaseUrl, $scriptDirUrl, $scriptExtension, $articleUrl;
+ var $fetchDescription, $initialCapital;
var $pathDisclosureProtection = 'paranoid';
var $descriptionCacheExpiry, $hashLevels, $url, $thumbUrl;
@@ -31,7 +39,8 @@ abstract class FileRepo {
$this->initialCapital = MWNamespace::isCapitalized( NS_FILE );
foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
- 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl' ) as $var )
+ 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl', 'scriptExtension' )
+ as $var )
{
if ( isset( $info[$var] ) ) {
$this->$var = $info[$var];
@@ -49,12 +58,13 @@ abstract class FileRepo {
/**
* Create a new File object from the local repository
- * @param mixed $title Title object or string
- * @param mixed $time Time at which the image was uploaded.
- * If this is specified, the returned object will be an
- * instance of the repository's old file class instead of
- * a current file. Repositories not supporting version
- * control should return false if this parameter is set.
+ *
+ * @param $title Mixed: Title object or string
+ * @param $time Mixed: Time at which the image was uploaded.
+ * If this is specified, the returned object will be an
+ * instance of the repository's old file class instead of a
+ * current file. Repositories not supporting version control
+ * should return false if this parameter is set.
*/
function newFile( $title, $time = false ) {
if ( !($title instanceof Title) ) {
@@ -79,7 +89,7 @@ abstract class FileRepo {
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param mixed $title Title object or string
+ * @param $title Mixed: Title object or string
* @param $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
@@ -144,7 +154,7 @@ abstract class FileRepo {
/*
* Find many files at once.
- * @param array $items, an array of titles, or an array of findFile() options with
+ * @param $items An array of titles, or an array of findFile() options with
* the "title" option giving the title. Example:
*
* $findItem = array( 'title' => $title, 'private' => true );
@@ -153,7 +163,7 @@ abstract class FileRepo {
*/
function findFiles( $items ) {
$result = array();
- foreach ( $items as $index => $item ) {
+ foreach ( $items as $item ) {
if ( is_array( $item ) ) {
$title = $item['title'];
$options = $item;
@@ -163,31 +173,33 @@ abstract class FileRepo {
$options = array();
}
$file = $this->findFile( $title, $options );
- if ( $file )
+ if ( $file ) {
$result[$file->getTitle()->getDBkey()] = $file;
+ }
}
return $result;
}
/**
* Create a new File object from the local repository
- * @param mixed $sha1 SHA-1 key
- * @param mixed $time Time at which the image was uploaded.
- * If this is specified, the returned object will be an
- * instance of the repository's old file class instead of
- * a current file. Repositories not supporting version
- * control should return false if this parameter is set.
+ * @param $sha1 Mixed: SHA-1 key
+ * @param $time Mixed: time at which the image was uploaded.
+ * If this is specified, the returned object will be an
+ * of the repository's old file class instead of a current
+ * file. Repositories not supporting version control should
+ * return false if this parameter is set.
*/
function newFileFromKey( $sha1, $time = false ) {
if ( $time ) {
if ( $this->oldFileFactoryKey ) {
return call_user_func( $this->oldFileFactoryKey, $sha1, $this, $time );
- } else {
- return false;
}
} else {
- return call_user_func( $this->fileFactoryKey, $sha1, $this );
+ if ( $this->fileFactoryKey ) {
+ return call_user_func( $this->fileFactoryKey, $sha1, $this );
+ }
}
+ return false;
}
/**
@@ -195,8 +207,8 @@ abstract class FileRepo {
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param string $sha1 string
- * @param array $options Option array, same as findFile().
+ * @param $sha1 String
+ * @param $options Option array, same as findFile().
*/
function findFileFromKey( $sha1, $options = array() ) {
if ( !is_array( $options ) ) {
@@ -217,7 +229,7 @@ abstract class FileRepo {
# Now try an old version of the file
if ( $time !== false ) {
$img = $this->newFileFromKey( $sha1, $time );
- if ( $img->exists() ) {
+ if ( $img && $img->exists() ) {
if ( !$img->isDeleted(File::DELETED_FILE) ) {
return $img;
} else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
@@ -237,7 +249,7 @@ abstract class FileRepo {
/**
* Get the URL corresponding to one of the four basic zones
- * @param String $zone One of: public, deleted, temp, thumb
+ * @param $zone String: one of: public, deleted, temp, thumb
* @return String or false
*/
function getZoneUrl( $zone ) {
@@ -255,7 +267,6 @@ abstract class FileRepo {
* Get the name of an image from its title object
*/
function getNameFromTitle( $title ) {
- global $wgCapitalLinks;
if ( $this->initialCapital != MWNamespace::isCapitalized( NS_FILE ) ) {
global $wgContLang;
$name = $title->getUserCaseDBKey();
@@ -295,6 +306,18 @@ abstract class FileRepo {
function getName() {
return $this->name;
}
+
+ /**
+ * Make an url to this repo
+ *
+ * @param $query mixed Query string to append
+ * @param $entry string Entry point; defaults to index
+ * @return string
+ */
+ function makeUrl( $query = '', $entry = 'index' ) {
+ $ext = isset( $this->scriptExtension ) ? $this->scriptExtension : '.php';
+ return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}{$ext}", $query );
+ }
/**
* Get the URL of an image description page. May return false if it is
@@ -325,8 +348,7 @@ abstract class FileRepo {
# We use "Image:" as the canonical namespace for
# compatibility across all MediaWiki versions,
# and just sort of hope index.php is right. ;)
- return $this->scriptDirUrl .
- "/index.php?title=Image:$encName";
+ return $this->makeUrl( "title=Image:$encName" );
}
return false;
}
@@ -336,8 +358,8 @@ abstract class FileRepo {
* MediaWiki this means action=render. This should only be called by the
* repository's file class, since it may return invalid results. User code
* should use File::getDescriptionText().
- * @param string $name Name of image to fetch
- * @param string $lang Language to fetch it in, if any.
+ * @param $name String: name of image to fetch
+ * @param $lang String: language to fetch it in, if any.
*/
function getDescriptionRenderUrl( $name, $lang = null ) {
$query = 'action=render';
@@ -345,9 +367,10 @@ abstract class FileRepo {
$query .= '&uselang=' . $lang;
}
if ( isset( $this->scriptDirUrl ) ) {
- return $this->scriptDirUrl . '/index.php?title=' .
+ return $this->makeUrl(
+ 'title=' .
wfUrlencode( 'Image:' . $name ) .
- "&$query";
+ "&$query" );
} else {
$descUrl = $this->getDescriptionUrl( $name );
if ( $descUrl ) {
@@ -357,14 +380,25 @@ abstract class FileRepo {
}
}
}
+
+ /**
+ * Get the URL of the stylesheet to apply to description pages
+ * @return string
+ */
+ function getDescriptionStylesheetUrl() {
+ if ( $this->scriptDirUrl ) {
+ return $this->makeUrl( 'title=MediaWiki:Filepage.css&' .
+ wfArrayToCGI( Skin::getDynamicStylesheetQuery() ) );
+ }
+ }
/**
* Store a file to a given destination.
*
- * @param string $srcPath Source path or virtual URL
- * @param string $dstZone Destination zone
- * @param string $dstRel Destination relative path
- * @param integer $flags Bitwise combination of the following flags:
+ * @param $srcPath String: source path or virtual URL
+ * @param $dstZone String: destination zone
+ * @param $dstRel String: destination relative path
+ * @param $flags Integer: bitwise combination of the following flags:
* self::DELETE_SOURCE Delete the source file after upload
* self::OVERWRITE Overwrite an existing destination file instead of failing
* self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
@@ -382,8 +416,8 @@ abstract class FileRepo {
/**
* Store a batch of files
*
- * @param array $triplets (src,zone,dest) triplets as per store()
- * @param integer $flags Flags as per store
+ * @param $triplets Array: (src,zone,dest) triplets as per store()
+ * @param $flags Integer: flags as per store
*/
abstract function storeBatch( $triplets, $flags = 0 );
@@ -391,18 +425,18 @@ abstract class FileRepo {
* Pick a random name in the temp zone and store a file to it.
* Returns a FileRepoStatus object with the URL in the value.
*
- * @param string $originalName The base name of the file as specified
+ * @param $originalName String: the base name of the file as specified
* by the user. The file extension will be maintained.
- * @param string $srcPath The current location of the file.
+ * @param $srcPath String: the current location of the file.
*/
abstract function storeTemp( $originalName, $srcPath );
/**
* Append the contents of the source path to the given file.
- * @param $srcPath string location of the source file
- * @param $toAppendPath string path to append to.
- * @param $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * @param $srcPath String: location of the source file
+ * @param $toAppendPath String: path to append to.
+ * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source file should be deleted if possible
* @return mixed Status or false
*/
@@ -410,8 +444,8 @@ abstract class FileRepo {
/**
* Remove a temporary file or mark it for garbage collection
- * @param string $virtualUrl The virtual URL returned by storeTemp
- * @return boolean True on success, false on failure
+ * @param $virtualUrl String: the virtual URL returned by storeTemp
+ * @return Boolean: true on success, false on failure
* STUB
*/
function freeTemp( $virtualUrl ) {
@@ -425,11 +459,11 @@ abstract class FileRepo {
* Returns a FileRepoStatus object. On success, the value contains "new" or
* "archived", to indicate whether the file was new with that name.
*
- * @param string $srcPath The source path or URL
- * @param string $dstRel The destination relative path
- * @param string $archiveRel The relative path where the existing file is to
+ * @param $srcPath String: the source path or URL
+ * @param $dstRel String: the destination relative path
+ * @param $archiveRel String: rhe relative path where the existing file is to
* be archived, if there is one. Relative to the public zone root.
- * @param integer $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source file should be deleted if possible
*/
function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
@@ -447,8 +481,8 @@ abstract class FileRepo {
/**
* Publish a batch of files
- * @param array $triplets (source,dest,archive) triplets as per publish()
- * @param integer $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * @param $triplets Array: (source,dest,archive) triplets as per publish()
+ * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source files should be deleted if possible
*/
abstract function publishBatch( $triplets, $flags = 0 );
@@ -461,8 +495,8 @@ abstract class FileRepo {
/**
* Checks existence of an array of files.
*
- * @param array $files URLs (or paths) of files to check
- * @param integer $flags Bitwise combination of the following flags:
+ * @param $files Array: URLs (or paths) of files to check
+ * @param $flags Integer: bitwise combination of the following flags:
* self::FILES_ONLY Mark file as existing only if it is a file (not directory)
* @return Either array of files and existence flags, or false
*/
@@ -478,7 +512,7 @@ abstract class FileRepo {
* assumes a naming scheme in the deleted zone based on content hash, as
* opposed to the public zone which is assumed to be unique.
*
- * @param array $sourceDestPairs Array of source/destination pairs. Each element
+ * @param $sourceDestPairs Array of source/destination pairs. Each element
* is a two-element array containing the source file path relative to the
* public root in the first element, and the archive file path relative
* to the deleted zone root in the second element.
@@ -490,10 +524,10 @@ abstract class FileRepo {
* Move a file to the deletion archive.
* If no valid deletion archive exists, this may either delete the file
* or throw an exception, depending on the preference of the repository
- * @param mixed $srcRel Relative path for the file to be deleted
- * @param mixed $archiveRel Relative path for the archive location.
+ * @param $srcRel Mixed: relative path for the file to be deleted
+ * @param $archiveRel Mixed: relative path for the archive location.
* Relative to a private archive directory.
- * @return WikiError object (wikitext-formatted), or true for success
+ * @return FileRepoStatus object
*/
function delete( $srcRel, $archiveRel ) {
return $this->deleteBatch( array( array( $srcRel, $archiveRel ) ) );
@@ -589,7 +623,7 @@ abstract class FileRepo {
* title object. If not, return false.
* STUB
*
- * @param Title $title Title of image
+ * @param $title Title of image
*/
function checkRedirect( $title ) {
return false;
@@ -600,7 +634,7 @@ abstract class FileRepo {
* Doesn't do anything for repositories that don't support image redirects.
*
* STUB
- * @param Title $title Title of image
+ * @param $title Title of image
*/
function invalidateImageRedirect( $title ) {}
@@ -620,7 +654,7 @@ abstract class FileRepo {
*/
public function getDisplayName() {
// We don't name our own repo, return nothing
- if ( $this->name == 'local' ) {
+ if ( $this->isLocal() ) {
return null;
}
// 'shared-repo-name-wikimediacommons' is used when $wgUseInstantCommons = true
@@ -632,6 +666,16 @@ abstract class FileRepo {
}
/**
+ * Returns true if this the local file repository.
+ *
+ * @return bool
+ */
+ function isLocal() {
+ return $this->getName() == 'local';
+ }
+
+
+ /**
* Get a key on the primary cache for this repository.
* Returns false if the repository's cache is not accessible at this site.
* The parameters are the parts of the key, as for wfMemcKey().
@@ -652,4 +696,11 @@ abstract class FileRepo {
array_unshift( $args, 'filerepo', $this->getName() );
return call_user_func_array( 'wfMemcKey', $args );
}
+
+ /**
+ * Get an UploadStash associated with this repo.
+ */
+ function getUploadStash() {
+ return new UploadStash( $this );
+ }
}
diff --git a/includes/filerepo/FileRepoStatus.php b/includes/filerepo/FileRepoStatus.php
index 63460fa8..161284c0 100644
--- a/includes/filerepo/FileRepoStatus.php
+++ b/includes/filerepo/FileRepoStatus.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Generic operation result for FileRepo-related operations
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* Generic operation result class for FileRepo-related operations
diff --git a/includes/filerepo/ForeignAPIFile.php b/includes/filerepo/ForeignAPIFile.php
index c46b1f8f..56fed75e 100644
--- a/includes/filerepo/ForeignAPIFile.php
+++ b/includes/filerepo/ForeignAPIFile.php
@@ -1,8 +1,14 @@
<?php
+/**
+ * Foreign file accessible through api.php requests.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
-/**
- * Very hacky and inefficient
- * do not use :D
+/**
+ * Foreign file accessible through api.php requests.
+ * Very hacky and inefficient, do not use :D
*
* @ingroup FileRepo
*/
@@ -15,16 +21,47 @@ class ForeignAPIFile extends File {
$this->mInfo = $info;
$this->mExists = $exists;
}
-
+
+ /**
+ * @static
+ * @param $title Title
+ * @param $repo ForeignApiRepo
+ * @return ForeignAPIFile|null
+ */
static function newFromTitle( $title, $repo ) {
- $info = $repo->getImageInfo( $title );
+ $data = $repo->fetchImageQuery( array(
+ 'titles' => 'File:' . $title->getDBKey(),
+ 'iiprop' => self::getProps(),
+ 'prop' => 'imageinfo' ) );
+
+ $info = $repo->getImageInfo( $data );
+
if( $info ) {
- return new ForeignAPIFile( $title, $repo, $info, true );
+ $lastRedirect = isset( $data['query']['redirects'] )
+ ? count( $data['query']['redirects'] ) - 1
+ : -1;
+ if( $lastRedirect >= 0 ) {
+ $newtitle = Title::newFromText( $data['query']['redirects'][$lastRedirect]['to']);
+ $img = new ForeignAPIFile( $newtitle, $repo, $info, true );
+ if( $img ) {
+ $img->redirectedFrom( $title->getDBkey() );
+ }
+ } else {
+ $img = new ForeignAPIFile( $title, $repo, $info, true );
+ }
+ return $img;
} else {
return null;
}
}
+ /**
+ * Get the property string for iiprop and aiprop
+ */
+ static function getProps() {
+ return 'timestamp|user|comment|url|size|sha1|metadata|mime';
+ }
+
// Dummy functions...
public function exists() {
return $this->mExists;
@@ -40,10 +77,10 @@ class ForeignAPIFile extends File {
return parent::transform( $params, $flags );
}
$thumbUrl = $this->repo->getThumbUrlFromCache(
- $this->getName(),
- isset( $params['width'] ) ? $params['width'] : -1,
- isset( $params['height'] ) ? $params['height'] : -1 );
- return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );;
+ $this->getName(),
+ isset( $params['width'] ) ? $params['width'] : -1,
+ isset( $params['height'] ) ? $params['height'] : -1 );
+ return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );
}
// Info we can get from API...
@@ -74,27 +111,33 @@ class ForeignAPIFile extends File {
}
public function getSize() {
- return intval( @$this->mInfo['size'] );
+ return isset( $this->mInfo['size'] ) ? intval( $this->mInfo['size'] ) : null;
}
public function getUrl() {
- return strval( @$this->mInfo['url'] );
+ return isset( $this->mInfo['url'] ) ? strval( $this->mInfo['url'] ) : null;
}
public function getUser( $method='text' ) {
- return strval( @$this->mInfo['user'] );
+ return isset( $this->mInfo['user'] ) ? strval( $this->mInfo['user'] ) : null;
}
public function getDescription() {
- return strval( @$this->mInfo['comment'] );
+ return isset( $this->mInfo['comment'] ) ? strval( $this->mInfo['comment'] ) : null;
}
function getSha1() {
- return wfBaseConvert( strval( @$this->mInfo['sha1'] ), 16, 36, 31 );
+ return isset( $this->mInfo['sha1'] ) ?
+ wfBaseConvert( strval( $this->mInfo['sha1'] ), 16, 36, 31 ) :
+ null;
}
function getTimestamp() {
- return wfTimestamp( TS_MW, strval( @$this->mInfo['timestamp'] ) );
+ return wfTimestamp( TS_MW,
+ isset( $this->mInfo['timestamp'] ) ?
+ strval( $this->mInfo['timestamp'] ) :
+ null
+ );
}
function getMimeType() {
@@ -122,15 +165,13 @@ class ForeignAPIFile extends File {
*/
function getThumbPath( $suffix = '' ) {
if ( $this->repo->canCacheThumbs() ) {
- global $wgUploadDirectory;
- $path = $wgUploadDirectory . '/thumb/' . $this->getHashPath( $this->getName() );
+ $path = $this->repo->getZonePath('thumb') . '/' . $this->getHashPath( $this->getName() );
if ( $suffix ) {
$path = $path . $suffix . '/';
}
return $path;
- }
- else {
- return null;
+ } else {
+ return null;
}
}
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index 264cb920..e4188d6b 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -1,9 +1,13 @@
<?php
+/**
+ * Foreign repository accessible through api.php requests.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A foreign repository with a remote MediaWiki with an API thingy
- * Very hacky and inefficient
- * do not use except for testing :D
*
* Example config:
*
@@ -18,17 +22,37 @@
* @ingroup FileRepo
*/
class ForeignAPIRepo extends FileRepo {
+ /* This version string is used in the user agent for requests and will help
+ * server maintainers in identify ForeignAPI usage.
+ * Update the version every time you make breaking or significant changes. */
+ const VERSION = "2.0";
+
var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
+ /* Check back with Commons after a day */
var $apiThumbCacheExpiry = 86400;
+ /* Redownload thumbnail files after a month */
+ var $fileCacheExpiry = 2629743;
+ /* Local image directory */
+ var $directory;
+ var $thumbDir;
+
protected $mQueryCache = array();
protected $mFileExists = array();
function __construct( $info ) {
parent::__construct( $info );
- $this->mApiBase = $info['apibase']; // http://commons.wikimedia.org/w/api.php
+ global $wgUploadDirectory;
+
+ // http://commons.wikimedia.org/w/api.php
+ $this->mApiBase = isset( $info['apibase'] ) ? $info['apibase'] : null;
+ $this->directory = isset( $info['directory'] ) ? $info['directory'] : $wgUploadDirectory;
+
if( isset( $info['apiThumbCacheExpiry'] ) ) {
$this->apiThumbCacheExpiry = $info['apiThumbCacheExpiry'];
}
+ if( isset( $info['fileCacheExpiry'] ) ) {
+ $this->fileCacheExpiry = $info['fileCacheExpiry'];
+ }
if( !$this->scriptDirUrl ) {
// hack for description fetches
$this->scriptDirUrl = dirname( $this->mApiBase );
@@ -41,6 +65,11 @@ class ForeignAPIRepo extends FileRepo {
if( $this->canCacheThumbs() && !$this->thumbUrl ) {
$this->thumbUrl = $this->url . '/thumb';
}
+ if ( isset( $info['thumbDir'] ) ) {
+ $this->thumbDir = $info['thumbDir'];
+ } else {
+ $this->thumbDir = "{$this->directory}/thumb";
+ }
}
/**
@@ -89,7 +118,7 @@ class ForeignAPIRepo extends FileRepo {
}
}
- $results = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ),
+ $data = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ),
'prop' => 'imageinfo' ) );
if( isset( $data['query']['pages'] ) ) {
$i = 0;
@@ -98,40 +127,32 @@ class ForeignAPIRepo extends FileRepo {
$i++;
}
}
+ return $results;
}
function getFileProps( $virtualUrl ) {
return false;
}
- protected function queryImage( $query ) {
- $data = $this->fetchImageQuery( $query );
-
- if( isset( $data['query']['pages'] ) ) {
- foreach( $data['query']['pages'] as $pageid => $info ) {
- if( isset( $info['imageinfo'][0] ) ) {
- return $info['imageinfo'][0];
- }
- }
- }
- return false;
- }
-
- protected function fetchImageQuery( $query ) {
+ function fetchImageQuery( $query ) {
global $wgMemc;
- $url = $this->mApiBase .
- '?' .
- wfArrayToCgi(
- array_merge( $query,
- array(
- 'format' => 'json',
- 'action' => 'query' ) ) );
+ $query = array_merge( $query,
+ array(
+ 'format' => 'json',
+ 'action' => 'query',
+ 'redirects' => 'true'
+ ) );
+ if ( $this->mApiBase ) {
+ $url = wfAppendQuery( $this->mApiBase, $query );
+ } else {
+ $url = $this->makeUrl( $query, 'api' );
+ }
if( !isset( $this->mQueryCache[$url] ) ) {
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) );
$data = $wgMemc->get( $key );
if( !$data ) {
- $data = Http::get( $url );
+ $data = self::httpGet( $url );
if ( !$data ) {
return null;
}
@@ -147,81 +168,141 @@ class ForeignAPIRepo extends FileRepo {
return FormatJson::decode( $this->mQueryCache[$url], true );
}
- function getImageInfo( $title, $time = false ) {
- return $this->queryImage( array(
- 'titles' => 'Image:' . $title->getText(),
- 'iiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime',
- 'prop' => 'imageinfo' ) );
+ function getImageInfo( $data ) {
+ if( $data && isset( $data['query']['pages'] ) ) {
+ foreach( $data['query']['pages'] as $info ) {
+ if( isset( $info['imageinfo'][0] ) ) {
+ return $info['imageinfo'][0];
+ }
+ }
+ }
+ return false;
}
function findBySha1( $hash ) {
$results = $this->fetchImageQuery( array(
'aisha1base36' => $hash,
- 'aiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime',
+ 'aiprop' => ForeignAPIFile::getProps(),
'list' => 'allimages', ) );
$ret = array();
if ( isset( $results['query']['allimages'] ) ) {
foreach ( $results['query']['allimages'] as $img ) {
+ // 1.14 was broken, doesn't return name attribute
+ if( !isset( $img['name'] ) ) {
+ continue;
+ }
$ret[] = new ForeignAPIFile( Title::makeTitle( NS_FILE, $img['name'] ), $this, $img );
}
}
return $ret;
}
- function getThumbUrl( $name, $width=-1, $height=-1 ) {
- $info = $this->queryImage( array(
- 'titles' => 'Image:' . $name,
- 'iiprop' => 'url',
+ function getThumbUrl( $name, $width=-1, $height=-1, &$result=NULL ) {
+ $data = $this->fetchImageQuery( array(
+ 'titles' => 'File:' . $name,
+ 'iiprop' => 'url|timestamp',
'iiurlwidth' => $width,
'iiurlheight' => $height,
'prop' => 'imageinfo' ) );
- if( $info && $info['thumburl'] ) {
+ $info = $this->getImageInfo( $data );
+
+ if( $data && $info && isset( $info['thumburl'] ) ) {
wfDebug( __METHOD__ . " got remote thumb " . $info['thumburl'] . "\n" );
+ $result = $info;
return $info['thumburl'];
} else {
return false;
}
}
+ /*
+ * Return the imageurl from cache if possible
+ *
+ * If the url has been requested today, get it from cache
+ * Otherwise retrieve remote thumb url, check for local file.
+ *
+ * @param $name String is a dbkey form of a title
+ * @param $width
+ * @param $height
+ */
function getThumbUrlFromCache( $name, $width, $height ) {
- global $wgMemc, $wgUploadPath, $wgServer, $wgUploadDirectory;
+ global $wgMemc;
if ( !$this->canCacheThumbs() ) {
return $this->getThumbUrl( $name, $width, $height );
}
-
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $name );
- if ( $thumbUrl = $wgMemc->get($key) ) {
- wfDebug("Got thumb from local cache. $thumbUrl \n");
- return $thumbUrl;
- }
- else {
- $foreignUrl = $this->getThumbUrl( $name, $width, $height );
- if( !$foreignUrl ) {
- wfDebug( __METHOD__ . " Could not find thumburl\n" );
- return false;
- }
- $thumb = Http::get( $foreignUrl );
- if( !$thumb ) {
- wfDebug( __METHOD__ . " Could not download thumb\n" );
- return false;
+ $sizekey = "$width:$height";
+
+ /* Get the array of urls that we already know */
+ $knownThumbUrls = $wgMemc->get($key);
+ if( !$knownThumbUrls ) {
+ /* No knownThumbUrls for this file */
+ $knownThumbUrls = array();
+ } else {
+ if( isset( $knownThumbUrls[$sizekey] ) ) {
+ wfDebug("Got thumburl from local cache. {$knownThumbUrls[$sizekey]} \n");
+ return $knownThumbUrls[$sizekey];
}
- // We need the same filename as the remote one :)
- $fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
- $path = 'thumb/' . $this->getHashPath( $name ) . $name . "/";
- if ( !is_dir($wgUploadDirectory . '/' . $path) ) {
- wfMkdirParents($wgUploadDirectory . '/' . $path);
+ /* This size is not yet known */
+ }
+
+ $metadata = null;
+ $foreignUrl = $this->getThumbUrl( $name, $width, $height, $metadata );
+
+ if( !$foreignUrl ) {
+ wfDebug( __METHOD__ . " Could not find thumburl\n" );
+ return false;
+ }
+
+ // We need the same filename as the remote one :)
+ $fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
+ if( !$this->validateFilename( $fileName ) ) {
+ wfDebug( __METHOD__ . " The deduced filename $fileName is not safe\n" );
+ return false;
+ }
+ $localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name;
+ $localFilename = $localPath . "/" . $fileName;
+ $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName );
+
+ if( file_exists( $localFilename ) && isset( $metadata['timestamp'] ) ) {
+ wfDebug( __METHOD__ . " Thumbnail was already downloaded before\n" );
+ $modified = filemtime( $localFilename );
+ $remoteModified = strtotime( $metadata['timestamp'] );
+ $current = time();
+ $diff = abs( $modified - $current );
+ if( $remoteModified < $modified && $diff < $this->fileCacheExpiry ) {
+ /* Use our current and already downloaded thumbnail */
+ $knownThumbUrls["$width:$height"] = $localUrl;
+ $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
+ return $localUrl;
}
- $localUrl = $wgServer . $wgUploadPath . '/' . $path . $fileName;
- # FIXME: Delete old thumbs that aren't being used. Maintenance script?
- if( !file_put_contents($wgUploadDirectory . '/' . $path . $fileName, $thumb ) ) {
- wfDebug( __METHOD__ . " could not write to thumb path\n" );
+ /* There is a new Commons file, or existing thumbnail older than a month */
+ }
+ $thumb = self::httpGet( $foreignUrl );
+ if( !$thumb ) {
+ wfDebug( __METHOD__ . " Could not download thumb\n" );
+ return false;
+ }
+ if ( !is_dir($localPath) ) {
+ if( !wfMkdirParents($localPath) ) {
+ wfDebug( __METHOD__ . " could not create directory $localPath for thumb\n" );
return $foreignUrl;
}
- $wgMemc->set( $key, $localUrl, $this->apiThumbCacheExpiry );
- wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" );
- return $localUrl;
}
+
+ # FIXME: Delete old thumbs that aren't being used. Maintenance script?
+ wfSuppressWarnings();
+ if( !file_put_contents( $localFilename, $thumb ) ) {
+ wfRestoreWarnings();
+ wfDebug( __METHOD__ . " could not write to thumb path\n" );
+ return $foreignUrl;
+ }
+ wfRestoreWarnings();
+ $knownThumbUrls[$sizekey] = $localUrl;
+ $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
+ wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" );
+ return $localUrl;
}
/**
@@ -239,10 +320,57 @@ class ForeignAPIRepo extends FileRepo {
}
/**
+ * Get the local directory corresponding to one of the three basic zones
+ */
+ function getZonePath( $zone ) {
+ switch ( $zone ) {
+ case 'public':
+ return $this->directory;
+ case 'thumb':
+ return $this->thumbDir;
+ default:
+ return false;
+ }
+ }
+
+ /**
* Are we locally caching the thumbnails?
* @return bool
*/
public function canCacheThumbs() {
return ( $this->apiThumbCacheExpiry > 0 );
}
+
+ /**
+ * The user agent the ForeignAPIRepo will use.
+ */
+ public static function getUserAgent() {
+ return Http::userAgent() . " ForeignAPIRepo/" . self::VERSION;
+ }
+
+ /**
+ * Like a Http:get request, but with custom User-Agent.
+ * @see Http:get
+ */
+ public static function httpGet( $url, $timeout = 'default', $options = array() ) {
+ $options['timeout'] = $timeout;
+ /* Http::get */
+ $url = wfExpandUrl( $url );
+ wfDebug( "ForeignAPIRepo: HTTP GET: $url\n" );
+ $options['method'] = "GET";
+
+ if ( !isset( $options['timeout'] ) ) {
+ $options['timeout'] = 'default';
+ }
+
+ $req = MWHttpRequest::factory( $url, $options );
+ $req->setUserAgent( ForeignAPIRepo::getUserAgent() );
+ $status = $req->execute();
+
+ if ( $status->isOK() ) {
+ return $req->getContent();
+ } else {
+ return false;
+ }
+ }
}
diff --git a/includes/filerepo/ForeignDBFile.php b/includes/filerepo/ForeignDBFile.php
index a24ff72b..5f04ea73 100644
--- a/includes/filerepo/ForeignDBFile.php
+++ b/includes/filerepo/ForeignDBFile.php
@@ -1,6 +1,14 @@
<?php
+/**
+ * Foreign file with an accessible MediaWiki database
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
+ * Foreign file with an accessible MediaWiki database
+ *
* @ingroup FileRepo
*/
class ForeignDBFile extends LocalFile {
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
index 35c2c4bf..590350b4 100644
--- a/includes/filerepo/ForeignDBRepo.php
+++ b/includes/filerepo/ForeignDBRepo.php
@@ -1,7 +1,14 @@
<?php
+/**
+ * A foreign repository with an accessible MediaWiki database
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A foreign repository with an accessible MediaWiki database
+ *
* @ingroup FileRepo
*/
class ForeignDBRepo extends LocalRepo {
@@ -28,10 +35,16 @@ class ForeignDBRepo extends LocalRepo {
function getMasterDB() {
if ( !isset( $this->dbConn ) ) {
- $class = 'Database' . ucfirst( $this->dbType );
- $this->dbConn = new $class( $this->dbServer, $this->dbUser,
- $this->dbPassword, $this->dbName, false, $this->dbFlags,
- $this->tablePrefix );
+ $this->dbConn = DatabaseBase::newFromType( $this->dbType,
+ array(
+ 'server' => $this->dbServer,
+ 'user' => $this->dbUser,
+ 'password' => $this->dbPassword,
+ 'dbname' => $this->dbName,
+ 'flags' => $this->dbFlags,
+ 'tableprefix' => $this->tablePrefix
+ )
+ );
}
return $this->dbConn;
}
@@ -65,7 +78,7 @@ class ForeignDBRepo extends LocalRepo {
function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
throw new MWException( get_class($this) . ': write operations are not supported' );
}
- function deleteBatch( $fileMap ) {
+ function deleteBatch( $sourceDestPairs ) {
throw new MWException( get_class($this) . ': write operations are not supported' );
}
}
diff --git a/includes/filerepo/ForeignDBViaLBRepo.php b/includes/filerepo/ForeignDBViaLBRepo.php
index 80325752..4c530b51 100644
--- a/includes/filerepo/ForeignDBViaLBRepo.php
+++ b/includes/filerepo/ForeignDBViaLBRepo.php
@@ -1,7 +1,14 @@
<?php
+/**
+ * A foreign repository with a MediaWiki database accessible via the configured LBFactory
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A foreign repository with a MediaWiki database accessible via the configured LBFactory
+ *
* @ingroup FileRepo
*/
class ForeignDBViaLBRepo extends LocalRepo {
diff --git a/includes/filerepo/Image.php b/includes/filerepo/Image.php
index 08ce219a..59a07ef9 100644
--- a/includes/filerepo/Image.php
+++ b/includes/filerepo/Image.php
@@ -1,8 +1,14 @@
<?php
+/**
+ * Backward compatibility code for MW < 1.11
+ *
+ * @file
+ */
/**
* Backwards compatibility class
- * @deprecated
+ *
+ * @deprecated. Will be removed in 1.18!
* @ingroup FileRepo
*/
class Image extends LocalFile {
@@ -17,7 +23,7 @@ class Image extends LocalFile {
* Do not use in core code.
* @deprecated
*/
- static function newFromTitle( $title, $time = false ) {
+ static function newFromTitle( $title, $repo, $time = null ) {
wfDeprecated( __METHOD__ );
$img = wfFindFile( $title, array( 'time' => $time ) );
if ( !$img ) {
@@ -30,7 +36,7 @@ class Image extends LocalFile {
* Wrapper for wfFindFile(), for backwards-compatibility only.
* Do not use in core code.
*
- * @param string $name name of the image, used to create a title object using Title::makeTitleSafe
+ * @param $name String: name of the image, used to create a title object using Title::makeTitleSafe
* @return image object or null if invalid title
* @deprecated
*/
@@ -55,8 +61,8 @@ class Image extends LocalFile {
* Note that fromSharedDirectory will only use the shared path for files
* that actually exist there now, and will return local paths otherwise.
*
- * @param string $name Name of the image, without the leading "Image:"
- * @param boolean $fromSharedDirectory Should this be in $wgSharedUploadPath?
+ * @param $name String: name of the image, without the leading "Image:"
+ * @param $fromSharedDirectory Boolean: Should this be in $wgSharedUploadPath?
* @return string URL of $name image
* @deprecated
*/
diff --git a/includes/filerepo/LocalFile.php b/includes/filerepo/LocalFile.php
index b6b4bfed..5489ecb2 100644
--- a/includes/filerepo/LocalFile.php
+++ b/includes/filerepo/LocalFile.php
@@ -1,5 +1,9 @@
<?php
/**
+ * Local file in the wiki's own database
+ *
+ * @file
+ * @ingroup FileRepo
*/
/**
@@ -28,9 +32,10 @@ class LocalFile extends File {
/**#@+
* @private
*/
- var $fileExists, # does the file file exist on disk? (loadFromXxx)
- $historyLine, # Number of line to return by nextHistoryLine() (constructor)
- $historyRes, # result of the query for the file's history (nextHistoryLine)
+ var
+ $fileExists, # does the file file exist on disk? (loadFromXxx)
+ $historyLine, # Number of line to return by nextHistoryLine() (constructor)
+ $historyRes, # result of the query for the file's history (nextHistoryLine)
$width, # \
$height, # |
$bits, # --- returned by getimagesize (loadFromXxx)
@@ -49,7 +54,7 @@ class LocalFile extends File {
$upgraded, # Whether the row was upgraded on load
$locked, # True if the image row is locked
$missing, # True if file is not present in file system. Not to be cached in memcached
- $deleted; # Bitfield akin to rev_deleted
+ $deleted; # Bitfield akin to rev_deleted
/**#@-*/
@@ -71,29 +76,31 @@ class LocalFile extends File {
$title = Title::makeTitle( NS_FILE, $row->img_name );
$file = new self( $title, $repo );
$file->loadFromRow( $row );
+
return $file;
}
-
+
/**
* Create a LocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
*/
static function newFromKey( $sha1, $repo, $timestamp = false ) {
- # Polymorphic function name to distinguish foreign and local fetches
- $fname = get_class( $this ) . '::' . __FUNCTION__;
-
$conds = array( 'img_sha1' => $sha1 );
- if( $timestamp ) {
+
+ if ( $timestamp ) {
$conds['img_timestamp'] = $timestamp;
}
- $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ), $conds, $fname );
- if( $row ) {
+
+ $dbr = $repo->getSlaveDB();
+ $row = $dbr->selectRow( 'image', self::selectFields(), $conds, __METHOD__ );
+
+ if ( $row ) {
return self::newFromRow( $row, $repo );
} else {
return false;
}
}
-
+
/**
* Fields in the image table
*/
@@ -121,10 +128,12 @@ class LocalFile extends File {
* Do not call this except from inside a repo class.
*/
function __construct( $title, $repo ) {
- if( !is_object( $title ) ) {
+ if ( !is_object( $title ) ) {
throw new MWException( __CLASS__ . ' constructor given bogus title.' );
}
+
parent::__construct( $title, $repo );
+
$this->metadata = '';
$this->historyLine = 0;
$this->historyRes = null;
@@ -132,11 +141,12 @@ class LocalFile extends File {
}
/**
- * Get the memcached key for the main data for this file, or false if
+ * Get the memcached key for the main data for this file, or false if
* there is no access to the shared cache.
*/
function getCacheKey() {
$hashedName = md5( $this->getName() );
+
return $this->repo->getSharedCacheKey( 'file', $hashedName );
}
@@ -145,13 +155,16 @@ class LocalFile extends File {
*/
function loadFromCache() {
global $wgMemc;
+
wfProfileIn( __METHOD__ );
$this->dataLoaded = false;
$key = $this->getCacheKey();
+
if ( !$key ) {
wfProfileOut( __METHOD__ );
return false;
}
+
$cachedValues = $wgMemc->get( $key );
// Check if the key existed and belongs to this version of MediaWiki
@@ -163,6 +176,7 @@ class LocalFile extends File {
}
$this->dataLoaded = true;
}
+
if ( $this->dataLoaded ) {
wfIncrStats( 'image_cache_hit' );
} else {
@@ -178,14 +192,18 @@ class LocalFile extends File {
*/
function saveToCache() {
global $wgMemc;
+
$this->load();
$key = $this->getCacheKey();
+
if ( !$key ) {
return;
}
+
$fields = $this->getCacheFields( '' );
$cache = array( 'version' => MW_FILE_VERSION );
$cache['fileExists'] = $this->fileExists;
+
if ( $this->fileExists ) {
foreach ( $fields as $field ) {
$cache[$field] = $this->$field;
@@ -206,9 +224,11 @@ class LocalFile extends File {
static $fields = array( 'size', 'width', 'height', 'bits', 'media_type',
'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user', 'user_text', 'description' );
static $results = array();
+
if ( $prefix == '' ) {
return $fields;
}
+
if ( !isset( $results[$prefix] ) ) {
$prefixedFields = array();
foreach ( $fields as $field ) {
@@ -216,6 +236,7 @@ class LocalFile extends File {
}
$results[$prefix] = $prefixedFields;
}
+
return $results[$prefix];
}
@@ -234,6 +255,7 @@ class LocalFile extends File {
$row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
array( 'img_name' => $this->getName() ), $fname );
+
if ( $row ) {
$this->loadFromRow( $row );
} else {
@@ -250,15 +272,20 @@ class LocalFile extends File {
function decodeRow( $row, $prefix = 'img_' ) {
$array = (array)$row;
$prefixLength = strlen( $prefix );
+
// Sanity check prefix once
if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
}
+
$decoded = array();
+
foreach ( $array as $name => $value ) {
$decoded[substr( $name, $prefixLength )] = $value;
}
+
$decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
+
if ( empty( $decoded['major_mime'] ) ) {
$decoded['mime'] = 'unknown/unknown';
} else {
@@ -267,8 +294,10 @@ class LocalFile extends File {
}
$decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
}
+
# Trim zero padding from char/binary field
$decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
+
return $decoded;
}
@@ -278,9 +307,11 @@ class LocalFile extends File {
function loadFromRow( $row, $prefix = 'img_' ) {
$this->dataLoaded = true;
$array = $this->decodeRow( $row, $prefix );
+
foreach ( $array as $name => $value ) {
$this->$name = $value;
}
+
$this->fileExists = true;
$this->maybeUpgradeRow();
}
@@ -305,6 +336,7 @@ class LocalFile extends File {
if ( wfReadOnly() ) {
return;
}
+
if ( is_null( $this->media_type ) ||
$this->mime == 'image/svg'
) {
@@ -337,6 +369,7 @@ class LocalFile extends File {
wfProfileOut( __METHOD__ );
return;
}
+
$dbw = $this->repo->getMasterDB();
list( $major, $minor ) = self::splitMime( $this->mime );
@@ -359,6 +392,7 @@ class LocalFile extends File {
), array( 'img_name' => $this->getName() ),
__METHOD__
);
+
$this->saveToCache();
wfProfileOut( __METHOD__ );
}
@@ -374,15 +408,18 @@ class LocalFile extends File {
$this->dataLoaded = true;
$fields = $this->getCacheFields( '' );
$fields[] = 'fileExists';
+
foreach ( $fields as $field ) {
if ( isset( $info[$field] ) ) {
$this->$field = $info[$field];
}
}
+
// Fix up mime fields
if ( isset( $info['major_mime'] ) ) {
$this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
} elseif ( isset( $info['mime'] ) ) {
+ $this->mime = $info['mime'];
list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
}
}
@@ -396,7 +433,7 @@ class LocalFile extends File {
/** isVisible inhereted */
function isMissing() {
- if( $this->missing === null ) {
+ if ( $this->missing === null ) {
list( $fileExists ) = $this->repo->fileExistsBatch( array( $this->getVirtualUrl() ), FileRepo::FILES_ONLY );
$this->missing = !$fileExists;
}
@@ -410,6 +447,7 @@ class LocalFile extends File {
*/
public function getWidth( $page = 1 ) {
$this->load();
+
if ( $this->isMultipage() ) {
$dim = $this->getHandler()->getPageDimensions( $this, $page );
if ( $dim ) {
@@ -429,6 +467,7 @@ class LocalFile extends File {
*/
public function getHeight( $page = 1 ) {
$this->load();
+
if ( $this->isMultipage() ) {
$dim = $this->getHandler()->getPageDimensions( $this, $page );
if ( $dim ) {
@@ -448,9 +487,10 @@ class LocalFile extends File {
*/
function getUser( $type = 'text' ) {
$this->load();
- if( $type == 'text' ) {
+
+ if ( $type == 'text' ) {
return $this->user_text;
- } elseif( $type == 'id' ) {
+ } elseif ( $type == 'id' ) {
return $this->user;
}
}
@@ -521,6 +561,7 @@ class LocalFile extends File {
function migrateThumbFile( $thumbName ) {
$thumbDir = $this->getThumbPath();
$thumbPath = "$thumbDir/$thumbName";
+
if ( is_dir( $thumbPath ) ) {
// Directory where file should be
// This happened occasionally due to broken migration code in 1.5
@@ -535,6 +576,7 @@ class LocalFile extends File {
// Doesn't exist anymore
clearstatcache();
}
+
if ( is_file( $thumbDir ) ) {
// File where directory should be
unlink( $thumbDir );
@@ -552,6 +594,7 @@ class LocalFile extends File {
*/
function getThumbnails() {
$this->load();
+
$files = array();
$dir = $this->getThumbPath();
@@ -560,10 +603,11 @@ class LocalFile extends File {
if ( $handle ) {
while ( false !== ( $file = readdir( $handle ) ) ) {
- if ( $file{0} != '.' ) {
+ if ( $file { 0 } != '.' ) {
$files[] = $file;
}
}
+
closedir( $handle );
}
}
@@ -585,8 +629,10 @@ class LocalFile extends File {
*/
function purgeHistory() {
global $wgMemc;
+
$hashedName = md5( $this->getName() );
$oldKey = $this->repo->getSharedCacheKey( 'oldfile', $hashedName );
+
if ( $oldKey ) {
$wgMemc->delete( $oldKey );
}
@@ -611,10 +657,12 @@ class LocalFile extends File {
*/
function purgeThumbnails() {
global $wgUseSquid;
+
// Delete thumbnails
$files = $this->getThumbnails();
$dir = $this->getThumbPath();
$urls = array();
+
foreach ( $files as $file ) {
# Check that the base file name is part of the thumb name
# This is a basic sanity check to avoid erasing unrelated directories
@@ -641,31 +689,42 @@ class LocalFile extends File {
$conds = $opts = $join_conds = array();
$eq = $inc ? '=' : '';
$conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
- if( $start ) {
+
+ if ( $start ) {
$conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
}
- if( $end ) {
+
+ if ( $end ) {
$conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) );
}
- if( $limit ) {
+
+ if ( $limit ) {
$opts['LIMIT'] = $limit;
}
+
// Search backwards for time > x queries
$order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
$opts['ORDER BY'] = "oi_timestamp $order";
$opts['USE INDEX'] = array( 'oldimage' => 'oi_name_timestamp' );
- wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
+ wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
&$conds, &$opts, &$join_conds ) );
$res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
$r = array();
- while( $row = $dbr->fetchObject( $res ) ) {
- $r[] = OldLocalFile::newFromRow( $row, $this->repo );
+
+ foreach ( $res as $row ) {
+ if ( $this->repo->oldFileFromRowFactory ) {
+ $r[] = call_user_func( $this->repo->oldFileFromRowFactory, $row, $this->repo );
+ } else {
+ $r[] = OldLocalFile::newFromRow( $row, $this->repo );
+ }
}
- if( $order == 'ASC' ) {
+
+ if ( $order == 'ASC' ) {
$r = array_reverse( $r ); // make sure it ends up descending
}
+
return $r;
}
@@ -694,13 +753,12 @@ class LocalFile extends File {
array( 'img_name' => $this->title->getDBkey() ),
$fname
);
+
if ( 0 == $dbr->numRows( $this->historyRes ) ) {
- $dbr->freeResult( $this->historyRes );
$this->historyRes = null;
return false;
}
} elseif ( $this->historyLine == 1 ) {
- $dbr->freeResult( $this->historyRes );
$this->historyRes = $dbr->select( 'oldimage', '*',
array( 'oi_name' => $this->title->getDBkey() ),
$fname,
@@ -717,8 +775,8 @@ class LocalFile extends File {
*/
public function resetHistory() {
$this->historyLine = 0;
+
if ( !is_null( $this->historyRes ) ) {
- $this->repo->getSlaveDB()->freeResult( $this->historyRes );
$this->historyRes = null;
}
}
@@ -739,14 +797,16 @@ class LocalFile extends File {
/**
* Upload a file and record it in the DB
- * @param string $srcPath Source path or virtual URL
- * @param string $comment Upload description
- * @param string $pageText Text to use for the new description page, if a new description page is created
- * @param integer $flags Flags for publish()
- * @param array $props File properties, if known. This can be used to reduce the
- * upload time when uploading virtual URLs for which the file info
- * is already known
- * @param string $timestamp Timestamp for img_timestamp, or false to use the current time
+ * @param $srcPath String: source path or virtual URL
+ * @param $comment String: upload description
+ * @param $pageText String: text to use for the new description page,
+ * if a new description page is created
+ * @param $flags Integer: flags for publish()
+ * @param $props Array: File properties, if known. This can be used to reduce the
+ * upload time when uploading virtual URLs for which the file info
+ * is already known
+ * @param $timestamp String: timestamp for img_timestamp, or false to use the current time
+ * @param $user Mixed: User object or null to use $wgUser
*
* @return FileRepoStatus object. On success, the value member contains the
* archive name, or an empty string if it was a new file.
@@ -754,12 +814,15 @@ class LocalFile extends File {
function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) {
$this->lock();
$status = $this->publish( $srcPath, $flags );
+
if ( $status->ok ) {
if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) {
$status->fatal( 'filenotfound', $srcPath );
}
}
+
$this->unlock();
+
return $status;
}
@@ -771,9 +834,11 @@ class LocalFile extends File {
$watch = false, $timestamp = false )
{
$pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
+
if ( !$this->recordUpload2( $oldver, $desc, $pageText ) ) {
return false;
}
+
if ( $watch ) {
global $wgUser;
$wgUser->addWatch( $this->getTitle() );
@@ -785,11 +850,12 @@ class LocalFile extends File {
/**
* Record a file upload in the upload log and the image table
*/
- function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null )
- {
- if( is_null( $user ) ) {
+ function recordUpload2(
+ $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null
+ ) {
+ if ( is_null( $user ) ) {
global $wgUser;
- $user = $wgUser;
+ $user = $wgUser;
}
$dbw = $this->repo->getMasterDB();
@@ -798,27 +864,30 @@ class LocalFile extends File {
if ( !$props ) {
$props = $this->repo->getFileProps( $this->getVirtualUrl() );
}
+
+ if ( $timestamp === false ) {
+ $timestamp = $dbw->timestamp();
+ }
+
$props['description'] = $comment;
$props['user'] = $user->getId();
$props['user_text'] = $user->getName();
- $props['timestamp'] = wfTimestamp( TS_MW );
+ $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
$this->setProps( $props );
- // Delete thumbnails and refresh the metadata cache
+ # Delete thumbnails
$this->purgeThumbnails();
- $this->saveToCache();
+
+ # The file is already on its final location, remove it from the squid cache
SquidUpdate::purge( array( $this->getURL() ) );
- // Fail now if the file isn't there
+ # Fail now if the file isn't there
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": File " . $this->getPath() . " went missing!\n" );
return false;
}
$reupload = false;
- if ( $timestamp === false ) {
- $timestamp = $dbw->timestamp();
- }
# Test to see if the row exists using INSERT IGNORE
# This avoids race conditions by locking the row until the commit, and also
@@ -826,7 +895,7 @@ class LocalFile extends File {
$dbw->insert( 'image',
array(
'img_name' => $this->getName(),
- 'img_size'=> $this->size,
+ 'img_size' => $this->size,
'img_width' => intval( $this->width ),
'img_height' => intval( $this->height ),
'img_bits' => $this->bits,
@@ -844,7 +913,7 @@ class LocalFile extends File {
'IGNORE'
);
- if( $dbw->affectedRows() == 0 ) {
+ if ( $dbw->affectedRows() == 0 ) {
$reupload = true;
# Collision, this is an update of a file
@@ -905,13 +974,17 @@ class LocalFile extends File {
$action = $reupload ? 'overwrite' : 'upload';
$log->addEntry( $action, $descTitle, $comment, array(), $user );
- if( $descTitle->exists() ) {
+ if ( $descTitle->exists() ) {
# Create a null revision
$latest = $descTitle->getLatestRevID();
- $nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(),
- $log->getRcComment(), false );
+ $nullRevision = Revision::newNullRevision(
+ $dbw,
+ $descTitle->getArticleId(),
+ $log->getRcComment(),
+ false
+ );
$nullRevision->insertOn( $dbw );
-
+
wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $user ) );
$article->updateRevisionOn( $dbw, $nullRevision );
@@ -919,24 +992,33 @@ class LocalFile extends File {
$descTitle->invalidateCache();
$descTitle->purgeSquid();
} else {
- // New file; create the description page.
- // There's already a log entry, so don't make a second RC entry
+ # New file; create the description page.
+ # There's already a log entry, so don't make a second RC entry
+ # Squid and file cache for the description page are purged by doEdit.
$article->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC );
}
- # Hooks, hooks, the magic of hooks...
- wfRunHooks( 'FileUpload', array( $this ) );
-
# Commit the transaction now, in case something goes wrong later
# The most important thing is that files don't get lost, especially archives
$dbw->commit();
+ # Save to cache and purge the squid
+ # We shall not saveToCache before the commit since otherwise
+ # in case of a rollback there is an usable file from memcached
+ # which in fact doesn't really exist (bug 24978)
+ $this->saveToCache();
+
+ # Hooks, hooks, the magic of hooks...
+ wfRunHooks( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) );
+
# Invalidate cache for all pages using this file
$update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
$update->doUpdate();
+
# Invalidate cache for all pages that redirects on this page
$redirs = $this->getTitle()->getRedirectsHere();
- foreach( $redirs as $redir ) {
+
+ foreach ( $redirs as $redir ) {
$update = new HTMLCacheUpdate( $redir, 'imagelinks' );
$update->doUpdate();
}
@@ -946,15 +1028,14 @@ class LocalFile extends File {
/**
* Move or copy a file to its public location. If a file exists at the
- * destination, move it to an archive. Returns the archive name on success
- * or an empty string if it was a new file, and a wikitext-formatted
- * WikiError object on failure.
+ * destination, move it to an archive. Returns a FileRepoStatus object with
+ * the archive name in the "value" member on success.
*
* The archive name should be passed through to recordUpload for database
* registration.
*
- * @param string $sourcePath Local filesystem path to the source image
- * @param integer $flags A bitwise combination of:
+ * @param $srcPath String: local filesystem path to the source image
+ * @param $flags Integer: a bitwise combination of:
* File::DELETE_SOURCE Delete the source file, i.e. move
* rather than copy
* @return FileRepoStatus object. On success, the value member contains the
@@ -962,17 +1043,21 @@ class LocalFile extends File {
*/
function publish( $srcPath, $flags = 0 ) {
$this->lock();
+
$dstRel = $this->getRel();
- $archiveName = gmdate( 'YmdHis' ) . '!'. $this->getName();
+ $archiveName = gmdate( 'YmdHis' ) . '!' . $this->getName();
$archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
$flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
$status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags );
+
if ( $status->value == 'new' ) {
$status->value = '';
} else {
$status->value = $archiveName;
}
+
$this->unlock();
+
return $status;
}
@@ -996,12 +1081,14 @@ class LocalFile extends File {
function move( $target ) {
wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
$this->lock();
+
$batch = new LocalFileMoveBatch( $this, $target );
$batch->addCurrent();
$batch->addOlds();
$status = $batch->execute();
wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
+
$this->purgeEverything();
$this->unlock();
@@ -1014,7 +1101,7 @@ class LocalFile extends File {
// Purge the new image
$this->purgeEverything();
}
-
+
return $status;
}
@@ -1032,6 +1119,7 @@ class LocalFile extends File {
*/
function delete( $reason, $suppress = false ) {
$this->lock();
+
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addCurrent();
@@ -1040,7 +1128,7 @@ class LocalFile extends File {
$result = $dbw->select( 'oldimage',
array( 'oi_archive_name' ),
array( 'oi_name' => $this->getName() ) );
- while ( $row = $dbw->fetchObject( $result ) ) {
+ foreach ( $result as $row ) {
$batch->addOld( $row->oi_archive_name );
}
$status = $batch->execute();
@@ -1053,6 +1141,7 @@ class LocalFile extends File {
}
$this->unlock();
+
return $status;
}
@@ -1064,21 +1153,26 @@ class LocalFile extends File {
*
* Cache purging is done; logging is caller's responsibility.
*
- * @param $reason
- * @param $suppress
+ * @param $archiveName String
+ * @param $reason String
+ * @param $suppress Boolean
* @throws MWException or FSException on database or file store failure
* @return FileRepoStatus object.
*/
- function deleteOld( $archiveName, $reason, $suppress=false ) {
+ function deleteOld( $archiveName, $reason, $suppress = false ) {
$this->lock();
+
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addOld( $archiveName );
$status = $batch->execute();
+
$this->unlock();
+
if ( $status->ok ) {
$this->purgeDescription();
$this->purgeHistory();
}
+
return $status;
}
@@ -1090,17 +1184,20 @@ class LocalFile extends File {
*
* @param $versions set of record ids of deleted items to restore,
* or empty to restore all revisions.
- * @param $unuppress
+ * @param $unsuppress Boolean
* @return FileRepoStatus
*/
function restore( $versions = array(), $unsuppress = false ) {
$batch = new LocalFileRestoreBatch( $this, $unsuppress );
+
if ( !$versions ) {
$batch->addAll();
} else {
$batch->addIds( $versions );
}
+
$status = $batch->execute();
+
if ( !$status->ok ) {
return $status;
}
@@ -1109,6 +1206,7 @@ class LocalFile extends File {
$cleanupStatus->successCount = 0;
$cleanupStatus->failCount = 0;
$status->merge( $cleanupStatus );
+
return $status;
}
@@ -1174,10 +1272,12 @@ class LocalFile extends File {
*/
function lock() {
$dbw = $this->repo->getMasterDB();
+
if ( !$this->locked ) {
$dbw->begin();
$this->locked++;
}
+
return $dbw->selectField( 'image', '1', array( 'img_name' => $this->getName() ), __METHOD__ );
}
@@ -1205,7 +1305,7 @@ class LocalFile extends File {
}
} // LocalFile class
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
/**
* Helper class for file deletion
@@ -1240,25 +1340,33 @@ class LocalFileDeleteBatch {
unset( $oldRels['.'] );
$deleteCurrent = true;
}
+
return array( $oldRels, $deleteCurrent );
}
/*protected*/ function getHashes() {
$hashes = array();
list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+
if ( $deleteCurrent ) {
$hashes['.'] = $this->file->getSha1();
}
+
if ( count( $oldRels ) ) {
$dbw = $this->file->repo->getMasterDB();
- $res = $dbw->select( 'oldimage', array( 'oi_archive_name', 'oi_sha1' ),
- 'oi_archive_name IN(' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
- __METHOD__ );
- while ( $row = $dbw->fetchObject( $res ) ) {
+ $res = $dbw->select(
+ 'oldimage',
+ array( 'oi_archive_name', 'oi_sha1' ),
+ 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
+ __METHOD__
+ );
+
+ foreach ( $res as $row ) {
if ( rtrim( $row->oi_sha1, "\0" ) === '' ) {
// Get the hash from the file
$oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
$props = $this->file->repo->getFileProps( $oldUrl );
+
if ( $props['fileExists'] ) {
// Upgrade the oldimage row
$dbw->update( 'oldimage',
@@ -1274,10 +1382,13 @@ class LocalFileDeleteBatch {
}
}
}
+
$missing = array_diff_key( $this->srcRels, $hashes );
+
foreach ( $missing as $name => $rel ) {
$this->status->error( 'filedelete-old-unregistered', $name );
}
+
foreach ( $hashes as $name => $hash ) {
if ( !$hash ) {
$this->status->error( 'filedelete-missing', $this->srcRels[$name] );
@@ -1290,6 +1401,7 @@ class LocalFileDeleteBatch {
function doDBInserts() {
global $wgUser;
+
$dbw = $this->file->repo->getMasterDB();
$encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
$encUserId = $dbw->addQuotes( $wgUser->getId() );
@@ -1377,6 +1489,7 @@ class LocalFileDeleteBatch {
function doDBDeletes() {
$dbw = $this->file->repo->getMasterDB();
list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+
if ( count( $oldRels ) ) {
$dbw->delete( 'oldimage',
array(
@@ -1384,6 +1497,7 @@ class LocalFileDeleteBatch {
'oi_archive_name' => array_keys( $oldRels )
), __METHOD__ );
}
+
if ( $deleteCurrent ) {
$dbw->delete( 'image', array( 'img_name' => $this->file->getName() ), __METHOD__ );
}
@@ -1401,14 +1515,16 @@ class LocalFileDeleteBatch {
$privateFiles = array();
list( $oldRels, $deleteCurrent ) = $this->getOldRels();
$dbw = $this->file->repo->getMasterDB();
- if( !empty( $oldRels ) ) {
+
+ if ( !empty( $oldRels ) ) {
$res = $dbw->select( 'oldimage',
array( 'oi_archive_name' ),
array( 'oi_name' => $this->file->getName(),
- 'oi_archive_name IN (' . $dbw->makeList( array_keys($oldRels) ) . ')',
- $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE ),
+ 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
+ $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
__METHOD__ );
- while( $row = $dbw->fetchObject( $res ) ) {
+
+ foreach ( $res as $row ) {
$privateFiles[$row->oi_archive_name] = 1;
}
}
@@ -1417,6 +1533,7 @@ class LocalFileDeleteBatch {
$this->deletionBatch = array();
$ext = $this->file->getExtension();
$dotExt = $ext === '' ? '' : ".$ext";
+
foreach ( $this->srcRels as $name => $srcRel ) {
// Skip files that have no hash (missing source).
// Keep private files where they are.
@@ -1441,6 +1558,7 @@ class LocalFileDeleteBatch {
// Execute the file deletion batch
$status = $this->file->repo->deleteBatch( $this->deletionBatch );
+
if ( !$status->isGood() ) {
$this->status->merge( $status );
}
@@ -1457,6 +1575,7 @@ class LocalFileDeleteBatch {
// Purge squid
if ( $wgUseSquid ) {
$urls = array();
+
foreach ( $this->srcRels as $srcRel ) {
$urlRel = str_replace( '%2F', '/', rawurlencode( $srcRel ) );
$urls[] = $this->file->repo->getZoneUrl( 'public' ) . '/' . $urlRel;
@@ -1470,6 +1589,7 @@ class LocalFileDeleteBatch {
// Commit and return
$this->file->unlock();
wfProfileOut( __METHOD__ );
+
return $this->status;
}
@@ -1478,19 +1598,25 @@ class LocalFileDeleteBatch {
*/
function removeNonexistentFiles( $batch ) {
$files = $newBatch = array();
- foreach( $batch as $batchItem ) {
+
+ foreach ( $batch as $batchItem ) {
list( $src, $dest ) = $batchItem;
$files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
}
+
$result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
- foreach( $batch as $batchItem )
- if( $result[$batchItem[0]] )
+
+ foreach ( $batch as $batchItem ) {
+ if ( $result[$batchItem[0]] ) {
$newBatch[] = $batchItem;
+ }
+ }
+
return $newBatch;
}
}
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
/**
* Helper class for file undeletion
@@ -1536,6 +1662,7 @@ class LocalFileRestoreBatch {
*/
function execute() {
global $wgLang;
+
if ( !$this->all && !$this->ids ) {
// Do nothing
return $this->file->repo->newGood();
@@ -1548,7 +1675,8 @@ class LocalFileRestoreBatch {
// Fetch all or selected archived revisions for the file,
// sorted from the most recent to the oldest.
$conditions = array( 'fa_name' => $this->file->getName() );
- if( !$this->all ) {
+
+ if ( !$this->all ) {
$conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')';
}
@@ -1565,7 +1693,8 @@ class LocalFileRestoreBatch {
$deleteIds = array();
$first = true;
$archiveNames = array();
- while( $row = $dbw->fetchObject( $result ) ) {
+
+ foreach ( $result as $row ) {
$idsPresent[] = $row->fa_id;
if ( $row->fa_name != $this->file->getName() ) {
@@ -1573,6 +1702,7 @@ class LocalFileRestoreBatch {
$status->failCount++;
continue;
}
+
if ( $row->fa_storage_key == '' ) {
// Revision was missing pre-deletion
$status->error( 'undelete-bad-store-key', $wgLang->timeanddate( $row->fa_timestamp ) );
@@ -1584,12 +1714,13 @@ class LocalFileRestoreBatch {
$deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
$sha1 = substr( $row->fa_storage_key, 0, strcspn( $row->fa_storage_key, '.' ) );
+
# Fix leading zero
if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
$sha1 = substr( $sha1, 1 );
}
- if( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
+ if ( is_null( $row->fa_major_mime ) || $row->fa_major_mime == 'unknown'
|| is_null( $row->fa_minor_mime ) || $row->fa_minor_mime == 'unknown'
|| is_null( $row->fa_media_type ) || $row->fa_media_type == 'UNKNOWN'
|| is_null( $row->fa_metadata ) ) {
@@ -1624,23 +1755,27 @@ class LocalFileRestoreBatch {
'img_timestamp' => $row->fa_timestamp,
'img_sha1' => $sha1
);
+
// The live (current) version cannot be hidden!
- if( !$this->unsuppress && $row->fa_deleted ) {
+ if ( !$this->unsuppress && $row->fa_deleted ) {
$storeBatch[] = array( $deletedUrl, 'public', $destRel );
$this->cleanupBatch[] = $row->fa_storage_key;
}
} else {
$archiveName = $row->fa_archive_name;
- if( $archiveName == '' ) {
+
+ if ( $archiveName == '' ) {
// This was originally a current version; we
// have to devise a new archive name for it.
// Format is <timestamp of archiving>!<name>
$timestamp = wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
+
do {
$archiveName = wfTimestamp( TS_MW, $timestamp ) . '!' . $row->fa_name;
$timestamp++;
} while ( isset( $archiveNames[$archiveName] ) );
}
+
$archiveNames[$archiveName] = true;
$destRel = $this->file->getArchiveRel( $archiveName );
$insertBatch[] = array(
@@ -1663,19 +1798,23 @@ class LocalFileRestoreBatch {
}
$deleteIds[] = $row->fa_id;
- if( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
+
+ if ( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) {
// private files can stay where they are
$status->successCount++;
} else {
$storeBatch[] = array( $deletedUrl, 'public', $destRel );
$this->cleanupBatch[] = $row->fa_storage_key;
}
+
$first = false;
}
+
unset( $result );
// Add a warning to the status object for missing IDs
$missingIds = array_diff( $this->ids, $idsPresent );
+
foreach ( $missingIds as $id ) {
$status->error( 'undelete-missing-filearchive', $id );
}
@@ -1692,6 +1831,7 @@ class LocalFileRestoreBatch {
// Store batch returned a critical error -- this usually means nothing was stored
// Stop now and return an error
$this->file->unlock();
+
return $status;
}
@@ -1704,9 +1844,11 @@ class LocalFileRestoreBatch {
if ( $insertCurrent ) {
$dbw->insert( 'image', $insertCurrent, __METHOD__ );
}
+
if ( $insertBatch ) {
$dbw->insert( 'oldimage', $insertBatch, __METHOD__ );
}
+
if ( $deleteIds ) {
$dbw->delete( 'filearchive',
array( 'fa_id IN (' . $dbw->makeList( $deleteIds ) . ')' ),
@@ -1714,8 +1856,8 @@ class LocalFileRestoreBatch {
}
// If store batch is empty (all files are missing), deletion is to be considered successful
- if( $status->successCount > 0 || !$storeBatch ) {
- if( !$exists ) {
+ if ( $status->successCount > 0 || !$storeBatch ) {
+ if ( !$exists ) {
wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" );
// Update site_stats
@@ -1729,7 +1871,9 @@ class LocalFileRestoreBatch {
$this->file->purgeHistory();
}
}
+
$this->file->unlock();
+
return $status;
}
@@ -1738,12 +1882,17 @@ class LocalFileRestoreBatch {
*/
function removeNonexistentFiles( $triplets ) {
$files = $filteredTriplets = array();
- foreach( $triplets as $file )
+ foreach ( $triplets as $file )
$files[$file[0]] = $file[0];
+
$result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
- foreach( $triplets as $file )
- if( $result[$file[0]] )
+
+ foreach ( $triplets as $file ) {
+ if ( $result[$file[0]] ) {
$filteredTriplets[] = $file;
+ }
+ }
+
return $filteredTriplets;
}
@@ -1753,15 +1902,20 @@ class LocalFileRestoreBatch {
function removeNonexistentFromCleanup( $batch ) {
$files = $newBatch = array();
$repo = $this->file->repo;
- foreach( $batch as $file ) {
+
+ foreach ( $batch as $file ) {
$files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' .
rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
}
$result = $repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
- foreach( $batch as $file )
- if( $result[$file] )
+
+ foreach ( $batch as $file ) {
+ if ( $result[$file] ) {
$newBatch[] = $file;
+ }
+ }
+
return $newBatch;
}
@@ -1773,13 +1927,16 @@ class LocalFileRestoreBatch {
if ( !$this->cleanupBatch ) {
return $this->file->repo->newGood();
}
+
$this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch );
+
$status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
+
return $status;
}
}
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
/**
* Helper class for file movement
@@ -1820,29 +1977,35 @@ class LocalFileMoveBatch {
array( 'oi_name' => $this->oldName ),
__METHOD__
);
- while( $row = $this->db->fetchObject( $result ) ) {
+
+ foreach ( $result as $row ) {
$oldName = $row->oi_archive_name;
$bits = explode( '!', $oldName, 2 );
- if( count( $bits ) != 2 ) {
+
+ if ( count( $bits ) != 2 ) {
wfDebug( "Invalid old file name: $oldName \n" );
continue;
}
+
list( $timestamp, $filename ) = $bits;
- if( $this->oldName != $filename ) {
+
+ if ( $this->oldName != $filename ) {
wfDebug( "Invalid old file name: $oldName \n" );
continue;
}
+
$this->oldCount++;
+
// Do we want to add those to oldCount?
- if( $row->oi_deleted & File::DELETED_FILE ) {
+ if ( $row->oi_deleted & File::DELETED_FILE ) {
continue;
}
+
$this->olds[] = array(
"{$archiveBase}/{$this->oldHash}{$oldName}",
"{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
);
}
- $this->db->freeResult( $result );
}
/**
@@ -1858,19 +2021,23 @@ class LocalFileMoveBatch {
wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
$statusMove = $repo->storeBatch( $triplets, FSRepo::DELETE_SOURCE );
wfDebugLog( 'imagemove', "Moved files for {$this->file->name}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" );
- if( !$statusMove->isOk() ) {
+
+ if ( !$statusMove->isOk() ) {
wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
$this->db->rollback();
}
$status->merge( $statusDb );
$status->merge( $statusMove );
+
return $status;
}
/**
- * Do the database updates and return a new WikiError indicating how many
- * rows where updated.
+ * Do the database updates and return a new FileRepoStatus indicating how
+ * many rows where updated.
+ *
+ * @return FileRepoStatus
*/
function doDBUpdates() {
$repo = $this->file->repo;
@@ -1878,13 +2045,14 @@ class LocalFileMoveBatch {
$dbw = $this->db;
// Update current image
- $dbw->update(
+ $dbw->update(
'image',
array( 'img_name' => $this->newName ),
array( 'img_name' => $this->oldName ),
__METHOD__
);
- if( $dbw->affectedRows() ) {
+
+ if ( $dbw->affectedRows() ) {
$status->successCount++;
} else {
$status->failCount++;
@@ -1895,11 +2063,12 @@ class LocalFileMoveBatch {
'oldimage',
array(
'oi_name' => $this->newName,
- 'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name', $dbw->addQuotes($this->oldName), $dbw->addQuotes($this->newName) ),
+ 'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name', $dbw->addQuotes( $this->oldName ), $dbw->addQuotes( $this->newName ) ),
),
array( 'oi_name' => $this->oldName ),
__METHOD__
);
+
$affected = $dbw->affectedRows();
$total = $this->oldCount;
$status->successCount += $affected;
@@ -1910,34 +2079,42 @@ class LocalFileMoveBatch {
/**
* Generate triplets for FSRepo::storeBatch().
- */
+ */
function getMoveTriplets() {
$moves = array_merge( array( $this->cur ), $this->olds );
$triplets = array(); // The format is: (srcUrl, destZone, destUrl)
- foreach( $moves as $move ) {
+
+ foreach ( $moves as $move ) {
// $move: (oldRelativePath, newRelativePath)
$srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
$triplets[] = array( $srcUrl, 'public', $move[1] );
wfDebugLog( 'imagemove', "Generated move triplet for {$this->file->name}: {$srcUrl} :: public :: {$move[1]}" );
}
+
return $triplets;
}
/**
* Removes non-existent files from move batch.
- */
+ */
function removeNonexistentFiles( $triplets ) {
$files = array();
- foreach( $triplets as $file )
+
+ foreach ( $triplets as $file ) {
$files[$file[0]] = $file[0];
+ }
+
$result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
$filteredTriplets = array();
- foreach( $triplets as $file )
- if( $result[$file[0]] ) {
+
+ foreach ( $triplets as $file ) {
+ if ( $result[$file[0]] ) {
$filteredTriplets[] = $file;
} else {
wfDebugLog( 'imagemove', "File {$file[0]} does not exist" );
}
+ }
+
return $filteredTriplets;
}
}
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index 6c4d21a2..02883c53 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -1,12 +1,22 @@
<?php
/**
+ * Local repository that stores files in the local filesystem and registers them
+ * in the wiki's own database.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
+
+/**
* A repository that stores files in the local filesystem and registers them
* in the wiki's own database. This is the most commonly used repository class.
* @ingroup FileRepo
*/
class LocalRepo extends FSRepo {
var $fileFactory = array( 'LocalFile', 'newFromTitle' );
+ var $fileFactoryKey = array( 'LocalFile', 'newFromKey' );
var $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
+ var $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
var $fileFromRowFactory = array( 'LocalFile', 'newFromRow' );
var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
@@ -71,7 +81,7 @@ class LocalRepo extends FSRepo {
/**
* Checks if there is a redirect named as $title
*
- * @param Title $title Title of image
+ * @param $title Title of file
*/
function checkRedirect( $title ) {
global $wgMemc;
@@ -156,9 +166,11 @@ class LocalRepo extends FSRepo {
);
$result = array();
- while ( $row = $res->fetchObject() )
+ foreach ( $res as $row ) {
$result[] = $this->newFileFromRow( $row );
+ }
$res->free();
+
return $result;
}
@@ -189,8 +201,8 @@ class LocalRepo extends FSRepo {
/**
* Invalidates image redirect cache related to that image
*
- * @param Title $title Title of image
- */
+ * @param $title Title of page
+ */
function invalidateImageRedirect( $title ) {
global $wgMemc;
$memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
diff --git a/includes/filerepo/NullRepo.php b/includes/filerepo/NullRepo.php
index 2bc61bde..d5a1ee03 100644
--- a/includes/filerepo/NullRepo.php
+++ b/includes/filerepo/NullRepo.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * File repository with no files.
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* File repository with no files, for performance testing
diff --git a/includes/filerepo/OldLocalFile.php b/includes/filerepo/OldLocalFile.php
index 35f3f9f2..9efe998f 100644
--- a/includes/filerepo/OldLocalFile.php
+++ b/includes/filerepo/OldLocalFile.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Old file in the in the oldimage table
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* Class to represent a file in the oldimage table
@@ -30,14 +36,12 @@ class OldLocalFile extends LocalFile {
}
static function newFromKey( $sha1, $repo, $timestamp = false ) {
- # Polymorphic function name to distinguish foreign and local fetches
- $fname = get_class( $this ) . '::' . __FUNCTION__;
-
$conds = array( 'oi_sha1' => $sha1 );
if( $timestamp ) {
$conds['oi_timestamp'] = $timestamp;
}
- $row = $dbr->selectRow( 'oldimage', $this->getCacheFields( 'oi_' ), $conds, $fname );
+ $dbr = $repo->getSlaveDB();
+ $row = $dbr->selectRow( 'oldimage', self::selectFields(), $conds, __METHOD__ );
if( $row ) {
return self::newFromRow( $row, $repo );
} else {
@@ -70,10 +74,10 @@ class OldLocalFile extends LocalFile {
}
/**
- * @param Title $title
- * @param FileRepo $repo
- * @param string $time Timestamp or null to load by archive name
- * @param string $archiveName Archive name or null to load by timestamp
+ * @param $title Title
+ * @param $repo FileRepo
+ * @param $time String: timestamp or null to load by archive name
+ * @param $archiveName String: archive name or null to load by timestamp
*/
function __construct( $title, $repo, $time, $archiveName ) {
parent::__construct( $title, $repo );
@@ -135,7 +139,7 @@ class OldLocalFile extends LocalFile {
}
function getUrlRel() {
- return 'archive/' . $this->getHashPath() . urlencode( $this->getArchiveName() );
+ return 'archive/' . $this->getHashPath() . rawurlencode( $this->getArchiveName() );
}
function upgradeRow() {
@@ -172,8 +176,8 @@ class OldLocalFile extends LocalFile {
}
/**
- * int $field one of DELETED_* bitfield constants
- * for file or revision rows
+ * @param $field Integer: one of DELETED_* bitfield constants
+ * for file or revision rows
* @return bool
*/
function isDeleted( $field ) {
@@ -193,7 +197,8 @@ class OldLocalFile extends LocalFile {
/**
* Determine if the current user is allowed to view a particular
* field of this image file, if it's marked as deleted.
- * @param int $field
+ *
+ * @param $field Integer
* @return bool
*/
function userCan( $field ) {
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index 1465400c..b9996941 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -1,14 +1,19 @@
<?php
/**
- * @defgroup FileRepo FileRepo
+ * Prioritized list of file repositories
*
* @file
* @ingroup FileRepo
*/
/**
- * @ingroup FileRepo
+ * @defgroup FileRepo FileRepo
+ */
+
+/**
* Prioritized list of file repositories
+ *
+ * @ingroup FileRepo
*/
class RepoGroup {
var $localRepo, $foreignRepos, $reposInitialised = false;
@@ -48,7 +53,9 @@ class RepoGroup {
/**
* Construct a group of file repositories.
- * @param array $data Array of repository info arrays.
+ *
+ * @param $localInfo Associative array for local repo's info
+ * @param $foreignInfo Array of repository info arrays.
* Each info array is an associative array with the 'class' member
* giving the class name. The entire array is passed to the repository
* constructor as the first parameter.
@@ -62,7 +69,8 @@ class RepoGroup {
/**
* Search repositories for an image.
* You can also use wfFindFile() to do this.
- * @param mixed $title Title object or string
+ *
+ * @param $title Mixed: Title object or string
* @param $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
@@ -92,10 +100,15 @@ class RepoGroup {
}
}
+ if ( $title->getNamespace() != NS_MEDIA && $title->getNamespace() != NS_FILE ) {
+ throw new MWException( __METHOD__ . ' recieved an Title object with incorrect namespace' );
+ }
+
# Check the cache
if ( empty( $options['ignoreRedirect'] )
&& empty( $options['private'] )
- && empty( $options['bypassCache'] ) )
+ && empty( $options['bypassCache'] )
+ && $title->getNamespace() == NS_FILE )
{
$useCache = true;
$time = isset( $options['time'] ) ? $options['time'] : '';
@@ -224,7 +237,7 @@ class RepoGroup {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
- foreach ( $this->foreignRepos as $key => $repo ) {
+ foreach ( $this->foreignRepos as $repo ) {
if ( $repo->name == $name)
return $repo;
}
@@ -243,8 +256,8 @@ class RepoGroup {
* Call a function for each foreign repo, with the repo object as the
* first parameter.
*
- * @param $callback callback The function to call
- * @param $params array Optional additional parameters to pass to the function
+ * @param $callback Callback: the function to call
+ * @param $params Array: optional additional parameters to pass to the function
*/
function forEachForeignRepo( $callback, $params = array() ) {
foreach( $this->foreignRepos as $repo ) {
@@ -258,7 +271,7 @@ class RepoGroup {
/**
* Does the installation have any foreign repos set up?
- * @return bool
+ * @return Boolean
*/
function hasForeignRepos() {
return (bool)$this->foreignRepos;
diff --git a/includes/filerepo/UnregisteredLocalFile.php b/includes/filerepo/UnregisteredLocalFile.php
index 6f63cb0b..990a218c 100644
--- a/includes/filerepo/UnregisteredLocalFile.php
+++ b/includes/filerepo/UnregisteredLocalFile.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * File without associated database record
+ *
+ * @file
+ * @ingroup FileRepo
+ */
/**
* A file object referring to either a standalone local file, or a file in a
@@ -94,7 +100,7 @@ class UnregisteredLocalFile extends File {
function getURL() {
if ( $this->repo ) {
- return $this->repo->getZoneUrl( 'public' ) . '/' . $this->repo->getHashPath( $this->name ) . urlencode( $this->name );
+ return $this->repo->getZoneUrl( 'public' ) . '/' . $this->repo->getHashPath( $this->name ) . rawurlencode( $this->name );
} else {
return false;
}
diff --git a/includes/installer/CliInstaller.php b/includes/installer/CliInstaller.php
new file mode 100644
index 00000000..9e8fb2c5
--- /dev/null
+++ b/includes/installer/CliInstaller.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * Core installer command line interface.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for the core installer command line interface.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class CliInstaller extends Installer {
+
+ private $optionMap = array(
+ 'dbtype' => 'wgDBtype',
+ 'dbserver' => 'wgDBserver',
+ 'dbname' => 'wgDBname',
+ 'dbuser' => 'wgDBuser',
+ 'dbpass' => 'wgDBpassword',
+ 'dbprefix' => 'wgDBprefix',
+ 'dbtableoptions' => 'wgDBTableOptions',
+ 'dbmysql5' => 'wgDBmysql5',
+ 'dbserver' => 'wgDBserver',
+ 'dbport' => 'wgDBport',
+ 'dbname' => 'wgDBname',
+ 'dbuser' => 'wgDBuser',
+ 'dbpass' => 'wgDBpassword',
+ 'dbschema' => 'wgDBmwschema',
+ 'dbpath' => 'wgSQLiteDataDir',
+ 'scriptpath' => 'wgScriptPath',
+ );
+
+ /**
+ * Constructor.
+ *
+ * @param $siteName
+ * @param $admin
+ * @param $option Array
+ */
+ function __construct( $siteName, $admin = null, array $option = array() ) {
+ global $wgContLang;
+
+ parent::__construct();
+
+ foreach ( $this->optionMap as $opt => $global ) {
+ if ( isset( $option[$opt] ) ) {
+ $GLOBALS[$global] = $option[$opt];
+ $this->setVar( $global, $option[$opt] );
+ }
+ }
+
+ if ( isset( $option['lang'] ) ) {
+ global $wgLang, $wgLanguageCode;
+ $this->setVar( '_UserLang', $option['lang'] );
+ $wgContLang = Language::factory( $option['lang'] );
+ $wgLang = Language::factory( $option['lang'] );
+ $wgLanguageCode = $option['lang'];
+ }
+
+ $this->setVar( 'wgSitename', $siteName );
+
+ $metaNS = $wgContLang->ucfirst( str_replace( ' ', '_', $siteName ) );
+ if ( $metaNS == 'MediaWiki' ) {
+ $metaNS = 'Project';
+ }
+ $this->setVar( 'wgMetaNamespace', $metaNS );
+
+ if ( $admin ) {
+ $this->setVar( '_AdminName', $admin );
+ }
+
+ if ( !isset( $option['installdbuser'] ) ) {
+ $this->setVar( '_InstallUser',
+ $this->getVar( 'wgDBuser' ) );
+ $this->setVar( '_InstallPassword',
+ $this->getVar( 'wgDBpassword' ) );
+ } else {
+ $this->setVar( '_InstallUser',
+ $option['installdbuser'] );
+ $this->setVar( '_InstallPassword',
+ $option['installdbpass'] );
+ }
+
+ if ( isset( $option['pass'] ) ) {
+ $this->setVar( '_AdminPassword', $option['pass'] );
+ }
+ }
+
+ /**
+ * Main entry point.
+ */
+ public function execute() {
+ $vars = Installer::getExistingLocalSettings();
+ if( $vars ) {
+ $this->showStatusMessage(
+ Status::newFatal( "config-localsettings-cli-upgrade" )
+ );
+ }
+
+ $this->performInstallation(
+ array( $this, 'startStage' ),
+ array( $this, 'endStage' )
+ );
+ }
+
+ /**
+ * Write LocalSettings.php to a given path
+ *
+ * @param $path String Full path to write LocalSettings.php to
+ */
+ public function writeConfigurationFile( $path ) {
+ $ls = new LocalSettingsGenerator( $this );
+ $ls->writeFile( "$path/LocalSettings.php" );
+ }
+
+ public function startStage( $step ) {
+ $this->showMessage( "config-install-$step" );
+ }
+
+ public function endStage( $step, $status ) {
+ $this->showStatusMessage( $status );
+ $this->showMessage( 'config-install-step-done' );
+ }
+
+ public function showMessage( $msg /*, ... */ ) {
+ echo $this->getMessageText( func_get_args() ) . "\n";
+ flush();
+ }
+
+ public function showError( $msg /*, ... */ ) {
+ echo "***{$this->getMessageText( func_get_args() )}***\n";
+ flush();
+ }
+
+ /**
+ * @return string
+ */
+ protected function getMessageText( $params ) {
+ $msg = array_shift( $params );
+
+ $text = wfMsgExt( $msg, array( 'parseinline' ), $params );
+
+ $text = preg_replace( '/<a href="(.*?)".*?>(.*?)<\/a>/', '$2 &lt;$1&gt;', $text );
+ return html_entity_decode( strip_tags( $text ), ENT_QUOTES );
+ }
+
+ /**
+ * Dummy
+ */
+ public function showHelpBox( $msg /*, ... */ ) {
+ }
+
+ public function showStatusMessage( Status $status ) {
+ $warnings = array_merge( $status->getWarningsArray(),
+ $status->getErrorsArray() );
+
+ if ( count( $warnings ) !== 0 ) {
+ foreach ( $warnings as $w ) {
+ call_user_func_array( array( $this, 'showMessage' ), $w );
+ }
+ }
+
+ if ( !$status->isOk() ) {
+ echo "\n";
+ exit;
+ }
+ }
+}
diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php
new file mode 100644
index 00000000..0da24f8e
--- /dev/null
+++ b/includes/installer/DatabaseInstaller.php
@@ -0,0 +1,580 @@
+<?php
+/**
+ * DBMS-specific installation helper.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Base class for DBMS-specific installation helper classes.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+abstract class DatabaseInstaller {
+
+ /**
+ * The Installer object.
+ *
+ * TODO: naming this parent is confusing, 'installer' would be clearer.
+ *
+ * @var WebInstaller
+ */
+ public $parent;
+
+ /**
+ * The database connection.
+ *
+ * @var DatabaseBase
+ */
+ public $db = null;
+
+ /**
+ * Internal variables for installation.
+ *
+ * @var array
+ */
+ protected $internalDefaults = array();
+
+ /**
+ * Array of MW configuration globals this class uses.
+ *
+ * @var array
+ */
+ protected $globalNames = array();
+
+ /**
+ * Return the internal name, e.g. 'mysql', or 'sqlite'.
+ */
+ public abstract function getName();
+
+ /**
+ * @return true if the client library is compiled in.
+ */
+ public abstract function isCompiled();
+
+ /**
+ * Get HTML for a web form that configures this database. Configuration
+ * at this time should be the minimum needed to connect and test
+ * whether install or upgrade is required.
+ *
+ * If this is called, $this->parent can be assumed to be a WebInstaller.
+ */
+ public abstract function getConnectForm();
+
+ /**
+ * Set variables based on the request array, assuming it was submitted
+ * via the form returned by getConnectForm(). Validate the connection
+ * settings by attempting to connect with them.
+ *
+ * If this is called, $this->parent can be assumed to be a WebInstaller.
+ *
+ * @return Status
+ */
+ public abstract function submitConnectForm();
+
+ /**
+ * Get HTML for a web form that retrieves settings used for installation.
+ * $this->parent can be assumed to be a WebInstaller.
+ * If the DB type has no settings beyond those already configured with
+ * getConnectForm(), this should return false.
+ */
+ public function getSettingsForm() {
+ return false;
+ }
+
+ /**
+ * Set variables based on the request array, assuming it was submitted via
+ * the form return by getSettingsForm().
+ *
+ * @return Status
+ */
+ public function submitSettingsForm() {
+ return Status::newGood();
+ }
+
+ /**
+ * Open a connection to the database using the administrative user/password
+ * currently defined in the session, without any caching. Returns a status
+ * object. On success, the status object will contain a Database object in
+ * its value member.
+ *
+ * @return Status
+ */
+ public abstract function openConnection();
+
+ /**
+ * Create the database and return a Status object indicating success or
+ * failure.
+ *
+ * @return Status
+ */
+ public abstract function setupDatabase();
+
+ /**
+ * Connect to the database using the administrative user/password currently
+ * defined in the session. Returns a status object. On success, the status
+ * object will contain a Database object in its value member.
+ *
+ * This will return a cached connection if one is available.
+ *
+ * @return Status
+ */
+ public function getConnection() {
+ if ( $this->db ) {
+ return Status::newGood( $this->db );
+ }
+
+ $status = $this->openConnection();
+ if ( $status->isOK() ) {
+ $this->db = $status->value;
+ // Enable autocommit
+ $this->db->clearFlag( DBO_TRX );
+ $this->db->commit();
+ }
+ return $status;
+ }
+
+ /**
+ * Create database tables from scratch.
+ *
+ * @return Status
+ */
+ public function createTables() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $this->db->selectDB( $this->getVar( 'wgDBname' ) );
+
+ if( $this->db->tableExists( 'user' ) ) {
+ $status->warning( 'config-install-tables-exist' );
+ $this->enableLB();
+ return $status;
+ }
+
+ $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
+ $this->db->begin( __METHOD__ );
+
+ $error = $this->db->sourceFile( $this->db->getSchema() );
+ if( $error !== true ) {
+ $this->db->reportQueryError( $error, 0, '', __METHOD__ );
+ $this->db->rollback( __METHOD__ );
+ $status->fatal( 'config-install-tables-failed', $error );
+ } else {
+ $this->db->commit( __METHOD__ );
+ }
+ // Resume normal operations
+ if( $status->isOk() ) {
+ $this->enableLB();
+ }
+ return $status;
+ }
+
+ /**
+ * Create the tables for each extension the user enabled
+ * @return Status
+ */
+ public function createExtensionTables() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $updater = DatabaseUpdater::newForDB( $this->db );
+ $extensionUpdates = $updater->getNewExtensions();
+
+ $ourExtensions = array_map( 'strtolower', $this->getVar( '_Extensions' ) );
+
+ foreach( $ourExtensions as $ext ) {
+ if( isset( $extensionUpdates[$ext] ) ) {
+ $this->db->begin( __METHOD__ );
+ $error = $this->db->sourceFile( $extensionUpdates[$ext] );
+ if( $error !== true ) {
+ $this->db->rollback( __METHOD__ );
+ $status->warning( 'config-install-tables-failed', $error );
+ } else {
+ $this->db->commit( __METHOD__ );
+ }
+ }
+ }
+
+ // Now run updates to create tables for old extensions
+ $updater->doUpdates( array( 'extensions' ) );
+
+ return $status;
+ }
+
+ /**
+ * Get the DBMS-specific options for LocalSettings.php generation.
+ *
+ * @return String
+ */
+ public abstract function getLocalSettings();
+
+ /**
+ * Override this to provide DBMS-specific schema variables, to be
+ * substituted into tables.sql and other schema files.
+ */
+ public function getSchemaVars() {
+ return array();
+ }
+
+ /**
+ * Set appropriate schema variables in the current database connection.
+ *
+ * This should be called after any request data has been imported, but before
+ * any write operations to the database.
+ */
+ public function setupSchemaVars() {
+ $status = $this->getConnection();
+ if ( $status->isOK() ) {
+ $status->value->setSchemaVars( $this->getSchemaVars() );
+ } else {
+ throw new MWException( __METHOD__.': unexpected DB connection error' );
+ }
+ }
+
+ /**
+ * Set up LBFactory so that wfGetDB() etc. works.
+ * We set up a special LBFactory instance which returns the current
+ * installer connection.
+ */
+ public function enableLB() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ throw new MWException( __METHOD__.': unexpected DB connection error' );
+ }
+ LBFactory::setInstance( new LBFactory_Single( array(
+ 'connection' => $status->value ) ) );
+ }
+
+ /**
+ * Perform database upgrades
+ *
+ * @return Boolean
+ */
+ public function doUpgrade() {
+ $this->setupSchemaVars();
+ $this->enableLB();
+
+ $ret = true;
+ ob_start( array( $this, 'outputHandler' ) );
+ try {
+ $up = DatabaseUpdater::newForDB( $this->db );
+ $up->doUpdates();
+ } catch ( MWException $e ) {
+ echo "\nAn error occured:\n";
+ echo $e->getText();
+ $ret = false;
+ }
+ ob_end_flush();
+ return $ret;
+ }
+
+ /**
+ * Allow DB installers a chance to make last-minute changes before installation
+ * occurs. This happens before setupDatabase() or createTables() is called, but
+ * long after the constructor. Helpful for things like modifying setup steps :)
+ */
+ public function preInstall() {
+
+ }
+
+ /**
+ * Allow DB installers a chance to make checks before upgrade.
+ */
+ public function preUpgrade() {
+
+ }
+
+ /**
+ * Get an array of MW configuration globals that will be configured by this class.
+ */
+ public function getGlobalNames() {
+ return $this->globalNames;
+ }
+
+ /**
+ * Construct and initialise parent.
+ * This is typically only called from Installer::getDBInstaller()
+ */
+ public function __construct( $parent ) {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Convenience function.
+ * Check if a named extension is present.
+ *
+ * @see wfDl
+ */
+ protected static function checkExtension( $name ) {
+ wfSuppressWarnings();
+ $compiled = wfDl( $name );
+ wfRestoreWarnings();
+ return $compiled;
+ }
+
+ /**
+ * Get the internationalised name for this DBMS.
+ */
+ public function getReadableName() {
+ return wfMsg( 'config-type-' . $this->getName() );
+ }
+
+ /**
+ * Get a name=>value map of MW configuration globals that overrides.
+ * DefaultSettings.php
+ */
+ public function getGlobalDefaults() {
+ return array();
+ }
+
+ /**
+ * Get a name=>value map of internal variables used during installation.
+ */
+ public function getInternalDefaults() {
+ return $this->internalDefaults;
+ }
+
+ /**
+ * Get a variable, taking local defaults into account.
+ */
+ public function getVar( $var, $default = null ) {
+ $defaults = $this->getGlobalDefaults();
+ $internal = $this->getInternalDefaults();
+ if ( isset( $defaults[$var] ) ) {
+ $default = $defaults[$var];
+ } elseif ( isset( $internal[$var] ) ) {
+ $default = $internal[$var];
+ }
+ return $this->parent->getVar( $var, $default );
+ }
+
+ /**
+ * Convenience alias for $this->parent->setVar()
+ */
+ public function setVar( $name, $value ) {
+ $this->parent->setVar( $name, $value );
+ }
+
+ /**
+ * Get a labelled text box to configure a local variable.
+ */
+ public function getTextBox( $var, $label, $attribs = array(), $helpData = "" ) {
+ $name = $this->getName() . '_' . $var;
+ $value = $this->getVar( $var );
+ if ( !isset( $attribs ) ) {
+ $attribs = array();
+ }
+ return $this->parent->getTextBox( array(
+ 'var' => $var,
+ 'label' => $label,
+ 'attribs' => $attribs,
+ 'controlName' => $name,
+ 'value' => $value,
+ 'help' => $helpData
+ ) );
+ }
+
+ /**
+ * Get a labelled password box to configure a local variable.
+ * Implements password hiding.
+ */
+ public function getPasswordBox( $var, $label, $attribs = array(), $helpData = "" ) {
+ $name = $this->getName() . '_' . $var;
+ $value = $this->getVar( $var );
+ if ( !isset( $attribs ) ) {
+ $attribs = array();
+ }
+ return $this->parent->getPasswordBox( array(
+ 'var' => $var,
+ 'label' => $label,
+ 'attribs' => $attribs,
+ 'controlName' => $name,
+ 'value' => $value,
+ 'help' => $helpData
+ ) );
+ }
+
+ /**
+ * Get a labelled checkbox to configure a local boolean variable.
+ */
+ public function getCheckBox( $var, $label, $attribs = array(), $helpData = "" ) {
+ $name = $this->getName() . '_' . $var;
+ $value = $this->getVar( $var );
+ return $this->parent->getCheckBox( array(
+ 'var' => $var,
+ 'label' => $label,
+ 'attribs' => $attribs,
+ 'controlName' => $name,
+ 'value' => $value,
+ 'help' => $helpData
+ ));
+ }
+
+ /**
+ * Get a set of labelled radio buttons.
+ *
+ * @param $params Array:
+ * Parameters are:
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * itemLabelPrefix: The message name prefix for the item labels (required)
+ * values: List of allowed values (required)
+ * itemAttribs Array of attribute arrays, outer key is the value name (optional)
+ *
+ */
+ public function getRadioSet( $params ) {
+ $params['controlName'] = $this->getName() . '_' . $params['var'];
+ $params['value'] = $this->getVar( $params['var'] );
+ return $this->parent->getRadioSet( $params );
+ }
+
+ /**
+ * Convenience function to set variables based on form data.
+ * Assumes that variables containing "password" in the name are (potentially
+ * fake) passwords.
+ * @param $varNames Array
+ */
+ public function setVarsFromRequest( $varNames ) {
+ return $this->parent->setVarsFromRequest( $varNames, $this->getName() . '_' );
+ }
+
+ /**
+ * Determine whether an existing installation of MediaWiki is present in
+ * the configured administrative connection. Returns true if there is
+ * such a wiki, false if the database doesn't exist.
+ *
+ * Traditionally, this is done by testing for the existence of either
+ * the revision table or the cur table.
+ *
+ * @return Boolean
+ */
+ public function needsUpgrade() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return false;
+ }
+
+ if ( !$this->db->selectDB( $this->getVar( 'wgDBname' ) ) ) {
+ return false;
+ }
+ return $this->db->tableExists( 'cur' ) || $this->db->tableExists( 'revision' );
+ }
+
+ /**
+ * Get a standard install-user fieldset.
+ *
+ * @return String
+ */
+ public function getInstallUserBox() {
+ return
+ Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', array(), wfMsg( 'config-db-install-account' ) ) .
+ $this->getTextBox( '_InstallUser', 'config-db-username', array(), $this->parent->getHelpBox( 'config-db-install-username' ) ) .
+ $this->getPasswordBox( '_InstallPassword', 'config-db-password', array(), $this->parent->getHelpBox( 'config-db-install-password' ) ) .
+ Html::closeElement( 'fieldset' );
+ }
+
+ /**
+ * Submit a standard install user fieldset.
+ */
+ public function submitInstallUserBox() {
+ $this->setVarsFromRequest( array( '_InstallUser', '_InstallPassword' ) );
+ return Status::newGood();
+ }
+
+ /**
+ * Get a standard web-user fieldset
+ * @param $noCreateMsg String: Message to display instead of the creation checkbox.
+ * Set this to false to show a creation checkbox.
+ *
+ * @return String
+ */
+ public function getWebUserBox( $noCreateMsg = false ) {
+ $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
+ $s = Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', array(), wfMsg( 'config-db-web-account' ) ) .
+ $this->getCheckBox(
+ '_SameAccount', 'config-db-web-account-same',
+ array( 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' )
+ ) .
+ Html::openElement( 'div', array( 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ) ) .
+ $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
+ $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
+ $this->parent->getHelpBox( 'config-db-web-help' );
+ if ( $noCreateMsg ) {
+ $s .= $this->parent->getWarningBox( wfMsgNoTrans( $noCreateMsg ) );
+ } else {
+ $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
+ }
+ $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
+ return $s;
+ }
+
+ /**
+ * Submit the form from getWebUserBox().
+ *
+ * @return Status
+ */
+ public function submitWebUserBox() {
+ $this->setVarsFromRequest(
+ array( 'wgDBuser', 'wgDBpassword', '_SameAccount', '_CreateDBAccount' )
+ );
+
+ if ( $this->getVar( '_SameAccount' ) ) {
+ $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
+ $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
+ }
+
+ if( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
+ return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
+ }
+
+ return Status::newGood();
+ }
+
+ /**
+ * Common function for databases that don't understand the MySQLish syntax of interwiki.sql.
+ *
+ * @return Status
+ */
+ public function populateInterwikiTable() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $this->db->selectDB( $this->getVar( 'wgDBname' ) );
+
+ if( $this->db->selectRow( 'interwiki', '*', array(), __METHOD__ ) ) {
+ $status->warning( 'config-install-interwiki-exists' );
+ return $status;
+ }
+ global $IP;
+ wfSuppressWarnings();
+ $rows = file( "$IP/maintenance/interwiki.list",
+ FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
+ wfRestoreWarnings();
+ $interwikis = array();
+ if ( !$rows ) {
+ return Status::newFatal( 'config-install-interwiki-list' );
+ }
+ foreach( $rows as $row ) {
+ $row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
+ if ( $row == "" ) continue;
+ $row .= "||";
+ $interwikis[] = array_combine(
+ array( 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ),
+ explode( '|', $row )
+ );
+ }
+ $this->db->insert( 'interwiki', $interwikis, __METHOD__ );
+ return Status::newGood();
+ }
+
+ public function outputHandler( $string ) {
+ return htmlspecialchars( $string );
+ }
+}
diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php
new file mode 100644
index 00000000..79928d1f
--- /dev/null
+++ b/includes/installer/DatabaseUpdater.php
@@ -0,0 +1,574 @@
+<?php
+/**
+ * DBMS-specific updater helper.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+require_once( dirname(__FILE__) . '/../../maintenance/Maintenance.php' );
+
+/*
+ * Class for handling database updates. Roughly based off of updaters.inc, with
+ * a few improvements :)
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+abstract class DatabaseUpdater {
+
+ /**
+ * Array of updates to perform on the database
+ *
+ * @var array
+ */
+ protected $updates = array();
+
+ /**
+ * List of extension-provided database updates
+ * @var array
+ */
+ protected $extensionUpdates = array();
+
+ /**
+ * Used to hold schema files during installation process
+ * @var array
+ */
+ protected $newExtensions = array();
+
+ /**
+ * Handle to the database subclass
+ *
+ * @var DatabaseBase
+ */
+ protected $db;
+
+ protected $shared = false;
+
+ protected $postDatabaseUpdateMaintenance = array(
+ 'DeleteDefaultMessages'
+ );
+
+ /**
+ * Constructor
+ *
+ * @param $db DatabaseBase object to perform updates on
+ * @param $shared bool Whether to perform updates on shared tables
+ * @param $maintenance Maintenance Maintenance object which created us
+ */
+ protected function __construct( DatabaseBase &$db, $shared, Maintenance $maintenance = null ) {
+ $this->db = $db;
+ $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
+ $this->shared = $shared;
+ if ( $maintenance ) {
+ $this->maintenance = $maintenance;
+ } else {
+ $this->maintenance = new FakeMaintenance;
+ }
+ $this->initOldGlobals();
+ $this->loadExtensions();
+ wfRunHooks( 'LoadExtensionSchemaUpdates', array( $this ) );
+ }
+
+ /**
+ * Initialize all of the old globals. One day this should all become
+ * something much nicer
+ */
+ private function initOldGlobals() {
+ global $wgExtNewTables, $wgExtNewFields, $wgExtPGNewFields,
+ $wgExtPGAlteredFields, $wgExtNewIndexes, $wgExtModifiedFields;
+
+ # For extensions only, should be populated via hooks
+ # $wgDBtype should be checked to specifiy the proper file
+ $wgExtNewTables = array(); // table, dir
+ $wgExtNewFields = array(); // table, column, dir
+ $wgExtPGNewFields = array(); // table, column, column attributes; for PostgreSQL
+ $wgExtPGAlteredFields = array(); // table, column, new type, conversion method; for PostgreSQL
+ $wgExtNewIndexes = array(); // table, index, dir
+ $wgExtModifiedFields = array(); // table, index, dir
+ }
+
+ /**
+ * Loads LocalSettings.php, if needed, and initialises everything needed for LoadExtensionSchemaUpdates hook
+ */
+ private function loadExtensions() {
+ if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
+ return; // already loaded
+ }
+ $vars = Installer::getExistingLocalSettings();
+ if ( !$vars ) {
+ return; // no LocalSettings found
+ }
+ if ( !isset( $vars['wgHooks'] ) && !isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
+ return;
+ }
+ global $wgHooks, $wgAutoloadClasses;
+ $wgHooks['LoadExtensionSchemaUpdates'] = $vars['wgHooks']['LoadExtensionSchemaUpdates'];
+ $wgAutoloadClasses = $wgAutoloadClasses + $vars['wgAutoloadClasses'];
+ }
+
+ /**
+ * @throws MWException
+ * @param DatabaseBase $db
+ * @param bool $shared
+ * @param null $maintenance
+ * @return DatabaseUpdater
+ */
+ public static function newForDB( &$db, $shared = false, $maintenance = null ) {
+ $type = $db->getType();
+ if( in_array( $type, Installer::getDBTypes() ) ) {
+ $class = ucfirst( $type ) . 'Updater';
+ return new $class( $db, $shared, $maintenance );
+ } else {
+ throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' );
+ }
+ }
+
+ /**
+ * Get a database connection to run updates
+ *
+ * @return DatabaseBase
+ */
+ public function getDB() {
+ return $this->db;
+ }
+
+ /**
+ * Output some text. If we're running from web, escape the text first.
+ *
+ * @param $str String: Text to output
+ */
+ public function output( $str ) {
+ if ( $this->maintenance->isQuiet() ) {
+ return;
+ }
+ global $wgCommandLineMode;
+ if( !$wgCommandLineMode ) {
+ $str = htmlspecialchars( $str );
+ }
+ echo $str;
+ flush();
+ }
+
+ /**
+ * Add a new update coming from an extension. This should be called by
+ * extensions while executing the LoadExtensionSchemaUpdates hook.
+ *
+ * @param $update Array: the update to run. Format is the following:
+ * first item is the callback function, it also can be a
+ * simple string with the name of a function in this class,
+ * following elements are parameters to the function.
+ * Note that callback functions will receive this object as
+ * first parameter.
+ */
+ public function addExtensionUpdate( Array $update ) {
+ $this->extensionUpdates[] = $update;
+ }
+
+ /**
+ * Convenience wrapper for addExtensionUpdate() when adding a new table (which
+ * is the most common usage of updaters in an extension)
+ * @param $tableName String Name of table to create
+ * @param $sqlPath String Full path to the schema file
+ */
+ public function addExtensionTable( $tableName, $sqlPath ) {
+ $this->extensionUpdates[] = array( 'addTable', $tableName, $sqlPath, true );
+ }
+
+ /**
+ * Add a brand new extension to MediaWiki. Used during the initial install
+ * @param $ext String Name of extension
+ * @param $sqlPath String Full path to the schema file
+ */
+ public function addNewExtension( $ext, $sqlPath ) {
+ $this->newExtensions[ strtolower( $ext ) ] = $sqlPath;
+ }
+
+ /**
+ * Get the list of extensions that registered a schema with our DB type
+ * @return array
+ */
+ public function getNewExtensions() {
+ return $this->newExtensions;
+ }
+
+ /**
+ * Get the list of extension-defined updates
+ *
+ * @return Array
+ */
+ protected function getExtensionUpdates() {
+ return $this->extensionUpdates;
+ }
+
+ public function getPostDatabaseUpdateMaintenance() {
+ return $this->postDatabaseUpdateMaintenance;
+ }
+
+ /**
+ * Do all the updates
+ *
+ * @param $what Array: what updates to perform
+ */
+ public function doUpdates( $what = array( 'core', 'extensions', 'purge' ) ) {
+ global $wgVersion;
+
+ $what = array_flip( $what );
+ if ( isset( $what['core'] ) ) {
+ $this->runUpdates( $this->getCoreUpdateList(), false );
+ }
+ if ( isset( $what['extensions'] ) ) {
+ $this->runUpdates( $this->getOldGlobalUpdates(), false );
+ $this->runUpdates( $this->getExtensionUpdates(), true );
+ }
+
+ $this->setAppliedUpdates( $wgVersion, $this->updates );
+
+ if( isset( $what['purge'] ) ) {
+ $this->purgeCache();
+ }
+ if ( isset( $what['core'] ) ) {
+ $this->checkStats();
+ }
+ }
+
+ /**
+ * Helper function for doUpdates()
+ *
+ * @param $updates Array of updates to run
+ * @param $passSelf Boolean: whether to pass this object we calling external
+ * functions
+ */
+ private function runUpdates( array $updates, $passSelf ) {
+ foreach ( $updates as $params ) {
+ $func = array_shift( $params );
+ if( !is_array( $func ) && method_exists( $this, $func ) ) {
+ $func = array( $this, $func );
+ } elseif ( $passSelf ) {
+ array_unshift( $params, $this );
+ }
+ call_user_func_array( $func, $params );
+ flush();
+ }
+ $this->updates = array_merge( $this->updates, $updates );
+ }
+
+ protected function setAppliedUpdates( $version, $updates = array() ) {
+ if( !$this->canUseNewUpdatelog() ) {
+ return;
+ }
+ $key = "updatelist-$version-" . time();
+ $this->db->insert( 'updatelog',
+ array( 'ul_key' => $key, 'ul_value' => serialize( $updates ) ),
+ __METHOD__ );
+ }
+
+ /**
+ * Helper function: check if the given key is present in the updatelog table.
+ * Obviously, only use this for updates that occur after the updatelog table was
+ * created!
+ */
+ public function updateRowExists( $key ) {
+ $row = $this->db->selectRow(
+ 'updatelog',
+ '1',
+ array( 'ul_key' => $key ),
+ __METHOD__
+ );
+ return (bool)$row;
+ }
+
+ /**
+ * Updatelog was changed in 1.17 to have a ul_value column so we can record
+ * more information about what kind of updates we've done (that's what this
+ * class does). Pre-1.17 wikis won't have this column, and really old wikis
+ * might not even have updatelog at all
+ *
+ * @return boolean
+ */
+ protected function canUseNewUpdatelog() {
+ return $this->db->tableExists( 'updatelog' ) &&
+ $this->db->fieldExists( 'updatelog', 'ul_value' );
+ }
+
+ /**
+ * Before 1.17, we used to handle updates via stuff like
+ * $wgExtNewTables/Fields/Indexes. This is nasty :) We refactored a lot
+ * of this in 1.17 but we want to remain back-compatible for a while. So
+ * load up these old global-based things into our update list.
+ */
+ protected function getOldGlobalUpdates() {
+ global $wgExtNewFields, $wgExtNewTables, $wgExtModifiedFields,
+ $wgExtNewIndexes, $wgSharedDB, $wgSharedTables;
+
+ $doUser = $this->shared ?
+ $wgSharedDB && in_array( 'user', $wgSharedTables ) :
+ !$wgSharedDB || !in_array( 'user', $wgSharedTables );
+
+ $updates = array();
+
+ foreach ( $wgExtNewTables as $tableRecord ) {
+ $updates[] = array(
+ 'addTable', $tableRecord[0], $tableRecord[1], true
+ );
+ }
+
+ foreach ( $wgExtNewFields as $fieldRecord ) {
+ if ( $fieldRecord[0] != 'user' || $doUser ) {
+ $updates[] = array(
+ 'addField', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2], true
+ );
+ }
+ }
+
+ foreach ( $wgExtNewIndexes as $fieldRecord ) {
+ $updates[] = array(
+ 'addIndex', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2], true
+ );
+ }
+
+ foreach ( $wgExtModifiedFields as $fieldRecord ) {
+ $updates[] = array(
+ 'modifyField', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2], true
+ );
+ }
+
+ return $updates;
+ }
+
+ /**
+ * Get an array of updates to perform on the database. Should return a
+ * multi-dimensional array. The main key is the MediaWiki version (1.12,
+ * 1.13...) with the values being arrays of updates, identical to how
+ * updaters.inc did it (for now)
+ *
+ * @return Array
+ */
+ protected abstract function getCoreUpdateList();
+
+ /**
+ * Applies a SQL patch
+ * @param $path String Path to the patch file
+ * @param $isFullPath Boolean Whether to treat $path as a relative or not
+ */
+ protected function applyPatch( $path, $isFullPath = false ) {
+ if ( $isFullPath ) {
+ $this->db->sourceFile( $path );
+ } else {
+ $this->db->sourceFile( $this->db->patchPath( $path ) );
+ }
+ }
+
+ /**
+ * Add a new table to the database
+ * @param $name String Name of the new table
+ * @param $patch String Path to the patch file
+ * @param $fullpath Boolean Whether to treat $patch path as a relative or not
+ */
+ protected function addTable( $name, $patch, $fullpath = false ) {
+ if ( $this->db->tableExists( $name ) ) {
+ $this->output( "...$name table already exists.\n" );
+ } else {
+ $this->output( "Creating $name table..." );
+ $this->applyPatch( $patch, $fullpath );
+ $this->output( "ok\n" );
+ }
+ }
+
+ /**
+ * Add a new field to an existing table
+ * @param $table String Name of the table to modify
+ * @param $field String Name of the new field
+ * @param $patch String Path to the patch file
+ * @param $fullpath Boolean Whether to treat $patch path as a relative or not
+ */
+ protected function addField( $table, $field, $patch, $fullpath = false ) {
+ if ( !$this->db->tableExists( $table ) ) {
+ $this->output( "...$table table does not exist, skipping new field patch\n" );
+ } elseif ( $this->db->fieldExists( $table, $field ) ) {
+ $this->output( "...have $field field in $table table.\n" );
+ } else {
+ $this->output( "Adding $field field to table $table..." );
+ $this->applyPatch( $patch, $fullpath );
+ $this->output( "ok\n" );
+ }
+ }
+
+ /**
+ * Add a new index to an existing table
+ * @param $table String Name of the table to modify
+ * @param $index String Name of the new index
+ * @param $patch String Path to the patch file
+ * @param $fullpath Boolean Whether to treat $patch path as a relative or not
+ */
+ protected function addIndex( $table, $index, $patch, $fullpath = false ) {
+ if ( $this->db->indexExists( $table, $index ) ) {
+ $this->output( "...$index key already set on $table table.\n" );
+ } else {
+ $this->output( "Adding $index key to table $table... " );
+ $this->applyPatch( $patch, $fullpath );
+ $this->output( "ok\n" );
+ }
+ }
+
+ /**
+ * Drop a field from an existing table
+ *
+ * @param $table String Name of the table to modify
+ * @param $field String Name of the old field
+ * @param $patch String Path to the patch file
+ * @param $fullpath Boolean Whether to treat $patch path as a relative or not
+ */
+ protected function dropField( $table, $field, $patch, $fullpath = false ) {
+ if ( $this->db->fieldExists( $table, $field ) ) {
+ $this->output( "Table $table contains $field field. Dropping... " );
+ $this->applyPatch( $patch, $fullpath );
+ $this->output( "ok\n" );
+ } else {
+ $this->output( "...$table table does not contain $field field.\n" );
+ }
+ }
+
+ /**
+ * Drop an index from an existing table
+ *
+ * @param $table String: Name of the table to modify
+ * @param $index String: Name of the old index
+ * @param $patch String: Path to the patch file
+ * @param $fullpath Boolean: Whether to treat $patch path as a relative or not
+ */
+ protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
+ if ( $this->db->indexExists( $table, $index ) ) {
+ $this->output( "Dropping $index from table $table... " );
+ $this->applyPatch( $patch, $fullpath );
+ $this->output( "ok\n" );
+ } else {
+ $this->output( "...$index key doesn't exist.\n" );
+ }
+ }
+
+ /**
+ * Modify an existing field
+ *
+ * @param $table String: name of the table to which the field belongs
+ * @param $field String: name of the field to modify
+ * @param $patch String: path to the patch file
+ * @param $fullpath Boolean: whether to treat $patch path as a relative or not
+ */
+ public function modifyField( $table, $field, $patch, $fullpath = false ) {
+ if ( !$this->db->tableExists( $table ) ) {
+ $this->output( "...$table table does not exist, skipping modify field patch\n" );
+ } elseif ( !$this->db->fieldExists( $table, $field ) ) {
+ $this->output( "...$field field does not exist in $table table, skipping modify field patch\n" );
+ } else {
+ $this->output( "Modifying $field field of table $table..." );
+ $this->applyPatch( $patch, $fullpath );
+ $this->output( "ok\n" );
+ }
+ }
+
+ /**
+ * Purge the objectcache table
+ */
+ protected function purgeCache() {
+ # We can't guarantee that the user will be able to use TRUNCATE,
+ # but we know that DELETE is available to us
+ $this->output( "Purging caches..." );
+ $this->db->delete( 'objectcache', '*', __METHOD__ );
+ $this->output( "done.\n" );
+ }
+
+ /**
+ * Check the site_stats table is not properly populated.
+ */
+ protected function checkStats() {
+ $this->output( "Checking site_stats row..." );
+ $row = $this->db->selectRow( 'site_stats', '*', array( 'ss_row_id' => 1 ), __METHOD__ );
+ if ( $row === false ) {
+ $this->output( "data is missing! rebuilding...\n" );
+ } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
+ $this->output( "missing ss_total_pages, rebuilding...\n" );
+ } else {
+ $this->output( "done.\n" );
+ return;
+ }
+ SiteStatsInit::doAllAndCommit( false );
+ }
+
+ # Common updater functions
+
+ protected function doActiveUsersInit() {
+ $activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', false, __METHOD__ );
+ if ( $activeUsers == -1 ) {
+ $activeUsers = $this->db->selectField( 'recentchanges',
+ 'COUNT( DISTINCT rc_user_text )',
+ array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ), __METHOD__
+ );
+ $this->db->update( 'site_stats',
+ array( 'ss_active_users' => intval( $activeUsers ) ),
+ array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 )
+ );
+ }
+ $this->output( "...ss_active_users user count set...\n" );
+ }
+
+ protected function doLogUsertextPopulation() {
+ if ( $this->updateRowExists( 'populate log_usertext' ) ) {
+ $this->output( "...log_user_text field already populated.\n" );
+ return;
+ }
+
+ $this->output(
+ "Populating log_user_text field, printing progress markers. For large\n" .
+ "databases, you may want to hit Ctrl-C and do this manually with\n" .
+ "maintenance/populateLogUsertext.php.\n" );
+ $task = new PopulateLogUsertext();
+ $task->execute();
+ $this->output( "Done populating log_user_text field.\n" );
+ }
+
+ protected function doLogSearchPopulation() {
+ if ( $this->updateRowExists( 'populate log_search' ) ) {
+ $this->output( "...log_search table already populated.\n" );
+ return;
+ }
+
+ $this->output(
+ "Populating log_search table, printing progress markers. For large\n" .
+ "databases, you may want to hit Ctrl-C and do this manually with\n" .
+ "maintenance/populateLogSearch.php.\n" );
+ $task = new PopulateLogSearch();
+ $task->execute();
+ $this->output( "Done populating log_search table.\n" );
+ }
+
+ protected function doUpdateTranscacheField() {
+ if ( $this->updateRowExists( 'convert transcache field' ) ) {
+ $this->output( "...transcache tc_time already converted.\n" );
+ return;
+ }
+
+ $this->output( "Converting tc_time from UNIX epoch to MediaWiki timestamp... " );
+ $this->applyPatch( 'patch-tc-timestamp.sql' );
+ $this->output( "ok\n" );
+ }
+
+ protected function doCollationUpdate() {
+ global $wgCategoryCollation;
+ if ( $this->db->selectField(
+ 'categorylinks',
+ 'COUNT(*)',
+ 'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
+ __METHOD__
+ ) == 0 ) {
+ $this->output( "...collations up-to-date.\n" );
+ return;
+ }
+
+ $task = new UpdateCollation();
+ $task->execute();
+ }
+}
diff --git a/includes/installer/Installer.i18n.php b/includes/installer/Installer.i18n.php
new file mode 100644
index 00000000..d6a8a757
--- /dev/null
+++ b/includes/installer/Installer.i18n.php
@@ -0,0 +1,12267 @@
+<?php
+/**
+ * Internationalization file for the install/upgrade process. None of the
+ * messages used here are loaded during normal operations, only during
+ * install and upgrade. So you should not put normal messages here.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+$messages = array();
+
+/** English */
+$messages['en'] = array(
+ 'config-desc' => 'The installer for MediaWiki',
+ 'config-title' => 'MediaWiki $1 installation',
+ 'config-information' => 'Information',
+ 'config-localsettings-upgrade' => "A <code>LocalSettings.php</code> file has been detected.
+To upgrade this installation, please enter the value of <code>\$wgUpgradeKey</code> in the box below.
+You will find it in LocalSettings.php.",
+ 'config-localsettings-cli-upgrade' => 'A LocalSettings.php file has been detected.
+To upgrade this installation, please run update.php instead',
+ 'config-localsettings-key' => 'Upgrade key:',
+ 'config-localsettings-badkey' => 'The key you provided is incorrect.',
+ 'config-upgrade-key-missing' => 'An existing installation of MediaWiki has been detected.
+To upgrade this installation, please put the following line at the bottom of your LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'The existing LocalSettings.php appears to be incomplete.
+The $1 variable is not set.
+Please change LocalSettings.php so that this variable is set, and click "Continue".',
+ 'config-localsettings-connection-error' => 'An error was encountered when connecting to the database using the settings specified in LocalSettings.php or AdminSettings.php. Please fix these settings and try again.
+
+$1',
+ 'config-session-error' => 'Error starting session: $1',
+ 'config-session-expired' => 'Your session data seems to have expired.
+Sessions are configured for a lifetime of $1.
+You can increase this by setting <code>session.gc_maxlifetime</code> in php.ini.
+Restart the installation process.',
+ 'config-no-session' => 'Your session data was lost!
+Check your php.ini and make sure <code>session.save_path</code> is set to an appropriate directory.',
+ 'config-your-language' => 'Your language:',
+ 'config-your-language-help' => 'Select a language to use during the installation process.',
+ 'config-wiki-language' => 'Wiki language:',
+ 'config-wiki-language-help' => 'Select the language that the wiki will predominantly be written in.',
+ 'config-back' => '← Back',
+ 'config-continue' => 'Continue →',
+ 'config-page-language' => 'Language',
+ 'config-page-welcome' => 'Welcome to MediaWiki!',
+ 'config-page-dbconnect' => 'Connect to database',
+ 'config-page-upgrade' => 'Upgrade existing installation',
+ 'config-page-dbsettings' => 'Database settings',
+ 'config-page-name' => 'Name',
+ 'config-page-options' => 'Options',
+ 'config-page-install' => 'Install',
+ 'config-page-complete' => 'Complete!',
+ 'config-page-restart' => 'Restart installation',
+ 'config-page-readme' => 'Read me',
+ 'config-page-releasenotes' => 'Release notes',
+ 'config-page-copying' => 'Copying',
+ 'config-page-upgradedoc' => 'Upgrading',
+ 'config-page-existingwiki' => 'Existing wiki',
+ 'config-help-restart' => 'Do you want to clear all saved data that you have entered and restart the installation process?',
+ 'config-restart' => 'Yes, restart it',
+ 'config-welcome' => "=== Environmental checks ===
+Basic checks are performed to see if this environment is suitable for MediaWiki installation.
+You should provide the results of these checks if you need help during installation.",
+ 'config-copyright' => "=== Copyright and Terms ===
+
+$1
+
+This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but '''without any warranty'''; without even the implied warranty of '''merchantability''' or '''fitness for a particular purpose'''.
+See the GNU General Public License for more details.
+
+You should have received <doclink href=Copying>a copy of the GNU General Public License</doclink> along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. or [http://www.gnu.org/copyleft/gpl.html read it online].",
+ 'config-sidebar' => "* [http://www.mediawiki.org MediaWiki home]
+* [http://www.mediawiki.org/wiki/Help:Contents User's Guide]
+* [http://www.mediawiki.org/wiki/Manual:Contents Administrator's Guide]
+* [http://www.mediawiki.org/wiki/Manual:FAQ FAQ]
+----
+* <doclink href=Readme>Read me</doclink>
+* <doclink href=ReleaseNotes>Release notes</doclink>
+* <doclink href=Copying>Copying</doclink>
+* <doclink href=UpgradeDoc>Upgrading</doclink>",
+ 'config-env-good' => 'The environment has been checked.
+You can install MediaWiki.',
+ 'config-env-bad' => 'The environment has been checked.
+You cannot install MediaWiki.',
+ 'config-env-php' => 'PHP $1 is installed.',
+ 'config-env-php-toolow' => 'PHP $1 is installed.
+However, MediaWiki requires PHP $2 or higher.',
+ 'config-unicode-using-utf8' => 'Using Brion Vibber\'s utf8_normalize.so for Unicode normalization.',
+ 'config-unicode-using-intl' => 'Using the [http://pecl.php.net/intl intl PECL extension] for Unicode normalization.',
+ 'config-unicode-pure-php-warning' => "'''Warning''': The [http://pecl.php.net/intl intl PECL extension] is not available to handle Unicode normalization, falling back to slow pure-PHP implementation.
+If you run a high-traffic site, you should read a little on [http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalization].",
+ 'config-unicode-update-warning' => "'''Warning''': The installed version of the Unicode normalization wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.
+You should [http://www.mediawiki.org/wiki/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
+ 'config-no-db' => 'Could not find a suitable database driver! You need to install a database driver for PHP.
+The following database types are supported: $1.
+
+If you are on shared hosting, ask your hosting provider to install a suitable database driver.
+If you compiled PHP yourself, reconfigure it with a database client enabled, for example using <code>./configure --with-mysql</code>.
+If you installed PHP from a Debian or Ubuntu package, then you also need install the php5-mysql module.',
+ 'config-no-fts3' => "'''Warning''': SQLite is compiled without the [http://sqlite.org/fts3.html FTS3 module], search features will be unavailable on this backend.",
+ 'config-register-globals' => "'''Warning: PHP's <code>[http://php.net/register_globals register_globals]</code> option is enabled.'''
+'''Disable it if you can.'''
+MediaWiki will work, but your server is exposed to potential security vulnerabilities.",
+ 'config-magic-quotes-runtime' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is active!'''
+This option corrupts data input unpredictably.
+You cannot install or use MediaWiki unless this option is disabled.",
+ 'config-magic-quotes-sybase' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] is active!'''
+This option corrupts data input unpredictably.
+You cannot install or use MediaWiki unless this option is disabled.",
+ 'config-mbstring' => "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is active!'''
+This option causes errors and may corrupt data unpredictably.
+You cannot install or use MediaWiki unless this option is disabled.",
+ 'config-ze1' => "'''Fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] is active!'''
+This option causes horrible bugs with MediaWiki.
+You cannot install or use MediaWiki unless this option is disabled.",
+ 'config-safe-mode' => "'''Warning:''' PHP's [http://www.php.net/features.safe-mode safe mode] is active.
+It may cause problems, particularly if using file uploads and <code>math</code> support.",
+ 'config-xml-bad' => "PHP's XML module is missing.
+MediaWiki requires functions in this module and will not work in this configuration.
+If you're running Mandrake, install the php-xml package.",
+ 'config-pcre' => 'The PCRE support module appears to be missing.
+MediaWiki requires the Perl-compatible regular expression functions to work.',
+ 'config-pcre-no-utf8' => "'''Fatal''': PHP's PCRE module seems to be compiled without PCRE_UTF8 support.
+MediaWiki requires UTF-8 support to function correctly.",
+ 'config-memory-raised' => "PHP's <code>memory_limit</code> is $1, raised to $2.",
+ 'config-memory-bad' => "'''Warning:''' PHP's <code>memory_limit</code> is $1.
+This is probably too low.
+The installation may fail!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] is installed',
+ 'config-apc' => '[http://www.php.net/apc APC] is installed',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] is installed',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] is installed',
+ 'config-no-cache' => "'''Warning:''' Could not find [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] or [http://www.iis.net/download/WinCacheForPhp WinCache].
+Object caching is not enabled.",
+ 'config-diff3-bad' => 'GNU diff3 not found.',
+ 'config-imagemagick' => 'Found ImageMagick: <code>$1</code>.
+Image thumbnailing will be enabled if you enable uploads.',
+ 'config-gd' => 'Found GD graphics library built-in.
+Image thumbnailing will be enabled if you enable uploads.',
+ 'config-no-scaling' => 'Could not find GD library or ImageMagick.
+Image thumbnailing will be disabled.',
+ 'config-no-uri' => "'''Error:''' Could not determine the current URI.
+Installation aborted.",
+ 'config-uploads-not-safe' => "'''Warning:''' Your default directory for uploads <code>$1</code> is vulnerable to arbitrary scripts execution.
+Although MediaWiki checks all uploaded files for security threats, it is highly recommended to [http://www.mediawiki.org/wiki/Manual:Security#Upload_security close this security vulnerability] before enabling uploads.",
+ 'config-brokenlibxml' => 'Your system has a combination of PHP and libxml2 versions which is buggy and can cause hidden data corruption in MediaWiki and other web applications.
+Upgrade to PHP 5.2.9 or later and libxml2 2.7.3 or later ([http://bugs.php.net/bug.php?id=45996 bug filed with PHP]).
+Installation aborted.',
+ 'config-using531' => 'MediaWiki cannot be used with PHP $1 due to a bug involving reference parameters to <code>__call()</code>.
+Upgrade to PHP 5.3.2 or higher, or downgrade to PHP 5.3.0 to resolve this.
+Installation aborted.',
+ 'config-suhosin-max-value-length' => "Suhosin is installed and limits the GET parameter length to $1 bytes. MediaWiki's ResourceLoader component will work around this limit, but that will degrade performance. If at all possible, you should set suhosin.get.max_value_length to 1024 or higher in php.ini , and set \$wgResourceLoaderMaxQueryLength to the same value in LocalSettings.php .",
+ 'config-db-type' => 'Database type:',
+ 'config-db-host' => 'Database host:',
+ 'config-db-host-help' => 'If your database server is on different server, enter the host name or IP address here.
+
+If you are using shared web hosting, your hosting provider should give you the correct host name in their documentation.
+
+If you are installing on a Windows server and using MySQL, using "localhost" may not work for the server name. If it does not, try "127.0.0.1" for the local IP address.',
+ 'config-db-host-oracle' => 'Database TNS:',
+ 'config-db-host-oracle-help' => 'Enter a valid [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; a tnsnames.ora file must be visible to this installation.<br />If you are using client libraries 10g or newer you can also use the [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect] naming method.',
+ 'config-db-wiki-settings' => 'Identify this wiki',
+ 'config-db-name' => 'Database name:',
+ 'config-db-name-help' => 'Choose a name that identifies your wiki.
+It should not contain spaces.
+
+If you are using shared web hosting, your hosting provider will either give you a specific database name to use or let you create databases via a control panel.',
+ 'config-db-name-oracle' => 'Database schema:',
+ 'config-db-account-oracle-warn' => "There are three supported scenarios for installing Oracle as database backend:
+
+If you wish to create database account as part of the installation process, please supply an account with SYSDBA role as database account for installation and specify the desired credentials for the web-access account, otherwise you can either create the web-access account manually and supply only that account (if it has required permissions to create the schema objects) or supply two different accounts, one with create privileges and a restricted one for web access.
+
+Script for creating an account with required privileges can be found in \"maintenance/oracle/\" directory of this installation. Keep in mind that using a restricted account will disable all maintenance capabilities with the default account.",
+ 'config-db-install-account' => 'User account for installation',
+ 'config-db-username' => 'Database username:',
+ 'config-db-password' => 'Database password:',
+ 'config-db-password-empty' => 'Please enter a password for the new database user: $1.
+While it may be possible to create users with no passwords, it is not secure.',
+ 'config-db-install-username' => 'Enter the username that will be used to connect to the database during the installation process.
+This is not the username of the MediaWiki account; this is the username for your database.',
+ 'config-db-install-password' => 'Enter the password that will be used to connect to the database during the installation process.
+This is not the password for the MediaWiki account; this is the password for your database.',
+ 'config-db-install-help' => 'Enter the username and password that will be used to connect to the database during the installation process.',
+ 'config-db-account-lock' => 'Use the same username and password during normal operation',
+ 'config-db-wiki-account' => 'User account for normal operation',
+ 'config-db-wiki-help' => 'Enter the username and password that will be used to connect to the database during normal wiki operation.
+If the account does not exist, and the installation account has sufficient privileges, this user account will be created with the minimum privileges required to operate the wiki.',
+ 'config-db-prefix' => 'Database table prefix:',
+ 'config-db-prefix-help' => 'If you need to share one database between multiple wikis, or between MediaWiki and another web application, you may choose to add a prefix to all the table names to avoid conflicts.
+Do not use spaces.
+
+This field is usually left empty.',
+ 'config-db-charset' => 'Database character set',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 backwards-compatible UTF-8',
+ 'config-charset-help' => "'''Warning:''' If you use '''backwards-compatible UTF-8''' on MySQL 4.1+, and subsequently back up the database with <code>mysqldump</code>, it may destroy all non-ASCII characters, irreversibly corrupting your backups!
+
+In '''binary mode''', MediaWiki stores UTF-8 text to the database in binary fields.
+This is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters.
+In '''UTF-8 mode''', MySQL will know what character set your data is in, and can present and convert it appropriately,
+but it will not let you store characters above the [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ 'config-mysql-old' => 'MySQL $1 or later is required, you have $2.',
+ 'config-db-port' => 'Database port:',
+ 'config-db-schema' => 'Schema for MediaWiki',
+ 'config-db-schema-help' => 'This schema will usually be fine.
+Only change it if you know you need to.',
+ 'config-pg-test-error' => "Cannot connect to database '''$1''': $2",
+ 'config-sqlite-dir' => 'SQLite data directory:',
+ 'config-sqlite-dir-help' => "SQLite stores all data in a single file.
+
+The directory you provide must be writable by the webserver during installation.
+
+It should '''not''' be accessible via the web, this is why we're not putting it where your PHP files are.
+
+The installer will write a <code>.htaccess</code> file along with it, but if that fails someone can gain access to your raw database.
+That includes raw user data (e-mail addresses, hashed passwords) as well as deleted revisions and other restricted data on the wiki.
+
+Consider putting the database somewhere else altogether, for example in <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Default tablespace:',
+ 'config-oracle-temp-ts' => 'Temporary tablespace:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki supports the following database systems:
+
+$1
+
+If you do not see the database system you are trying to use listed below, then follow the instructions linked above to enable support.',
+ 'config-support-mysql' => '* $1 is the primary target for MediaWiki and is best supported ([http://www.php.net/manual/en/mysql.installation.php how to compile PHP with MySQL support])',
+ 'config-support-postgres' => '* $1 is a popular open source database system as an alternative to MySQL ([http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]). There may be some minor outstanding bugs, and it is not recommended for use in a production environment.',
+ 'config-support-sqlite' => '* $1 is a lightweight database system which is very well supported. ([http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], uses PDO)',
+ 'config-support-oracle' => '* $1 is a commercial enterprise database. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
+ 'config-header-mysql' => 'MySQL settings',
+ 'config-header-postgres' => 'PostgreSQL settings',
+ 'config-header-sqlite' => 'SQLite settings',
+ 'config-header-oracle' => 'Oracle settings',
+ 'config-invalid-db-type' => 'Invalid database type',
+ 'config-missing-db-name' => 'You must enter a value for "Database name"',
+ 'config-missing-db-host' => 'You must enter a value for "Database host"',
+ 'config-missing-db-server-oracle' => 'You must enter a value for "Database TNS"',
+ 'config-invalid-db-server-oracle' => 'Invalid database TNS "$1".
+Use only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_) and dots (.).',
+ 'config-invalid-db-name' => 'Invalid database name "$1".
+Use only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_) and hyphens (-).',
+ 'config-invalid-db-prefix' => 'Invalid database prefix "$1".
+Use only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_) and hyphens (-).',
+ 'config-connection-error' => '$1.
+
+Check the host, username and password and try again.',
+ 'config-invalid-schema' => 'Invalid schema for MediaWiki "$1".
+Use only ASCII letters (a-z, A-Z), numbers (0-9) and underscores (_).',
+ 'config-db-sys-create-oracle' => 'Installer only supports using a SYSDBA account for creating a new account.',
+ 'config-db-sys-user-exists-oracle' => 'User account "$1" already exists. SYSDBA can only be used for creating of a new account!',
+ 'config-postgres-old' => 'PostgreSQL $1 or later is required, you have $2.',
+ 'config-sqlite-name-help' => 'Choose a name that identifies your wiki.
+Do not use spaces or hyphens.
+This will be used for the SQLite data file name.',
+ 'config-sqlite-parent-unwritable-group' => 'Cannot create the data directory <code><nowiki>$1</nowiki></code>, because the parent directory <code><nowiki>$2</nowiki></code> is not writable by the webserver.
+
+The installer has determined the user your webserver is running as.
+Make the <code><nowiki>$3</nowiki></code> directory writable by it to continue.
+On a Unix/Linux system do:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Cannot create the data directory <code><nowiki>$1</nowiki></code>, because the parent directory <code><nowiki>$2</nowiki></code> is not writable by the webserver.
+
+The installer could not determine the user your webserver is running as.
+Make the <code><nowiki>$3</nowiki></code> directory globally writable by it (and others!) to continue.
+On a Unix/Linux system do:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Error creating the data directory "$1".
+Check the location and try again.',
+ 'config-sqlite-dir-unwritable' => 'Unable to write to the directory "$1".
+Change its permissions so that the webserver can write to it, and try again.',
+ 'config-sqlite-connection-error' => '$1.
+
+Check the data directory and database name below and try again.',
+ 'config-sqlite-readonly' => 'The file <code>$1</code> is not writeable.',
+ 'config-sqlite-cant-create-db' => 'Could not create database file <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'PHP is missing FTS3 support, downgrading tables',
+ 'config-can-upgrade' => "There are MediaWiki tables in this database.
+To upgrade them to MediaWiki $1, click '''Continue'''.",
+ 'config-upgrade-done' => "Upgrade complete.
+
+You can now [$1 start using your wiki].
+
+If you want to regenerate your <code>LocalSettings.php</code> file, click the button below.
+This is '''not recommended''' unless you are having problems with your wiki.",
+ 'config-upgrade-done-no-regenerate' => "Upgrade complete.
+
+You can now [$1 start using your wiki].",
+ 'config-regenerate' => 'Regenerate LocalSettings.php →',
+ 'config-show-table-status' => 'SHOW TABLE STATUS query failed!',
+ 'config-unknown-collation' => "'''Warning:''' Database is using unrecognised collation.",
+ 'config-db-web-account' => 'Database account for web access',
+ 'config-db-web-help' => 'Select the username and password that the web server will use to connect to the database server, during ordinary operation of the wiki.',
+ 'config-db-web-account-same' => 'Use the same account as for installation',
+ 'config-db-web-create' => 'Create the account if it does not already exist',
+ 'config-db-web-no-create-privs' => 'The account you specified for installation does not have enough privileges to create an account.
+The account you specify here must already exist.',
+ 'config-mysql-engine' => 'Storage engine:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' is almost always the best option, since it has good concurrency support.
+
+'''MyISAM''' may be faster in single-user or read-only installations.
+MyISAM databases tend to get corrupted more often than InnoDB databases.",
+ 'config-mysql-charset' => 'Database character set:',
+ 'config-mysql-binary' => 'Binary',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "In '''binary mode''', MediaWiki stores UTF-8 text to the database in binary fields.
+This is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters.
+
+In '''UTF-8 mode''', MySQL will know what character set your data is in, and can present and convert it appropriately, but it will not let you store characters above the [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ 'config-site-name' => 'Name of wiki:',
+ 'config-site-name-help' => "This will appear in the title bar of the browser and in various other places.",
+ 'config-site-name-blank' => 'Enter a site name.',
+ 'config-project-namespace' => 'Project namespace:',
+ 'config-ns-generic' => 'Project',
+ 'config-ns-site-name' => 'Same as the wiki name: $1',
+ 'config-ns-other' => 'Other (specify)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-project-namespace-help' => 'Following Wikipedia\'s example, many wikis keep their policy pages separate from their content pages, in a "\'\'\'project namespace\'\'\'".
+All page titles in this namespace start with a certain prefix, which you can specify here.
+Traditionally, this prefix is derived from the name of the wiki, but it cannot contain punctuation characters such as "#" or ":".',
+ 'config-ns-invalid' => 'The specified namespace "<nowiki>$1</nowiki>" is invalid.
+Specify a different project namespace.',
+ 'config-ns-conflict' => 'The specified namespace "<nowiki>$1</nowiki>" conflicts with a default MediaWiki namespace.
+Specify a different project namespace.',
+ 'config-admin-box' => 'Administrator account',
+ 'config-admin-name' => 'Your name:',
+ 'config-admin-password' => 'Password:',
+ 'config-admin-password-confirm' => 'Password again:',
+ 'config-admin-help' => 'Enter your preferred username here, for example "Joe Bloggs".
+This is the name you will use to log in to the wiki.',
+ 'config-admin-name-blank' => 'Enter an administrator username.',
+ 'config-admin-name-invalid' => 'The specified username "<nowiki>$1</nowiki>" is invalid.
+Specify a different username.',
+ 'config-admin-password-blank' => 'Enter a password for the administrator account.',
+ 'config-admin-password-same' => 'The password must not be the same as the username.',
+ 'config-admin-password-mismatch' => 'The two passwords you entered do not match.',
+ 'config-admin-email' => 'E-mail address:',
+ 'config-admin-email-help' => 'Enter an e-mail address here to allow you to receive e-mail from other users on the wiki, reset your password, and be notified of changes to pages on your watchlist. You can leave this field empty.',
+ 'config-admin-error-user' => 'Internal error when creating an admin with the name "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Internal error when setting a password for the admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'You have entered an invalid e-mail address.',
+ 'config-subscribe' => 'Subscribe to the [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release announcements mailing list].',
+ 'config-subscribe-help' => 'This is a low-volume mailing list used for release announcements, including important security announcements.
+You should subscribe to it and update your MediaWiki installation when new versions come out.',
+ 'config-almost-done' => 'You are almost done!
+You can now skip the remaining configuration and install the wiki right now.',
+ 'config-optional-continue' => 'Ask me more questions.',
+ 'config-optional-skip' => "I'm bored already, just install the wiki.",
+ 'config-profile' => 'User rights profile:',
+ 'config-profile-wiki' => 'Traditional wiki',
+ 'config-profile-no-anon' => 'Account creation required',
+ 'config-profile-fishbowl' => 'Authorized editors only',
+ 'config-profile-private' => 'Private wiki',
+ 'config-profile-help' => "Wikis work best when you let as many people edit them as possible.
+In MediaWiki, it is easy to review the recent changes, and to revert any damage that is done by naive or malicious users.
+
+However, many have found MediaWiki to be useful in a wide variety of roles, and sometimes it is not easy to convince everyone of the benefits of the wiki way.
+So you have the choice.
+
+A '''{{int:config-profile-wiki}}''' allows anyone to edit, without even logging in.
+A wiki with '''{{int:config-profile-no-anon}}''' provides extra accountability, but may deter casual contributors.
+
+The '''{{int:config-profile-fishbowl}}''' scenario allows approved users to edit, but the public can view the pages, including history.
+A '''{{int:config-profile-private}}''' only allows approved users to view pages, with the same group allowed to edit.
+
+More complex user rights configurations are available after installation, see the [http://www.mediawiki.org/wiki/Manual:User_rights relevant manual entry].",
+ 'config-license' => 'Copyright and license:',
+ 'config-license-none' => 'No license footer',
+ 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
+ 'config-license-cc-0' => 'Creative Commons Zero',
+ 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
+ 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 or later',
+ 'config-license-pd' => 'Public Domain',
+ 'config-license-cc-choose' => 'Select a custom Creative Commons license',
+ 'config-license-help' => "Many public wikis put all contributions under a [http://freedomdefined.org/Definition free license].
+This helps to create a sense of community ownership and encourages long-term contribution.
+It is not generally necessary for a private or corporate wiki.
+
+If you want to be able to use text from Wikipedia, and you want Wikipedia to be able to accept text copied from your wiki, you should choose '''Creative Commons Attribution Share Alike'''.
+
+The GNU Free Documentation License was the old license Wikipedia was under.
+It is still a valid license, however, this license has some features which make reuse and interpretation difficult.",
+ 'config-email-settings' => 'E-mail settings',
+ 'config-enable-email' => 'Enable outbound e-mail',
+ 'config-enable-email-help' => "If you want e-mail to work, [http://www.php.net/manual/en/mail.configuration.php PHP's mail settings] need to be configured correctly.
+If you do not want any e-mail features, you can disable them here.",
+ 'config-email-user' => 'Enable user-to-user e-mail',
+ 'config-email-user-help' => 'Allow all users to send each other e-mail if they have enabled it in their preferences.',
+ 'config-email-usertalk' => 'Enable user talk page notification',
+ 'config-email-usertalk-help' => 'Allow users to receive notifications on user talk page changes, if they have enabled it in their preferences.',
+ 'config-email-watchlist' => 'Enable watchlist notification',
+ 'config-email-watchlist-help' => 'Allow users to receive notifications about their watched pages if they have enabled it in their preferences.',
+ 'config-email-auth' => 'Enable e-mail authentication',
+ 'config-email-auth-help' => "If this option is enabled, users have to confirm their e-mail address using a link sent to them whenever they set or change it.
+Only authenticated e-mail addresses can receive e-mails from other users or change notification e-mails.
+Setting this option is '''recommended''' for public wikis because of potential abuse of the e-mail features.",
+ 'config-email-sender' => 'Return e-mail address:',
+ 'config-email-sender-help' => 'Enter the e-mail address to use as the return address on outbound e-mail.
+This is where bounces will be sent.
+Many mail servers require at least the domain name part to be valid.',
+ 'config-upload-settings' => 'Images and file uploads',
+ 'config-upload-enable' => 'Enable file uploads',
+ 'config-upload-help' => "File uploads potentially expose your server to security risks.
+For more information, read the [http://www.mediawiki.org/wiki/Manual:Security security section] in the manual.
+
+To enable file uploads, change the mode on the <code>images</code> subdirectory under MediaWiki's root directory so that the web server can write to it.
+Then enable this option.",
+ 'config-upload-deleted' => 'Directory for deleted files:',
+ 'config-upload-deleted-help' => 'Choose a directory in which to archive deleted files.
+Ideally, this should not be accessible from the web.',
+ 'config-logo' => 'Logo URL:',
+ 'config-logo-help' => "MediaWiki's default skin includes space for a 135x160 pixel logo above the sidebar menu.
+Upload an image of the appropriate size, and enter the URL here.
+
+If you do not want a logo, leave this box blank.",
+ 'config-instantcommons' => 'Enable Instant Commons',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] is a feature that allows wikis to use images, sounds and other media found on the [http://commons.wikimedia.org/ Wikimedia Commons] site.
+In order to do this, MediaWiki requires access to the Internet.
+
+For more information on this feature, including instructions on how to set it up for wikis other than the Wikimedia Commons, consult [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos the manual].',
+ 'config-cc-error' => 'The Creative Commons license chooser gave no result.
+Enter the license name manually.',
+ 'config-cc-again' => 'Pick again...',
+ 'config-cc-not-chosen' => 'Choose which Creative Commons license you want and click "proceed".',
+ 'config-advanced-settings' => 'Advanced configuration',
+ 'config-cache-options' => 'Settings for object caching:',
+ 'config-cache-help' => 'Object caching is used to improve the speed of MediaWiki by caching frequently used data.
+Medium to large sites are highly encouraged to enable this, and small sites will see benefits as well.',
+ 'config-cache-none' => 'No caching (no functionality is removed, but speed may be impacted on larger wiki sites)',
+ 'config-cache-accel' => 'PHP object caching (APC, eAccelerator, XCache or WinCache)',
+ 'config-cache-memcached' => 'Use Memcached (requires additional setup and configuration)',
+ 'config-memcached-servers' => 'Memcached servers:',
+ 'config-memcached-help' => 'List of IP addresses to use for Memcached.
+Should specify one per line and specify the port to be used. For example:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'You selected Memcached as your cache type but did not specify any servers.',
+ 'config-memcache-badip' => 'You have entered an invalid IP address for Memcached: $1.',
+ 'config-memcache-noport' => 'You did not specify a port to use for Memcached server: $1.
+If you do not know the port, the default is 11211.',
+ 'config-memcache-badport' => 'Memcached port numbers should be between $1 and $2.',
+ 'config-extensions' => 'Extensions',
+ 'config-extensions-help' => 'The extensions listed above were detected in your <code>./extensions</code> directory.
+
+They may require additional configuration, but you can enable them now',
+ 'config-install-alreadydone' => "'''Warning:''' You seem to have already installed MediaWiki and are trying to install it again.
+Please proceed to the next page.",
+ 'config-install-begin' => 'By pressing "{{int:config-continue}}", you will begin the installation of MediaWiki.
+If you still want to make changes, press back.',
+ 'config-install-step-done' => 'done',
+ 'config-install-step-failed' => 'failed',
+ 'config-install-extensions' => 'Including extensions',
+ 'config-install-database' => 'Setting up database',
+ 'config-install-schema' => 'Creating schema',
+ 'config-install-pg-schema-not-exist' => 'PostgreSQL schema does not exist.',
+ 'config-install-pg-schema-failed' => 'Tables creation failed.
+Make sure that the user "$1" can write to the schema "$2".',
+ 'config-install-pg-commit' => 'Committing changes',
+ 'config-install-pg-plpgsql' => 'Checking for language PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'You need to install the language PL/pgSQL in the database $1',
+ 'config-pg-no-create-privs' => 'The account you specified for installation does not have enough privileges to create an account.',
+ 'config-pg-not-in-role' => 'The account you specified for the web user already exists.
+The account you specified for installation is not a superuser and is not a member of the web user\'s role, so it is unable to create objects owned by the web user.
+
+MediaWiki currently requires that the tables be owned by the web user. Please specify another web account name, or click "back" and specify a suitably privileged install user.',
+ 'config-install-user' => 'Creating database user',
+ 'config-install-user-alreadyexists' => 'User "$1" already exists',
+ 'config-install-user-create-failed' => 'Creating user "$1" failed: $2',
+ 'config-install-user-grant-failed' => 'Granting permission to user "$1" failed: $2',
+ 'config-install-user-missing' => 'The specified user "$1" does not exist.',
+ 'config-install-user-missing-create' => 'The specified user "$1" does not exist.
+Please click the "create account" checkbox below if you want to create it.',
+ 'config-install-tables' => 'Creating tables',
+ 'config-install-tables-exist' => "'''Warning''': MediaWiki tables seem to already exist.
+Skipping creation.",
+ 'config-install-tables-failed' => "'''Error''': Table creation failed with the following error: $1",
+ 'config-install-interwiki' => 'Populating default interwiki table',
+ 'config-install-interwiki-list' => 'Could not read file <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Warning''': The interwiki table seems to already have entries.
+Skipping default list.",
+ 'config-install-stats' => 'Initializing statistics',
+ 'config-install-keys' => 'Generating secret keys',
+ 'config-insecure-keys' => "'''Warning:''' {{PLURAL:$2|A secure key|Secure keys}} ($1) generated during installation {{PLURAL:$2|is|are}} not completely safe. Consider changing {{PLURAL:$2|it|them}} manually.",
+ 'config-install-sysop' => 'Creating administrator user account',
+ 'config-install-subscribe-fail' => 'Unable to subscribe to mediawiki-announce: $1',
+ 'config-install-mainpage' => 'Creating main page with default content',
+ 'config-install-extension-tables' => 'Creating tables for enabled extensions',
+ 'config-install-mainpage-failed' => 'Could not insert main page: $1',
+ 'config-install-done' => "'''Congratulations!'''
+You have successfully installed MediaWiki.
+
+The installer has generated a <code>LocalSettings.php</code> file.
+It contains all your configuration.
+
+You will need to download it and put it in the base of your wiki installation (the same directory as index.php). The download should have started automatically.
+
+If the download was not offered, or if you cancelled it, you can restart the download by clicking the link below:
+
+$3
+
+'''Note''': If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.
+
+When that has been done, you can '''[$2 enter your wiki]'''.",
+ 'config-download-localsettings' => 'Download LocalSettings.php',
+ 'config-help' => 'help',
+);
+
+/** Message documentation (Message documentation)
+ * @author Dani
+ * @author EugeneZelenko
+ * @author Kghbln
+ * @author McDutchie
+ * @author Nike
+ * @author Platonides
+ * @author Purodha
+ * @author Raymond
+ * @author Siebrand
+ * @author Umherirrender
+ */
+$messages['qqq'] = array(
+ 'config-desc' => '{{desc}}',
+ 'config-title' => 'Parameters:
+* $1 is the version of MediaWiki that is being installed.',
+ 'config-information' => '{{Identical|Information}}',
+ 'config-localsettings-cli-upgrade' => 'Do not translate the <code>LocalSettings.php</code> and the <code>update.php</code> parts.',
+ 'config-session-error' => 'Parameters:
+* $1 is the error that was encountered with the session.',
+ 'config-session-expired' => 'Parameters:
+* $1 is the configured session lifetime.',
+ 'config-back' => '{{Identical|Back}}',
+ 'config-continue' => '{{Identical|Continue}}',
+ 'config-page-language' => '{{Identical|Language}}',
+ 'config-page-name' => '{{Identical|Name}}',
+ 'config-page-options' => '{{Identical|Options}}',
+ 'config-page-install' => '{{Identical|Install}}',
+ 'config-page-copying' => 'This is a link to the full GPL text',
+ 'config-restart' => 'Button text to confirm the installation procedure has to be restarted.',
+ 'config-env-php' => 'Parameters:
+* $1 is the version of PHP that has been installed.',
+ 'config-no-db-help' => 'Parameters:
+* $1 is comma separated list of supported database types by MediaWiki.',
+ 'config-memory-raised' => 'Parameters:
+* $1 is the configured <code>memory_limit</code>.
+* $2 is the value to which <code>memory_limit</code> was raised.',
+ 'config-memory-bad' => 'Parameters:
+* $1 is the configured <code>memory_limit</code>.',
+ 'config-xcache' => 'Message indicates if this program is available',
+ 'config-apc' => 'Message indicates if this program is available',
+ 'config-eaccel' => 'Message indicates if this program is available',
+ 'config-wincache' => 'Message indicates if this program is available',
+ 'config-db-host-oracle' => 'TNS = [[:wikipedia:Transparent Network Substrate|Transparent Network Substrate]] (<== wikipedia link)',
+ 'config-db-wiki-settings' => 'This is more acurate: "Enter identifying or distinguishing data for this wiki" since a MySQL database can host tables of several wikis.',
+ 'config-db-account-lock' => "It might be easier to translate ''normal operation'' as \"also after the installation process\"",
+ 'config-support-mysql' => 'Parameters:
+* $1 - a link to the MySQL home page having the anchor text "MySQL".',
+ 'config-support-postgres' => 'Parameters:
+* $1 - a link to the PostgreSQL home page having the anchor text "PostgreSQL".',
+ 'config-support-sqlite' => 'Parameters:
+* $1 - a link to the SQLite home page having the anchor text "SQLite".',
+ 'config-support-oracle' => 'Parameters:
+* $1 - a link to the Oracle home page, the anchor text of which is "Oracle".',
+ 'config-sqlite-dir-unwritable' => 'webserver refers to a software like Apache or Lighttpd.',
+ 'config-can-upgrade' => 'Should we no use an {{int:xxx}} construct for "continue" ?
+
+Parameters:
+* $1 - Version or Revision indicator.',
+ 'config-show-table-status' => '{{doc-important|"SHOW TABLE STATUS" is a MySQL command. Do not translate this.}}',
+ 'config-ns-generic' => '{{Identical|Project}}',
+ 'config-admin-name' => '{{Identical|Your name}}',
+ 'config-admin-password' => '{{Identical|Password}}',
+ 'config-admin-email' => '{{Identical|E-mail address}}',
+ 'config-subscribe' => 'Used as label for the installer checkbox',
+ 'config-profile-help' => 'Messages referenced:
+* {{msg-mw|config-profile-wiki}}
+* {{msg-mw|config-profile-no-anon}}
+* {{msg-mw|config-profile-fishbowl}}
+* {{msg-mw|config-profile-private}}',
+ 'config-upload-help' => 'The word "mode" here refers to the access rights given to various user groups when attempting to create and store files and/or subdiretories in the said directory on the server. It also refers to the <code>mode</code> command used to maipulate said right mask under Unix, Linux, and similar operating systems. A less operating-system-centric translation is fine.',
+ 'config-logo-help' => '{{doc-important|For languages with right-to-left script, translate "top left corner" as "top right corner".}}',
+ 'config-cc-not-chosen' => 'Do not translate the <code>"proceed".</code> part.
+This message refers to a block of HTML being embedded into the installer page. It comes from the Creative Commons Web site. The block is in the English language. It is a scripted license chooser. When an individual license has been selected, it asks you to klick "proceed" so as to return to the MediaWiki installer page.',
+ 'config-extensions' => '{{Identical|Extension}}',
+ 'config-install-step-done' => '{{Identical|Done}}',
+ 'config-install-pg-schema-failed' => 'Parameters:
+* $1 = database user name (usernames in the database are unrelated to wiki user names)
+* $2 =',
+ 'config-install-user' => 'Message indicates that the user is being created',
+ 'config-install-user-grant-failed' => 'Parameters:
+* $1 is the database username for which granting rights failed
+* $2 is the error message',
+ 'config-install-tables' => 'Message indicates that the tables are being created',
+ 'config-install-interwiki' => 'Message indicates that the interwikitables are being populated',
+ 'config-insecure-keys' => 'Parameters:
+* $1 - A list of names of the secret keys that were generated.
+* $2 - the number of items in the list $1, to be used with PLURAL.',
+ 'config-install-sysop' => 'Message indicates that the administrator user account is being created',
+ 'config-install-subscribe-fail' => '{{doc-important|"mediawiki-announce" is the name of a mailing list and should not be translated.}}',
+ 'config-install-done' => 'Parameters:
+* $1 is the URL to LocalSettings download
+* $2 is a link to the wiki.
+* $3 is a download link with attached download icon. The config-download-localsettings message will be used as the link text.',
+ 'config-download-localsettings' => 'The link text used in the download link in config-install-done.',
+ 'config-help' => 'This is used in help boxes.
+{{Identical|Help}}',
+);
+
+/** Magyar (magázó) (Magyar (magázó))
+ * @author Dani
+ * @author Glanthor Reviol
+ */
+$messages['hu-formal'] = array(
+ 'config-localsettings-upgrade' => "'''Figyelmeztetés''': már létezik a <code>LocalSettings.php</code> fájl.
+A szoftver frissíthető.
+Adja meg a <code>\$wgUpgradeKey</code>-ben található kulcsot a beviteli mezőben",
+ 'config-session-expired' => 'Úgy tűnik, hogy a munkamenetadatok lejártak.
+A munkamenetek élettartama a következőre van beállítva: $1.
+Az érték növelhető a php.ini <code>session.gc_maxlifetime</code> beállításának módosításával.
+Indítsa újra a telepítési folyamatot.',
+ 'config-no-session' => 'Elvesztek a munkamenetadatok!
+Ellenőrizze, hogy a php.ini-ben a <code>session.save_path</code> beállítás a megfelelő könyvtárra mutat-e.',
+ 'config-your-language-help' => 'Válassza ki a telepítési folyamat során használandó nyelvet.',
+ 'config-wiki-language-help' => 'Az a nyelv, amin a wiki tartalmának legnagyobb része íródik.',
+ 'config-page-welcome' => 'Üdvözli a MediaWiki!',
+ 'config-help-restart' => 'Szeretné törölni az eddig megadott összes adatot és újraindítani a telepítési folyamatot?',
+ 'config-welcome' => '=== Környezet ellenőrzése ===
+Alapvető ellenőrzés, ami megmondja, hogy a környezet alkalmas-e a MediaWiki számára.
+Ha probléma merülne fel a telepítés során, meg kell adnia mások számára az alább megjelenő információkat.',
+ 'config-unicode-pure-php-warning' => "'''Figyelmeztetés''': Az [http://pecl.php.net/intl intl PECL kiterjesztés] nem érhető el Unicode normalizáláshoz.
+Ha nagy látogatottságú oldalt üzemeltet, itt találhat információkat [http://www.mediawiki.org/wiki/Unicode_normalization_considerations a témáról].",
+ 'config-register-globals' => "'''Figyelmeztetés: A PHP <code>[http://php.net/register_globals register_globals]</code> beállítása engedélyezve van.'''
+'''Tiltsa le, ha van rá lehetősége.'''
+A MediaWiki működőképes a beállítás használata mellett, de a szerver biztonsági kockázatnak lesz kitéve.",
+ 'config-imagemagick' => 'Az ImageMagick megtalálható a rendszeren: <code>$1</code>.
+A bélyegképek készítése engedélyezve lesz, ha engedélyezi a feltöltéseket.',
+ 'config-db-name-help' => 'Válassza ki a wikije azonosítására használt nevet.
+Nem tartalmazhat szóközt vagy kötőjelet.
+
+Ha megosztott webtárhelyet használ, a szolgáltatója vagy egy konkrét adatbázisnevet ad önnek használatra, vagy létrehozhat egyet a vezérlőpulton keresztül.',
+ 'config-db-install-help' => 'Adja meg a felhasználónevet és jelszót, amivel a telepítő csatlakozhat az adatbázishoz.',
+ 'config-db-wiki-help' => 'Adja meg azt a felhasználónevet és jelszót, amivel a wiki fog csatlakozni az adatbázishoz működés közben.
+Ha a fiók nem létezik és a telepítést végző fiók rendelkezik megfelelő jogosultsággal, egy új fiók készül a megadott a névvel, azon minimális jogosultságkörrel, ami a wiki működéséhez szükséges.',
+ 'config-charset-help' => "'''Figyelmezetés:''' Ha a '''visszafelé kompatibilis UTF-8''' beállítást használja MySQL 4.1 vagy újabb verziók esetén, és utána a <code>mysqldump</code> programmal készít róla biztonsági másolatot, az tönkreteheti az összes nem ASCII-karaktert, visszafordíthatatlanul károsítva a másolatokban tárolt adatokat!
+
+'''Bináris''' módban a MediaWiki az UTF-8-ban kódolt szöveget bináris mezőkben tárolja az adatbázisban.
+Ez sokkal hatékonyabb a MySQL UTF-8-módjától, és lehetővé teszi, hogy a teljes Unicode-karakterkészletet használja.
+'''UTF-8-módban''' MySQL tudja, hogy milyen karakterkészlettel van kódolva az adat, megfelelően van megjelenítve és konvertálva, de
+nem használhatja a [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] feletti karaktereket.",
+ 'config-db-schema-help' => 'A fenti sémák általában megfelelőek.
+Csak akkor módosítson rajta, ha szükség van rá.',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Nem lehet létrehozni az adatok tárolásához szükséges <code><nowiki>$1</nowiki></code> könyvtárat, mert a webszerver nem írhat a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>).
+
+A telepítő nem tudta megállapíteni, hogy melyik felhasználói fiókon fut a webszerver.
+A folytatáshoz tegye írhatóvá ezen fiók (és más fiókok!) számára a következő könyvtárat: <code><nowiki>$3</nowiki></code>.
+Unix/Linux rendszereken tedd a következőt:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-ns-other' => 'Más (adja meg)',
+ 'config-admin-name-blank' => 'Adja meg az adminisztrátor felhasználónevét!',
+ 'config-admin-name-invalid' => 'A megadott felhasználónév (<nowiki>$1</nowiki>) érvénytelen.
+Adjon meg egy másik felhasználónevet.',
+ 'config-admin-password-blank' => 'Adja meg az adminisztrátori fiók jelszavát!',
+ 'config-instantcommons-help' => 'Az [http://www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [http://commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.
+A használatához a MediaWikinek internethozzáférésre van szüksége.
+
+A funkcióról és hogy hogyan állítható be más wikik esetén [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos a kézikönyvben] találhat további információkat.',
+ 'config-install-done' => "'''Gratulálunk!'''
+Sikeresen telepítette a MediaWikit.
+
+A telepítő készített egy <code>LocalSettings.php</code> fájlt.
+Ez tartalmazza az összes beállítást.
+
+[$1 Le kell töltenie], és el kell helyeznie a MediaWiki telepítési könyvtárába (az a könyvtár, ahol az index.php van).
+'''Megjegyzés''': Ha ezt most nem teszi meg, és kilép, a generált fájl nem lesz elérhető a későbbiekben.
+
+Ha ezzel készen van, '''[$2 beléphet a wikibe]'''.",
+);
+
+/** Afrikaans (Afrikaans)
+ * @author Naudefj
+ */
+$messages['af'] = array(
+ 'config-desc' => 'Die Installasieprogram vir MediaWiki',
+ 'config-title' => 'Installasie MediaWiki $1',
+ 'config-information' => 'Inligting',
+ 'config-localsettings-key' => 'Opgradeer-sleutel:',
+ 'config-localsettings-badkey' => 'Die sleutel wat u verskaf het is verkeerd.',
+ 'config-session-error' => 'Fout met begin van sessie: $1',
+ 'config-no-session' => "U sessiedata is verlore!
+Kontroleer u php.ini en maak seker dat <code>session.save_path</code> na 'n geldige gids wys.",
+ 'config-your-language' => 'U taal:',
+ 'config-your-language-help' => "Kies 'n taal om tydens die installasieproses te gebruik.",
+ 'config-wiki-language' => 'Wiki se taal:',
+ 'config-wiki-language-help' => 'Kies die taal waarin die wiki hoofsaaklik geskryf sal word.',
+ 'config-back' => '← Terug',
+ 'config-continue' => 'Gaan voort →',
+ 'config-page-language' => 'Taal',
+ 'config-page-welcome' => 'Welkom by MediaWiki!',
+ 'config-page-dbconnect' => 'Konnekteer na die databasis',
+ 'config-page-upgrade' => "Opgradeer 'n bestaande installasie",
+ 'config-page-dbsettings' => 'Databasis-instellings',
+ 'config-page-name' => 'Naam',
+ 'config-page-options' => 'Opsies',
+ 'config-page-install' => 'Installeer',
+ 'config-page-complete' => 'Voltooi!',
+ 'config-page-restart' => 'Herbegin installasie',
+ 'config-page-readme' => 'Lees my',
+ 'config-page-releasenotes' => 'Vrystellingsnotas',
+ 'config-page-copying' => 'Besig met kopiëring',
+ 'config-page-upgradedoc' => 'Besig met opgradering',
+ 'config-page-existingwiki' => 'Bestaande wiki',
+ 'config-restart' => 'Ja, herbegin dit',
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki tuisblad]
+* [http://www.mediawiki.org/wiki/Help:Contents Gebruikershandleiding] (Engelstalig)
+* [http://www.mediawiki.org/wiki/Manual:Contents Administrateurshandleiding] (Engelstalig)
+* [http://www.mediawiki.org/wiki/Manual:FAQ Algemene vrae] (Engelstalig)
+----
+* <doclink href=Readme>Lees my</doclink>
+* <doclink href=ReleaseNotes>Vrystellingsnotas</doclink>
+* <doclink href=Copying>Kopiëring</doclink>
+* <doclink href=UpgradeDoc>Opgradering</doclink>',
+ 'config-env-good' => 'Die omgewing is gekontroleer.
+U kan MediaWiki installeer.',
+ 'config-env-bad' => 'Die omgewing is gekontroleer.
+U kan nie MediaWiki installeer nie.</span>',
+ 'config-env-php' => 'PHP $1 is tans geïnstalleer.',
+ 'config-no-db' => "Kon nie 'n geskikte databasisdrywer vind nie!",
+ 'config-memory-raised' => 'PHP se <code>memory_limit</code> is $1, en is verhoog tot $2.',
+ 'config-memory-bad' => "'''Waarskuwing:''' PHP se <code>memory_limit</code> is $1.
+Dit is waarskynlik te laag.
+Die installasie mag moontlik faal!",
+ 'config-xcache' => '[Http://trac.lighttpd.net/xcache/ XCache] is geïnstalleer',
+ 'config-apc' => '[Http://www.php.net/apc APC] is geïnstalleer',
+ 'config-eaccel' => '[Http://eaccelerator.sourceforge.net/ eAccelerator] is geïnstalleer',
+ 'config-wincache' => '[Http://www.iis.net/download/WinCacheForPhp WinCache] is geïnstalleer',
+ 'config-diff3-bad' => 'GNU diff3 nie gevind nie.',
+ 'config-db-type' => 'Databasistipe:',
+ 'config-db-host' => 'Databasisbediener:',
+ 'config-db-host-oracle' => 'Databasis-TNS:',
+ 'config-db-wiki-settings' => 'Identifiseer hierdie wiki',
+ 'config-db-name' => 'Databasisnaam:',
+ 'config-db-name-oracle' => 'Databasis-skema:',
+ 'config-db-install-account' => 'Gebruiker vir die installasie',
+ 'config-db-username' => 'Databasis gebruikersnaam:',
+ 'config-db-password' => 'Databasis wagwoord:',
+ 'config-db-prefix' => 'Voorvoegsel vir databasistabelle:',
+ 'config-db-charset' => 'Karakterstelsel vir databasis',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-mysql-old' => 'U moet MySQL $1 of later gebruik.
+U gebruik tans $2.',
+ 'config-db-port' => 'Databasispoort:',
+ 'config-db-schema' => 'Skema vir MediaWiki',
+ 'config-sqlite-dir' => 'Gids vir SQLite se data:',
+ 'config-oracle-def-ts' => 'Standaard tabelruimte:',
+ 'config-oracle-temp-ts' => 'Tydelike tabelruimte:',
+ 'config-header-mysql' => 'MySQL-instellings',
+ 'config-header-postgres' => 'PostgreSQL-instellings',
+ 'config-header-sqlite' => 'SQLite-instellings',
+ 'config-header-oracle' => 'Oracle-instellings',
+ 'config-invalid-db-type' => 'Ongeldige databasistipe',
+ 'config-missing-db-name' => 'U moet \'n waarde vir "Databasnaam" verskaf',
+ 'config-sqlite-readonly' => 'Die lêer <code>$1</code> kan nie geskryf word nie.',
+ 'config-sqlite-cant-create-db' => 'Kon nie databasislêer <code>$1</code> skep nie.',
+ 'config-regenerate' => 'Herskep LocalSettings.php →',
+ 'config-show-table-status' => 'Die uitvoer van SHOW TABLE STATUS het gefaal!',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-binary' => 'Binêr',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Naam van die wiki:',
+ 'config-site-name-blank' => "Verskaf 'n naam vir u webwerf.",
+ 'config-project-namespace' => 'Projeknaamruimte:',
+ 'config-ns-generic' => 'Projek',
+ 'config-ns-site-name' => 'Dieselfde as die wiki: $1',
+ 'config-ns-other' => 'Ander (spesifiseer)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-admin-box' => 'Administrateur se gebruiker',
+ 'config-admin-name' => 'U naam:',
+ 'config-admin-password' => 'Wagwoord:',
+ 'config-admin-password-confirm' => 'Wagwoord weer:',
+ 'config-admin-password-blank' => "Verskaf 'n wagwoord vir die administrateur in.",
+ 'config-admin-password-same' => 'Die wagwoord mag nie dieselfde as die gebruikersnaam wees nie.',
+ 'config-admin-password-mismatch' => 'Die twee wagwoorde wat u ingetik het stem nie ooreen nie.',
+ 'config-admin-email' => 'E-posadres:',
+ 'config-optional-continue' => 'Vra my meer vrae.',
+ 'config-optional-skip' => 'Ek is reeds verveeld, installeer maar net die wiki.',
+ 'config-profile-wiki' => 'Tradisionele wiki',
+ 'config-profile-no-anon' => 'Skep van gebruiker is verpligtend',
+ 'config-profile-fishbowl' => 'Slegs vir gemagtigde redaksie',
+ 'config-profile-private' => 'Privaat wiki',
+ 'config-license' => 'Kopiereg en lisensie:',
+ 'config-license-none' => 'Geen lisensie in die onderskrif',
+ 'config-license-pd' => 'Publieke Domein',
+ 'config-license-cc-choose' => "Kies 'n Creative Commons-lisensie",
+ 'config-email-settings' => 'E-posinstellings',
+ 'config-email-sender' => 'E-posadres vir antwoorde:',
+ 'config-upload-settings' => 'Oplaai van beelde en lêer',
+ 'config-upload-enable' => 'Aktiveer die oplaai van lêers',
+ 'config-upload-deleted' => 'Gids vir verwyderde lêers:',
+ 'config-logo' => 'URL vir logo:',
+ 'config-cc-again' => 'Kies weer...',
+ 'config-advanced-settings' => 'Gevorderde konfigurasie',
+ 'config-memcached-servers' => 'Memcached-bedieners:',
+ 'config-extensions' => 'Uitbreidings',
+ 'config-install-step-done' => 'gedoen',
+ 'config-install-step-failed' => 'het misluk',
+ 'config-install-extensions' => 'Insluitende uitbreidings',
+ 'config-install-database' => 'Stel die databasis op',
+ 'config-install-pg-schema-failed' => 'Die skep van tabelle het gefaal.
+Maak seker dat die gebruiker "$1" na skema "$2" mag skryf.',
+ 'config-install-pg-commit' => 'Wysigings word gestoor',
+ 'config-pg-no-plpgsql' => 'U moet die taal PL/pgSQL in die database $1 installeer',
+ 'config-install-user' => 'Besig om die databasisgebruiker te skep',
+ 'config-install-user-grant-failed' => 'Die toekenning van regte aan gebruiker "$1" het gefaal: $2',
+ 'config-install-tables' => 'Skep tabelle',
+ 'config-install-tables-exist' => "'''Waarskuwing''': Dit lyk of MediaWiki se tabelle reeds bestaan.
+Die skep van tabelle word oorgeslaan.",
+ 'config-install-tables-failed' => "'''Fout''': die skep van 'n tabel het gefaal met die volgende fout: $1",
+ 'config-install-interwiki' => 'Besig om data in die interwiki-tabel in te laai',
+ 'config-install-interwiki-list' => 'Kon nie die lêer <code>interwiki.list</code> vind nie.',
+ 'config-install-interwiki-exists' => "'''Waarskuwing''': Die interwiki-tabel bevat reeds inskrywings.
+Die standaardlys word oorgeslaan.",
+ 'config-install-keys' => 'Genereer geheime sleutel',
+ 'config-install-sysop' => "Skep 'n gebruiker vir die administrateur",
+ 'config-install-mainpage' => 'Skep die hoofblad met standaard inhoud',
+ 'config-install-mainpage-failed' => 'Kon nie die hoofblad laai nie: $1',
+ 'config-install-done' => "'''Veels geluk!'''
+U het MediaWiki suksesvol geïnstalleer.
+
+Die installeerder het 'n <code>LocalSettings.php</code> lêer opgestel.
+Dit bevat al u instellings.
+
+U sal dit moet [$1 aflaai] en dit in die hoofgids van u wiki-installasie plaas; in dieselfde gids as index.php.
+'''Let wel''': As u dit nie nou doen nie, sal die gegenereerde konfigurasielêer nie later meer beskikbaar wees nadat u die installasie afgesluit het nie.
+
+As dit gedoen is, kan u '''[u $2 wiki besoek]'''.",
+ 'config-download-localsettings' => 'Laai LocalSettings.php af',
+ 'config-help' => 'hulp',
+);
+
+/** Arabic (العربية)
+ * @author Meno25
+ */
+$messages['ar'] = array(
+ 'config-type-mysql' => 'ماي إس كيو إل',
+ 'config-type-postgres' => 'بوستجر إس كيو إل',
+ 'config-type-sqlite' => 'إس كيو لايت',
+ 'config-type-oracle' => 'أوراكل',
+);
+
+/** Aramaic (ܐܪܡܝܐ)
+ * @author Basharh
+ */
+$messages['arc'] = array(
+ 'config-information' => 'ܝܕ̈ܥܬܐ',
+ 'config-your-language' => 'ܠܫܢܐ ܕܝܠܟ:',
+ 'config-wiki-language' => 'ܠܫܢܐ ܕܘܝܩܝ:',
+ 'config-page-language' => 'ܠܫܢܐ',
+ 'config-page-name' => 'ܫܡܐ',
+ 'config-page-options' => 'ܓܒܝܬ̈ܐ',
+ 'config-page-install' => 'ܢܨܘܒ',
+ 'config-ns-other-default' => 'ܘܝܩܝ ܕܝܠܝ',
+ 'config-admin-box' => 'ܚܘܫܒܢܐ ܕܡܕܒܪܢܐ',
+ 'config-admin-name' => 'ܫܡܐ ܕܝܠܟ:',
+ 'config-admin-password' => 'ܡܠܬܐ ܕܥܠܠܐ:',
+ 'config-admin-password-confirm' => 'ܡܠܬܐ ܕܥܠܠܐ ܙܒܢܬܐ ܐܚܪܬܐ:',
+ 'config-admin-email' => 'ܦܪܫܓܢܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ:',
+ 'config-profile-private' => 'ܘܝܩܝ ܦܪܨܘܦܝܐ',
+ 'config-email-settings' => 'ܛܘܝܒ̈ܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ',
+);
+
+/** Belarusian (Taraškievica orthography) (‪Беларуская (тарашкевіца)‬)
+ * @author EugeneZelenko
+ * @author Jim-by
+ * @author Wizardist
+ * @author Zedlik
+ */
+$messages['be-tarask'] = array(
+ 'config-desc' => 'Праграма ўсталяваньня MediaWiki',
+ 'config-title' => 'Усталяваньне MediaWiki $1',
+ 'config-information' => 'Інфармацыя',
+ 'config-localsettings-upgrade' => 'Выяўлены файл <code>LocalSettings.php</code>.
+Каб абнавіць гэтае усталяваньне, калі ласка, увядзіце значэньне <code>$wgUpgradeKey</code> у полі ніжэй.
+Яго можна знайсьці ў LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Быў знойдзены файл LocalSettings.php.
+Каб зьмяніць гэтае ўсталяваньне, калі ласка, запусьціце update.php',
+ 'config-localsettings-key' => 'Ключ паляпшэньня:',
+ 'config-localsettings-badkey' => 'Пададзены Вамі ключ зьяўляецца няслушным',
+ 'config-upgrade-key-missing' => 'Выяўленае існуючае ўсталяваньне MediaWiki.
+Каб абнавіць гэтае ўсталяваньне, калі ласка, устаўце наступны радок у канец Вашага LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'Выглядае, што існуючы LocalSettings.php зьяўляецца няпоўным.
+Не ўстаноўленая пераменная $1.
+Калі ласка, зьмяніце LocalSettings.php так, каб была ўстаноўленая гэтая пераменная, і націсьніце «Працягваць».',
+ 'config-localsettings-connection-error' => 'Адбылася памылка падчас злучэньня з базай зьвестак з выкарыстаньнем наладаў, пазначаных у LocalSettings.php ці AdminSettings.php. Калі ласка, выпраўце гэтыя налады і паспрабуйце зноў.
+
+$1',
+ 'config-session-error' => 'Памылка стварэньня сэсіі: $1',
+ 'config-session-expired' => 'Скончыўся тэрмін дзеяньня зьвестак сэсіі.
+Сэсія мае абмежаваны тэрмін у $1.
+Вы можаце павялічыць яго, зьмяніўшы парамэтар <code>session.gc_maxlifetime</code> у php.ini.
+Перазапусьціце праграму ўсталяваньня.',
+ 'config-no-session' => 'Зьвесткі сэсіі згубленыя!
+Праверце php.ini і ўпэўніцеся, што ўстаноўлены слушны шлях у <code>session.save_path</code>.',
+ 'config-your-language' => 'Вашая мова:',
+ 'config-your-language-help' => 'Выберыце мову для выкарыстаньня падчас усталяваньня.',
+ 'config-wiki-language' => 'Мова вікі:',
+ 'config-wiki-language-help' => 'Выберыце мову, на якой пераважна будзе пісацца зьмест у вікі.',
+ 'config-back' => '← Назад',
+ 'config-continue' => 'Далей →',
+ 'config-page-language' => 'Мова',
+ 'config-page-welcome' => 'Вітаем у MediaWiki!',
+ 'config-page-dbconnect' => 'Падключэньне да базы зьвестак',
+ 'config-page-upgrade' => 'Абнавіць існуючую ўстаноўку',
+ 'config-page-dbsettings' => 'Налады базы зьвестак',
+ 'config-page-name' => 'Назва',
+ 'config-page-options' => 'Налады',
+ 'config-page-install' => 'Усталяваць',
+ 'config-page-complete' => 'Зроблена!',
+ 'config-page-restart' => 'Пачаць усталяваньне зноў',
+ 'config-page-readme' => 'Дадатковыя зьвесткі',
+ 'config-page-releasenotes' => 'Заўвагі да выпуску',
+ 'config-page-copying' => 'Капіяваньне',
+ 'config-page-upgradedoc' => 'Абнаўленьне',
+ 'config-page-existingwiki' => 'Існуючая вікі',
+ 'config-help-restart' => 'Ці жадаеце выдаліць усе ўведзеныя зьвесткі і пачаць працэс усталяваньня зноў?',
+ 'config-restart' => 'Так, пачаць зноў',
+ 'config-welcome' => '== Праверка асяродзьдзя ==
+Праверка патрэбная для запэўніваньня, што гэтае асяродзьдзе слушнае для ўсталяваньня MediaWiki.
+Вам патрэбна будзе падаць усе вынікі праверкі, калі спатрэбіцца дапамога падчас усталяваньня.',
+ 'config-copyright' => "== Аўтарскае права і ўмовы ==
+
+$1
+
+This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but '''without any warranty'''; without even the implied warranty of '''merchantability''' or '''fitness for a particular purpose'''.
+See the GNU General Public License for more details.
+
+You should have received <doclink href=Copying>a copy of the GNU General Public License</doclink> along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. or [http://www.gnu.org/copyleft/gpl.html read it online].",
+ 'config-sidebar' => '* [http://www.mediawiki.org Хатняя старонка MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Даведка для ўдзельнікаў]
+* [http://www.mediawiki.org/wiki/Manual:Contents Даведка для адміністратараў]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Адказы на частыя пытаньні]
+----
+* <doclink href=Readme>Прачытайце</doclink>
+* <doclink href=ReleaseNotes>Паляпшэньні ў вэрсіі</doclink>
+* <doclink href=Copying>Капіяваньне</doclink>
+* <doclink href=UpgradeDoc>Абнаўленьне</doclink>',
+ 'config-env-good' => 'Асяродзьдзе было праверанае.
+Вы можаце ўсталёўваць MediaWiki.',
+ 'config-env-bad' => 'Асяродзьдзе было праверанае.
+Усталяваньне MediaWiki немагчымае.',
+ 'config-env-php' => 'Усталяваны PHP $1.',
+ 'config-env-php-toolow' => 'Усталяваны PHP $1.
+Але MediaWiki патрабуе PHP вэрсіі $2 ці навейшай.',
+ 'config-unicode-using-utf8' => 'Выкарыстоўваецца бібліятэка Unicode-нармалізацыі Браяна Вібэра',
+ 'config-unicode-using-intl' => 'Выкарыстоўваецца [http://pecl.php.net/intl intl пашырэньне з PECL] для Unicode-нармалізацыі',
+ 'config-unicode-pure-php-warning' => "'''Папярэджаньне''': [http://pecl.php.net/intl Пашырэньне intl з PECL] — ня слушнае для Unicode-нармалізацыі, цяпер выкарыстоўваецца марудная PHP-рэалізацыя.
+Калі ў Вас сайт з высокай наведваемасьцю, раім пачытаць пра [http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-нармалізацыю].",
+ 'config-unicode-update-warning' => "'''Папярэджаньне''': усталяваная вэрсія бібліятэкі для Unicode-нармалізацыі выкарыстоўвае састарэлую вэрсію бібліятэкі з [http://site.icu-project.org/ праекту ICU].
+Раім [http://www.mediawiki.org/wiki/Unicode_normalization_considerations абнавіць], калі ваш сайт будзе працаваць зь Unicode.",
+ 'config-no-db' => 'Немагчыма знайсьці слушны драйвэр базы зьвестак!',
+ 'config-no-db-help' => 'Вам трэба ўсталяваць драйвэр базы зьвестак для PHP.
+Падтрымліваюцца наступныя тыпы базаў зьвестак: $1.
+
+Калі вы выкарыстоўваеце агульны хостынг, запытайцеся ў свайго хостынг-правайдэра наконт усталяваньня патрабуемага драйвэр базы зьвестак.
+Калі Вы кампілявалі PHP самастойна, пераканфігуруйце і сабярыце яго з уключаным кліентам базаў зьвестак, напрыклад, <code>./configure --with-mysql</code>.
+Калі Вы ўсталёўвалі PHP з Debian/Ubuntu-рэпазытарыя, то вам трэба ўсталяваць дадаткова пакет <code>php5-mysql</code>',
+ 'config-no-fts3' => "'''Папярэджаньне''': SQLite створаны без модуля [http://sqlite.org/fts3.html FTS3], для гэтага ўнутранага інтэрфэйсу ня будзе даступная магчымасьць пошуку.",
+ 'config-register-globals' => "'''Папярэджаньне: уключаная опцыя PHP <code>[http://php.net/register_globals register_globals]</code>.'''
+'''Адключыце яе, калі можаце.'''
+MediaWiki будзе працаваць, але гэта панізіць узровень бясьпекі сэрвэра.",
+ 'config-magic-quotes-runtime' => "'''Фатальная памылка: уключаная опцыя PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''
+Гэтая опцыя псуе ўводны паток зьвестак непрадказальным чынам.
+Працяг усталяваньня альбо выкарыстаньне MediaWiki без адключэньня гэтай опцыі немагчымыя.",
+ 'config-magic-quotes-sybase' => "'''Фатальная памылка: рэжым [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] уключаны!'''
+Гэты рэжым шкодзіць уваходныя зьвесткі непрадказальным чынам.
+Працяг усталяваньня альбо выкарыстаньне MediaWiki немагчымыя, пакуль рэжым ня будзе выключаны.",
+ 'config-mbstring' => "'''Фатальная памылка: рэжым [http://www.php.net/manual/en/ref.info.php#mbstring.overload mbstring.func_overload] уключаны!'''
+Гэты рэжым выклікае памылкі і можа шкодзіць зьвесткі непрадказальным чынам.
+Працяг усталяваньня альбо выкарыстаньне MediaWiki немагчымыя, пакуль рэжым ня будзе выключаны.",
+ 'config-ze1' => "'''Фатальная памылка: рэжым [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] уключаны!'''
+Гэтая рэжым стварае вялікія праблемы ў працы MediaWiki.
+Працяг усталяваньня альбо выкарыстаньне MediaWiki немагчымыя, пакуль рэжым ня будзе выключаны.",
+ 'config-safe-mode' => "'''Папярэджаньне:''' [http://www.php.net/features.safe-mode бясьпечны рэжым] PHP уключаны.
+Гэта можа выклікаць праблемы, галоўным чынам падчас загрузак файлаў і ў падтрымцы <code>math</code>.",
+ 'config-xml-bad' => 'Ня знойдзены модуль XML для PHP.
+MediaWiki патрэбныя функцыі з гэтага модулю, таму MediaWiki ня будзе працаваць у гэтай канфігурацыі.
+Калі Вы выкарыстоўваеце Mandrake, усталюйце пакет php-xml.',
+ 'config-pcre' => 'Ня знойдзены модуль падтрымкі PCRE.
+MediaWiki для працы патрабуюцца функцыі рэгулярных выразаў у стылі Perl.',
+ 'config-pcre-no-utf8' => "'''Крытычная памылка''': модуль PCRE для PHP скампіляваны без падтрымкі PCRE_UTF8.
+MediaWiki патрабуе падтрымкі UTF-8 для слушнай працы.",
+ 'config-memory-raised' => 'Абмежаваньне на даступную для PHP памяць <code>memory_limit</code> было падвышанае з $1 да $2.',
+ 'config-memory-bad' => "'''Папярэджаньне:''' памер PHP <code>memory_limit</code> складае $1.
+Верагодна, гэта вельмі мала.
+Усталяваньне можа быць няўдалым!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] усталяваны',
+ 'config-apc' => '[http://www.php.net/apc APC] усталяваны',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] усталяваны',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] усталяваны',
+ 'config-no-cache' => "'''Папярэджаньне:''' немагчыма знайсьці [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] ці [http://www.iis.net/download/WinCacheForPhp WinCache].
+Аб’ектнае кэшаваньне ня ўключанае.",
+ 'config-diff3-bad' => 'GNU diff3 ня знойдзены.',
+ 'config-imagemagick' => 'Знойдзены ImageMagick: <code>$1</code>.
+Пасьля ўключэньня загрузак будзе ўключанае маштабаваньне выяваў.',
+ 'config-gd' => 'GD падтрымліваецца ўбудавана.
+Пасьля ўключэньня загрузак будзе ўключанае маштабаваньне выяваў.',
+ 'config-no-scaling' => 'Ні GD, ні ImageMagick ня знойдзеныя.
+Маштабаваньне выяваў будзе адключанае.',
+ 'config-no-uri' => "'''Памылка:''' Не магчыма вызначыць цяперашні URI.
+Усталяваньне спыненае.",
+ 'config-uploads-not-safe' => "'''Папярэджаньне:''' дырэкторыя для загрузак па змоўчваньні <code>$1</code> уразьлівая да выкананьня адвольнага коду.
+Хоць MediaWiki і правярае ўсе файлы перад захаваньнем, вельмі рэкамэндуецца [http://www.mediawiki.org/wiki/Manual:Security#Upload_security закрыць гэтую ўразьлівасьць] перад уключэньнем магчымасьці загрузкі файлаў.",
+ 'config-brokenlibxml' => 'У Вашай сыстэме ўсталяваныя PHP і libxml2 зь несумяшчальнымі вэрсіямі, што можа прывесьці да пашкоджаньня зьвестак MediaWiki і іншых ўэб-дастасаваньняў.
+Абнавіце PHP да вэрсіі 5.2.9 ці болей позьняй, а libxml2 да 2.7.3 ці болей позьняй ([http://bugs.php.net/bug.php?id=45996 паведамленьне пра памылку на сайце PHP]).
+Усталяваньне перарванае.',
+ 'config-using531' => 'PHP $1 не сумяшчальнае з MediaWiki з-за памылкі ў перадачы парамэтраў па ўказальніку да <code>__call()</code>.
+Абнавіце PHP да вэрсіі 5.3.2 ці болей позьняй, ці адкаціце да вэрсіі 5.3.0 каб гэта выправіць.
+Усталяваньне перарванае.',
+ 'config-db-type' => 'Тып базы зьвестак:',
+ 'config-db-host' => 'Хост базы зьвестак:',
+ 'config-db-host-help' => 'Калі сэрвэр Вашай базы зьвестак знаходзіцца на іншым сэрвэры, увядзіце тут імя хоста ці IP-адрас.
+
+Калі Вы набываеце shared-хостынг, Ваш хостынг-правайдэр мусіць даць Вам слушнае імя хоста базы зьвестак у сваёй дакумэнтацыі.
+
+Калі Вы усталёўваеце сэрвэр Windows з выкарыстаньнем MySQL, выкарыстаньне «localhost» можа не працаваць для назвы сэрвэра. У гэтым выпадку паспрабуйце пазначыць «127.0.0.1» для лякальнага IP-адраса.',
+ 'config-db-host-oracle' => 'TNS базы зьвестак:',
+ 'config-db-host-oracle-help' => 'Увядзіце слушнае [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm лякальнае імя злучэньня]; файл tnsnames.ora павінен быць бачным для гэтага ўсталяваньня.<br />Калі Вы выкарыстоўваеце кліенцкія бібліятэкі 10g ці больш новыя, Вы можаце таксама выкарыстоўваць мэтад наданьня назваў [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm лёгкае злучэньне].',
+ 'config-db-wiki-settings' => 'Ідэнтыфікацыя гэтай вікі',
+ 'config-db-name' => 'Назва базы зьвестак:',
+ 'config-db-name-help' => 'Выберыце імя для вызначэньня Вашай вікі.
+Яно ня мусіць зьмяшчаць прагалаў.
+
+Калі Вы набываеце shared-хостынг, Ваш хостынг-правайдэр мусіць надаць Вам ці пэўнае імя базы зьвестак для выкарыстаньня, ці магчымасьць ствараць базы зьвестак праз кантрольную панэль.',
+ 'config-db-name-oracle' => 'Схема базы зьвестак:',
+ 'config-db-account-oracle-warn' => 'Існуюць тры сцэнары ўсталяваньня Oracle як базы зьвестак для MediaWiki:
+
+Калі Вы жадаеце стварыць рахунак базы зьвестак як частку працэсу ўсталяваньня, калі ласка, падайце рахунак з роляй SYSDBA як рахунак базы зьвестак для ўсталяваньня і пазначце пажаданыя правы рахунку з доступам да Інтэрнэту, у адваротным выпадку Вы можаце таксама стварыць рахунак з доступам да Інтэрнэту ўручную і падаць толькі гэты рахунак (калі патрабуюцца правы для стварэньня схемы аб’ектаў) ці падайце два розных рахункі, адзін з правамі на стварэньне і адзін з абмежаваньнямі для доступу да Інтэрнэту.
+
+Скрыпт для стварэньня рахунку з патрабуемымі правамі можна знайсьці ў дырэкторыі гэтага ўсталяваньня «maintenance/oracle/». Памятайце, што выкарыстаньне рахунку з абмежаваньнямі адключыць усе падтрымліваемыя магчымасьці даступныя па змоўчваньні.',
+ 'config-db-install-account' => 'Імя карыстальніка для ўсталяваньня',
+ 'config-db-username' => 'Імя карыстальніка базы зьвестак:',
+ 'config-db-password' => 'Пароль базы зьвестак:',
+ 'config-db-password-empty' => 'Калі ласка, увядзіце пароль для новага карыстальніка базы зьвестак: $1.
+Магчыма стварыць карыстальніка без паролю, але гэта небясьпечна.',
+ 'config-db-install-username' => 'Увядзіце імя карыстальніка, якое будзе выкарыстоўвацца для злучэньня з базай зьвестак падчас усталяваньня. Гэта не назва рахунку MediaWiki; гэта імя карыстальніка Вашай базы зьвестак.',
+ 'config-db-install-password' => 'Увядзіце пароль, які будзе выкарыстоўвацца для злучэньня з базай зьвестак падчас усталяваньня. Гэта не пароль рахунку MediaWiki; гэта пароль Вашай базы зьвестак.',
+ 'config-db-install-help' => 'Увядзіце імя карыстальніка і пароль, якія будуць выкарыстаныя для далучэньня да базы зьвестак падчас працэсу ўсталяваньня.',
+ 'config-db-account-lock' => 'Выкарыстоўваць тыя ж імя карыстальніка і пароль пасьля ўсталяваньня',
+ 'config-db-wiki-account' => 'Імя карыстальніка для працы',
+ 'config-db-wiki-help' => 'Увядзіце імя карыстальніка і пароль, якія будуць выкарыстаныя для далучэньня да базы зьвестак падчас працы (пасьля ўсталяваньня).
+Калі рахунак ня створаны, а рахунак для ўсталяваньня мае значныя правы, гэты рахунак будзе створаны зь мінімальна патрэбнымі для працы вікі правамі.',
+ 'config-db-prefix' => 'Прэфікс табліцаў базы зьвестак:',
+ 'config-db-prefix-help' => 'Калі Вы разьдзяляеце адну базу зьвестак паміж некалькімі вікі, ці паміж MediaWiki і іншым вэб-дастасаваньнем, можаце вызначыць прэфікс назваў табліцаў для пазьбяганьня канфліктаў.
+Пазьбягайце прагалаў.
+
+Гэтае поле звычайна пакідаецца пустым.',
+ 'config-db-charset' => 'Кадаваньне базы зьвестак',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 UTF-8 з адваротнай сумяшчальнасьцю',
+ 'config-charset-help' => "'''Папярэджаньне:''' калі Вы выкарыстоўваеце '''UTF-8 з адваротнай сумяшчальнасьцю''' на MySQL 4.1+ і зробіце рэзэрвовую копію праз <code>mysqldump</code>, ён можа зьнішчыць усе не-ASCII-сымбалі беспаваротна!
+
+У '''бінарным (binary)''' рэжыме MediaWiki захоўвае тэксты ў UTF-8 у палёх тыпу binary.
+Гэты рэжым болей эфэктыўны за рэжым MySQL UTF-8 і дазваляе выкарыстоўваць увесь абсяг сымбаляў Unicode.
+У рэжыме '''UTF-8''' MySQL будзе ведаць, у якім кадаваньне Вы зьмяшчаеце зьвесткі, і будзе вяртаць іх у адпаведным кадаваньні,
+але MySQL ня можа ўтрымліваць сымбалі па-за [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Стандартным шматмоўным пластом] сымбаляў Unicode.",
+ 'config-mysql-old' => 'Патрабуецца MySQL $1 ці навейшая, усталяваная вэрсія $2.',
+ 'config-db-port' => 'Порт базы зьвестак:',
+ 'config-db-schema' => 'Схема для MediaWiki',
+ 'config-db-schema-help' => 'Гэтая схема слушная ў большасьці выпадкаў.
+Зьмяняйце яе толькі тады, калі Вы ведаеце, што гэта неабходна.',
+ 'config-sqlite-dir' => 'Дырэкторыя зьвестак SQLite:',
+ 'config-sqlite-dir-help' => "SQLite захоўвае ўсе зьвесткі ў адзіным файле.
+
+Пададзеная Вамі дырэкторыя павінна быць даступнай да запісу вэб-сэрвэрам падчас усталяваньня.
+
+Яна '''ня''' мусіць быць даступнай праз Сеціва, вось чаму мы не захоўваем яе ў адным месцы з файламі PHP.
+
+Праграма ўсталяваньня дадаткова створыць файл <code>.htaccess</code>, але калі ён не выкарыстоўваецца, хто заўгодна зможа атрымаць зьвесткі з базы зьвестак.
+Гэта ўключае як прыватныя зьвесткі ўдзельнікаў (адрасы электроннай пошты, хэшы пароляў), гэтак і выдаленыя вэрсіі старонак і іншыя зьвесткі, доступ да якіх маецца абмежаваны.
+
+Падумайце над тым, каб зьмяшчаць базу зьвестак у іншым месцы, напрыклад у <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Прастора табліцаў па змоўчваньні:',
+ 'config-oracle-temp-ts' => 'Часовая прастора табліцаў:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki падтрымлівае наступныя сыстэмы базаў зьвестак:
+
+$1
+
+Калі Вы ня бачыце сыстэму базаў зьвестак, якую Вы спрабуеце выкарыстоўваць ў сьпісе ніжэй, перайдзіце па спасылцы інструкцыі, якая знаходзіцца ніжэй, каб уключыць падтрымку.',
+ 'config-support-mysql' => '* $1 зьяўляецца галоўнай мэтай MediaWiki і падтрымліваецца лепей за ўсё ([http://www.php.net/manual/en/mysql.installation.php як кампіляваць PHP з падтрымкай MySQL])',
+ 'config-support-postgres' => '* $1 — вядомая сыстэма базы зьвестак з адкрытым кодам, якая зьяўляецца альтэрнатывай MySQL ([http://www.php.net/manual/en/pgsql.installation.php як кампіляваць PHP з падтрымкай PostgreSQL]). Яна можа ўтрымліваць дробныя памылкі, і не рэкамэндуецца выкарыстоўваць яе для працуючых праектаў.',
+ 'config-support-sqlite' => '* $1 — невялікая сыстэма базы зьвестак, якая мае вельмі добрую падтрымку. ([http://www.php.net/manual/en/pdo.installation.php як кампіляваць PHP з падтрымкай SQLite], выкарыстоўвае PDO)',
+ 'config-support-oracle' => '* $1 зьяўляецца камэрцыйнай прафэсійнай базай зьвестак. ([http://www.php.net/manual/en/oci8.installation.php Як скампіляваць PHP з падтрымкай OCI8])',
+ 'config-header-mysql' => 'Налады MySQL',
+ 'config-header-postgres' => 'Налады PostgreSQL',
+ 'config-header-sqlite' => 'Налады SQLite',
+ 'config-header-oracle' => 'Налады Oracle',
+ 'config-invalid-db-type' => 'Няслушны тып базы зьвестак',
+ 'config-missing-db-name' => 'Вы павінны ўвесьці значэньне парамэтру «Імя базы зьвестак»',
+ 'config-missing-db-host' => 'Вы павінны ўвесьці значэньне парамэтру «Хост базы зьвестак»',
+ 'config-missing-db-server-oracle' => 'Вы павінны ўвесьці значэньне парамэтру «TNS базы зьвестак»',
+ 'config-invalid-db-server-oracle' => 'Няслушнае TNS базы зьвестак «$1».
+Назва можа ўтрымліваць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), сымбалі падкрэсьліваньня(_) і кропкі (.).',
+ 'config-invalid-db-name' => 'Няслушная назва базы зьвестак «$1».
+Назва можа ўтрымліваць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), сымбалі падкрэсьліваньня(_) і працяжнікі (-).',
+ 'config-invalid-db-prefix' => 'Няслушны прэфікс базы зьвестак «$1».
+Ён можа зьмяшчаць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), сымбалі падкрэсьліваньня (_) і працяжнікі (-).',
+ 'config-connection-error' => '$1.
+
+Праверце хост, імя карыстальніка і пароль ніжэй і паспрабуйце зноў.',
+ 'config-invalid-schema' => 'Няслушная схема для MediaWiki «$1».
+Выкарыстоўвайце толькі ASCII-літары (a-z, A-Z), лічбы (0-9) і сымбалі падкрэсьліваньня (_).',
+ 'config-db-sys-create-oracle' => 'Праграма ўсталяваньня падтрымлівае толькі выкарыстаньне рахунку SYSDBA для стварэньня новага рахунку.',
+ 'config-db-sys-user-exists-oracle' => 'Рахунак карыстальніка «$1» ужо існуе. SYSDBA можа выкарыстоўвацца толькі для стварэньня новых рахункаў!',
+ 'config-postgres-old' => 'Патрабуецца PostgreSQL $1 ці навейшая, усталяваная вэрсія $2.',
+ 'config-sqlite-name-help' => 'Выберыце назву, якая будзе ідэнтыфікаваць Вашую вікі.
+Не выкарыстоўвайце прагалы ці злучкі.
+Назва будзе выкарыстоўвацца ў назьве файла зьвестак SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Немагчыма стварыць дырэкторыю зьвестак <code><nowiki>$1</nowiki></code>, таму што бацькоўская дырэкторыя <code><nowiki>$2</nowiki></code> абароненая ад запісаў вэб-сэрвэра.
+
+Праграма ўсталяваньня вызначыла карыстальніка, які запусьціў вэб-сэрвэр.
+Дазвольце запісы ў дырэкторыю <code><nowiki>$3</nowiki></code> для працягу.
+У сыстэме Unix/Linux зрабіце:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Немагчыма стварыць дырэкторыю зьвестак <code><nowiki>$1</nowiki></code>, таму што бацькоўская дырэкторыя <code><nowiki>$2</nowiki></code> абароненая ад запісаў вэб-сэрвэра.
+
+Праграма ўсталяваньня вызначыла карыстальніка, які запусьціў вэб-сэрвэр.
+Дазвольце яму (і іншым) запісы ў дырэкторыю <code><nowiki>$3</nowiki></code> для працягу.
+У сыстэме Unix/Linux зрабіце:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Памылка падчас стварэньня дырэкторыі «$1».
+Праверце шлях і паспрабуйце зноў.',
+ 'config-sqlite-dir-unwritable' => 'Запіс у дырэкторыю «$1» немагчымы.
+Зьмяніце налады доступу, каб вэб-сэрвэр меў правы на запіс, і паспрабуйце зноў.',
+ 'config-sqlite-connection-error' => '$1.
+
+Праверце дырэкторыю для зьвестак, назву базы зьвестак і паспрабуйце зноў.',
+ 'config-sqlite-readonly' => 'Файл <code>$1</code> недаступны для запісу.',
+ 'config-sqlite-cant-create-db' => 'Немагчыма стварыць файл базы зьвестак <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'PHP бракуе падтрымкі FTS3 — табліцы пагаршаюцца',
+ 'config-can-upgrade' => "У гэтай базе зьвестак ёсьць табліцы MediaWiki.
+Каб абнавіць іх да MediaWiki $1, націсьніце '''Працягнуць'''.",
+ 'config-upgrade-done' => "Абнаўленьне завершанае.
+
+Цяпер Вы можаце [$1 пачаць выкарыстаньне вікі].
+
+Калі Вы жадаеце рэгенэраваць <code>LocalSettings.php</code>, націсьніце кнопку ніжэй.
+Гэтае дзеяньне '''не рэкамэндуецца''', калі Вы ня маеце праблемаў у працы вікі.",
+ 'config-upgrade-done-no-regenerate' => 'Абнаўленьне скончанае.
+
+Цяпер Вы можаце [$1 пачаць працу з вікі].',
+ 'config-regenerate' => 'Рэгенэраваць LocalSettings.php →',
+ 'config-show-table-status' => "Запыт 'SHOW TABLE STATUS' не атрымаўся!",
+ 'config-unknown-collation' => "'''Папярэджаньне:''' база зьвестак выкарыстоўвае нераспазнанае супастаўленьне.",
+ 'config-db-web-account' => 'Рахунак базы зьвестак для вэб-доступу',
+ 'config-db-web-help' => 'Выберыце імя карыстальніка і пароль, які выкарыстоўваецца вэб-сэрвэрам для злучэньня з сэрвэрам базы зьвестак, падчас звычайных апэрацыяў вікі.',
+ 'config-db-web-account-same' => 'Выкарыстоўваць той жа рахунак, што для ўсталяваньня',
+ 'config-db-web-create' => 'Стварыць рахунак, калі ён яшчэ не існуе',
+ 'config-db-web-no-create-privs' => 'Рахунак, які Вы пазначылі для ўсталяваньня ня мае правоў для стварэньня рахунку.
+Рахунак, які Вы пазначылі тут, мусіць ужо існаваць.',
+ 'config-mysql-engine' => 'Рухавік сховішча:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' — звычайна найбольш слушны варыянт, таму што добра падтрымлівае паралелізм.
+
+'''MyISAM''' можа быць хутчэйшай у вікі з адным удзельнікам, ці толькі для чытаньня.
+Базы зьвестак на MyISAM вядомыя тым, што ў іх зьвесткі шкодзяцца нашмат часьцей за InnoDB.",
+ 'config-mysql-charset' => 'Кадаваньне базы зьвестак:',
+ 'config-mysql-binary' => 'Двайковае',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "У '''двайковым рэжыме''', MediaWiki захоўвае тэкст у кадаваньні UTF-8 у базе зьвестак у двайковых палях.
+Гэта болей эфэктыўна за рэжым MySQL UTF-8, і дазваляе Вам выкарыстоўваць увесь дыяпазон сымбаляў Unicode.
+
+У '''рэжыме UTF-8''', MySQL ведае, якая табліцы сымбаляў выкарыстоўваецца ў Вашых зьвестках, і можа адпаведна прадстаўляць і канвэртаваць іх, але гэта не дазволіць Вам захоўваць сымбалі па-за межамі [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базавага шматмоўнага дыяпазону].",
+ 'config-site-name' => 'Назва вікі:',
+ 'config-site-name-help' => 'Назва будзе паказвацца ў загалоўку браўзэра і ў некаторых іншых месцах.',
+ 'config-site-name-blank' => 'Увядзіце назву сайта.',
+ 'config-project-namespace' => 'Прастора назваў праекту:',
+ 'config-ns-generic' => 'Праект',
+ 'config-ns-site-name' => 'Такая ж, як і назва вікі: $1',
+ 'config-ns-other' => 'Іншая (вызначце)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-project-namespace-help' => "Па прыкладу Вікіпэдыі, шматлікія вікі трымаюць уласныя старонкі з правіламі асобна ад старонак са зьместам, у «'''прасторы назваў праекту'''».
+Усе назвы старонак у гэтай прасторы назваў пачынаюцца з прыстаўкі, якую Вы можаце пазначыць тут.
+Традыцыйна, гэтая прыстаўка вытворная ад назвы вікі, яле яна ня можа ўтрымліваць некаторыя сымбалі, такія як «#» ці «:».",
+ 'config-ns-invalid' => 'Пададзеная няслушная прастора назваў «<nowiki>$1</nowiki>».
+Падайце іншую прастору назваў праекту.',
+ 'config-ns-conflict' => 'Пазначаная прастора назваў «<nowiki>$1</nowiki>» канфліктуе з прасторай назваў MediaWiki па змоўчваньні.
+Пазначце іншую прастору назваў праекту.',
+ 'config-admin-box' => 'Рахунак адміністратара',
+ 'config-admin-name' => 'Вашае імя:',
+ 'config-admin-password' => 'Пароль:',
+ 'config-admin-password-confirm' => 'Пароль яшчэ раз:',
+ 'config-admin-help' => 'Увядзіце тут Вашае імя ўдзельніка, напрыклад «Янка Кавалевіч».
+Гэтае імя будзе выкарыстоўвацца для ўваходу ў вікі.',
+ 'config-admin-name-blank' => 'Увядзіце імя адміністратара.',
+ 'config-admin-name-invalid' => 'Пададзенае няслушнае імя ўдзельніка «<nowiki>$1</nowiki>».
+Падайце іншае імя ўдзельніка.',
+ 'config-admin-password-blank' => 'Увядзіце пароль рахунку адміністратара.',
+ 'config-admin-password-same' => 'Пароль ня можа быць аднолькавым зь іменем удзельніка.',
+ 'config-admin-password-mismatch' => 'Уведзеныя Вамі паролі не супадаюць.',
+ 'config-admin-email' => 'Адрас электроннай пошты:',
+ 'config-admin-email-help' => 'Увядзіце тут адрас электроннай пошты, каб атрымліваць электронныя лісты ад іншых удзельнікаў вікі, скідваць Ваш пароль і атрымліваць абвешчаньні пра зьмены старонак, якія знаходзяцца ў Вашым сьпісе назіраньня. Вы можаце пакінуць гэтае поле пустым.',
+ 'config-admin-error-user' => 'Унутраная памылка падчас стварэньня рахунку адміністратара зь іменем «<nowiki>$1</nowiki>».',
+ 'config-admin-error-password' => 'Унутраная памылка падчас устаноўкі паролю для адміністратара «<nowiki>$1</nowiki>»: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Вы ўвялі няслушны адрас электроннай пошты',
+ 'config-subscribe' => 'Падпісацца на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў].',
+ 'config-subscribe-help' => 'Гэта ня вельмі актыўны сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў, які ўключаючы важныя навіны пра бясьпеку.
+Вам неабходна падпісацца на яго і абнавіць Вашае ўсталяваньне MediaWiki, калі зьявяцца новыя вэрсіі.',
+ 'config-almost-done' => 'Вы амаль што скончылі!
+Астатнія налады можна прапусьціць і пачаць усталяваньне вікі.',
+ 'config-optional-continue' => 'Задаць болей пытаньняў.',
+ 'config-optional-skip' => 'Хопіць, проста ўсталяваць вікі.',
+ 'config-profile' => 'Профіль правоў удзельніка:',
+ 'config-profile-wiki' => 'Традыцыйная вікі',
+ 'config-profile-no-anon' => 'Патрэбнае стварэньне рахунку',
+ 'config-profile-fishbowl' => 'Толькі для аўтарызаваных рэдактараў',
+ 'config-profile-private' => 'Прыватная вікі',
+ 'config-profile-help' => "Вікі працуюць лепей, калі Вы дазваляеце як мага большай колькасьці людзей рэдагаваць яе.
+У MediaWiki вельмі лёгка праглядаць апошнія зьмены і выпраўляць любыя пашкоджаньні зробленыя недасьведчанымі ўдзельнікамі альбо вандаламі.
+
+Тым ня менш, многія лічаць, што MediaWiki можа быць карыснай ў шматлікіх іншых ролях, і часта вельмі нялёгка растлумачыць усім перавагі выкарыстаньня тэхналёгіяў вікі.
+Таму Вы маеце выбар.
+
+'''{{int:config-profile-wiki}}''' дазваляе рэдагаваць усім, нават без уваходу ў сыстэму.
+Вікі з '''{{int:config-profile-no-anon}}''' дазваляе дадатковую адказнасьць, але можа адштурхнуць некаторых патэнцыйных удзельнікаў.
+
+Сцэнар '''{{int:config-profile-fishbowl}}''' дазваляе рэдагаваць зацьверджаным удзельнікам, але ўсе могуць праглядаць старонкі іх гісторыю.
+'''{{int:config-profile-private}}''' дазваляе праглядаць і рэдагаваць старонкі толькі зацьверджаным удзельнікам.
+
+Больш складаныя правы ўдзельнікаў даступныя пасьля ўсталяваньня, глядзіце [http://www.mediawiki.org/wiki/Manual:User_rights адпаведную старонку дакумэнтацыі].",
+ 'config-license' => 'Аўтарскія правы і ліцэнзія:',
+ 'config-license-none' => 'Без інфармацыі пра ліцэнзію',
+ 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
+ 'config-license-cc-0' => 'Creative Commons Zero',
+ 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
+ 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 ці болей позьняя',
+ 'config-license-pd' => 'Грамадзкі набытак',
+ 'config-license-cc-choose' => 'Выберыце іншую ліцэнзію Creative Commons',
+ 'config-license-help' => "Шматлікія адкрытыя вікі разьмяшчаюць унёскі на ўмовах ліцэнзіі [http://freedomdefined.org/Definition вольнай ліцэнзіі].
+Гэта дазваляе ствараць сэнс супольнай уласнасьці і садзейнічае доўгатэрміновым унёскам.
+Гэта не неабходна для прыватных і карпаратыўных вікі.
+
+Калі Вы жадаеце выкарыстоўваць тэкст з Вікіпэдыі, і жадаеце каб Вікіпэдыя магла прынімаць тэкст скапіяваны з Вашай вікі, Вам неабходна выбраць ліцэнзію '''Creative Commons Attribution Share Alike'''.
+
+Раней Вікіпэдыя выкарыстоўвала ліцэнзію GNU Free Documentation. Яна ўсё яшчэ дзейнічае, але яна ўтрымлівае некаторыя моманты, якія ўскладняюць паўторнае выкарыстоўваньне і інтэрпрэтацыю матэрыялаў.",
+ 'config-email-settings' => 'Налады электроннай пошты',
+ 'config-enable-email' => 'Дазволіць выходзячыя электронныя лісты',
+ 'config-enable-email-help' => 'Калі Вы жадаеце, каб працавала электронная пошта, неабходна сканфігураваць PHP [http://www.php.net/manual/en/mail.configuration.php адпаведным чынам].
+Калі Вы не жадаеце выкарыстоўваць магчымасьці электроннай пошты, Вы можаце яе адключыць.',
+ 'config-email-user' => 'Дазволіць электронную пошту для сувязі паміж удзельнікамі',
+ 'config-email-user-help' => 'Дазволіць усім удзельнікам дасылаць адзін аднаму электронныя лісты, калі ўключаная адпаведная магчымасьць ў іх наладах.',
+ 'config-email-usertalk' => 'Уключыць абвяшчэньні пра паведамленьні на старонцы абмеркаваньня',
+ 'config-email-usertalk-help' => 'Дазваляе ўдзельнікам атрымліваць абвяшчэньні пра зьмены на старонцы абмеркаваньня, калі гэтая магчымасьць уключаная ў іх наладах.',
+ 'config-email-watchlist' => 'Уключыць абвяшчэньні пра зьмены ў сьпісе назіраньня',
+ 'config-email-watchlist-help' => 'Дазваляе ўдзельнікам атрымліваць абвяшчэньні пра зьмены ў іх сьпісе назіраньня, калі гэтая магчымасьць уключаная ў іх наладах.',
+ 'config-email-auth' => 'Уключыць аўтэнтыфікацыю праз электронную пошту',
+ 'config-email-auth-help' => "Калі гэтая магчымасьць уключаная, удзельнікі павінны пацьвердзіць іх адрас электроннай пошты праз спасылку, якая дасылаецца ім праз электронную пошту. Яна дасылаецца і падчас зьмены адрасу электроннай пошты.
+Толькі аўтэнтыфікаваныя адрасы электроннай пошты могуць атрымліваць электронныя лісты ад іншых удзельнікаў, ці зьмяняць абвяшчэньні дасылаемыя праз электронную пошту.
+Уключэньне гэтай магчымасьці '''рэкамэндуецца''' для адкрытых вікі, з-за магчымых злоўжываньняў магчымасьцямі электроннай пошты.",
+ 'config-email-sender' => 'Адрас электроннай пошты для вяртаньня:',
+ 'config-email-sender-help' => 'Увядзіце адрас электроннай пошты для вяртаньня ў якасьці адрасу дасылаемых электронных лістоў.
+Сюды будуць дасылацца неатрыманыя электронныя лісты.
+Шматлікія паштовыя сэрвэры патрабуюць, каб хаця б назва дамэну была слушнай.',
+ 'config-upload-settings' => 'Загрузкі выяваў і файлаў',
+ 'config-upload-enable' => 'Дазволіць загрузку файлаў',
+ 'config-upload-help' => 'Дазвол загрузкі файлаў можа патэнцыйна пагражаць бясьпекі сэрвэра.
+Дадатковую інфармацыю можна атрымаць ў [http://www.mediawiki.org/wiki/Manual:Security разьдзеле бясьпекі].
+
+Каб дазволіць загрузку файлаў, зьмяніце рэжым падкаталёга <code>images</code> у карэннай дырэкторыі MediaWiki так, каб ўэб-сэрвэр меў доступ на запіс.
+Потым дазвольце гэтую магчымасьць.',
+ 'config-upload-deleted' => 'Дырэкторыя для выдаленых файлаў:',
+ 'config-upload-deleted-help' => 'Выберыце дырэкторыю, у якой будуць захоўвацца выдаленыя файлы.
+У ідэальным выпадку, яна не павінна мець доступу з Інтэрнэту.',
+ 'config-logo' => 'URL-адрас лягатыпу:',
+ 'config-logo-help' => 'Афармленьне MediaWiki па змоўчваньні уключае прастору для лягатыпу памерам 135×160 піксэляў у верхнім левым куце.
+Загрузіце выяву адпаведнага памеру, і увядзіце тут URL-адрас.
+
+Калі Вы не жадаеце мець ніякага лягатыпу, пакіньце гэтае поле пустым.',
+ 'config-instantcommons' => 'Дазволіць Instant Commons',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] — магчымасьць, якая дазваляе вікі выкарыстоўваць выявы, гукі і іншыя мэдыя, якія знаходзяцца на сайце [http://commons.wikimedia.org/ Wikimedia Commons].
+Каб гэта зрабіць, MediaWiki патрабуе доступу да Інтэрнэту.
+
+Каб даведацца болей пра гэтую магчымасьць, уключаючы інструкцыю пра тое, як яе ўстанавіць ў любой вікі, акрамя Wikimedia Commons, глядзіце [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos дакумэнтацыю].',
+ 'config-cc-error' => 'Выбар ліцэнзіі Creative Commons ня даў вынікаў.
+Увядзіце назву ліцэнзіі ўручную.',
+ 'config-cc-again' => 'Выберыце яшчэ раз…',
+ 'config-cc-not-chosen' => 'Выберыце, якую ліцэнзію Creative Commons Вы жадаеце выкарыстоўваць і націсьніце «працягваць».',
+ 'config-advanced-settings' => 'Дадатковыя налады',
+ 'config-cache-options' => 'Налады кэшаваньня аб’ектаў:',
+ 'config-cache-help' => 'Кэшаваньне аб’ектаў павялічвае хуткасьць працы MediaWiki праз кэшаваньне зьвестак, якія часта выкарыстоўваюцца.
+Вельмі рэкамэндуем уключыць гэта для сярэдніх і буйных сайтаў, таксама будзе карысна для дробных сайтаў.',
+ 'config-cache-none' => 'Без кэшаваньня (ніякія магчымасьці не страчваюцца, але хуткасьць працы буйных сайтаў можа зьнізіцца)',
+ 'config-cache-accel' => 'Кэшаваньне аб’ектаў PHP (APC, eAccelerator, XCache ці WinCache)',
+ 'config-cache-memcached' => 'Выкарыстоўваць Memcached (патрабуе дадатковай канфігурацыі)',
+ 'config-memcached-servers' => 'Сэрвэры memcached:',
+ 'config-memcached-help' => 'Сьпіс IP-адрасоў, якія будуць выкарыстоўвацца Memcached.
+Адрасы павінны быць у асобным радку з пазначэньнем порту, які будзе выкарыстоўвацца. Напрыклад:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Вы выбралі Memcached у якасьці тыпу Вашага кэша, але не пазначылі ніякага сэрвэра',
+ 'config-memcache-badip' => 'Вы ўвялі няслушны IP-адрас для Memcached: $1',
+ 'config-memcache-noport' => 'Вы не пазначылі порт для выкарыстаньня сэрвэрам Memcached: $1.
+Калі Вы ня ведаеце порт, то па змоўчваньні выкарыстоўваецца 11211',
+ 'config-memcache-badport' => 'Нумар порту Memcached павінен быць паміж $1 і $2',
+ 'config-extensions' => 'Пашырэньні',
+ 'config-extensions-help' => 'Пашырэньні пададзеныя вышэй, былі знойдзеныя ў Вашай дырэкторыі <code>./extensions</code>.
+
+Яны могуць патрабаваць дадатковых наладаў, але іх можна ўключыць зараз',
+ 'config-install-alreadydone' => "'''Папярэджаньне:''' здаецца, што Вы ўжо ўсталёўвалі MediaWiki і спрабуеце зрабіць гэтай зноў.
+Калі ласка, перайдзіце на наступную старонку.",
+ 'config-install-begin' => 'Пасьля націску кнопкі «{{int:config-continue}}» пачнецца ўсталяваньне MediaWiki.
+Калі Вы жадаеце што-небудзь зьмяніць, націсьніце кнопку «Вярнуцца».',
+ 'config-install-step-done' => 'зроблена',
+ 'config-install-step-failed' => 'не атрымалася',
+ 'config-install-extensions' => 'Уключаючы пашырэньні',
+ 'config-install-database' => 'Налада базы зьвестак',
+ 'config-install-pg-schema-not-exist' => 'Схема PostgreSQL не існуе',
+ 'config-install-pg-schema-failed' => 'Немагчыма стварыць табліцу.
+Упэўніцеся, што карыстальнік «$1» можа пісаць у схему «$2».',
+ 'config-install-pg-commit' => 'Захаваньне зьменаў',
+ 'config-install-pg-plpgsql' => 'Праверка падтрымкі мовы PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Вам неабходна ўсталяваць падтрымку мовы PL/pgSQL у базе зьвестак $1',
+ 'config-pg-no-create-privs' => 'Рахунак, які Вы пазначылі для ўсталяваньня ня мае дастаткова правоў для стварэньня рахунку.',
+ 'config-install-user' => 'Стварэньне карыстальніка базы зьвестак',
+ 'config-install-user-alreadyexists' => 'Удзельнік «$1» ужо існуе',
+ 'config-install-user-create-failed' => 'Немагчыма стварыць ўдзельніка «$1»: $2',
+ 'config-install-user-grant-failed' => 'Немагчыма даць правы удзельніку «$1»: $2',
+ 'config-install-tables' => 'Стварэньне табліцаў',
+ 'config-install-tables-exist' => "'''Папярэджаньне''': Выглядае, што табліцы MediaWiki ужо існуюць.
+Стварэньне прапушчанае.",
+ 'config-install-tables-failed' => "'''Памылка''': немагчыма стварыць табліцы з-за наступнай памылкі: $1",
+ 'config-install-interwiki' => 'Запаўненьне табліцы інтэрвікі па змоўчваньні',
+ 'config-install-interwiki-list' => 'Немагчыма знайсьці файл <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Папярэджаньне''': выглядае, што табліца інтэрвікі ўжо запоўненая.
+Сьпіс па змоўчваньні прапушчаны.",
+ 'config-install-stats' => 'Ініцыялізацыі статыстыкі',
+ 'config-install-keys' => 'Стварэньне сакрэтнага ключа',
+ 'config-insecure-keys' => "'''Папярэджаньне:''' {{PLURAL:$2|Ключ бясьпекі $1 створаны|Ключы бясьпекі $1 створаныя}} падчас усталяваньня, не зьяўляюцца поўнасьцю бясьпечнымі. Рэкамэндуецца зьмяніць {{PLURAL:$2|яго ўручную|іх уручную}}.",
+ 'config-install-sysop' => 'Стварэньне рахунку адміністратара',
+ 'config-install-subscribe-fail' => 'Немагчыма падпісацца на «mediawiki-announce»',
+ 'config-install-mainpage' => 'Стварэньне галоўнай старонкі са зьместам па змоўчваньні',
+ 'config-install-extension-tables' => 'Стварэньне табліцаў для ўключаных пашырэньняў',
+ 'config-install-mainpage-failed' => 'Немагчыма ўставіць галоўную старонку: $1',
+ 'config-install-done' => "'''Віншуем!'''
+Вы пасьпяхова ўсталявалі MediaWiki.
+
+Праграма ўсталяваньня стварыла файл <code>LocalSettings.php</code>.
+Ён утрымлівае ўсе Вашыя налады.
+
+Вам неабходна загрузіць яго і захаваць у карэнную дырэкторыю Вашай вікі (у тую ж самую дырэкторыю, дзе знаходзіцца index.php). Загрузка павінна пачацца аўтаматычна.
+
+Калі загрузка не пачалася, ці Вы яе адмянілі, Вы можаце перазапусьціць яе націснуўшы на спасылку ніжэй:
+
+$3
+
+'''Заўвага''': калі Вы гэтага ня зробіце зараз, то створаны файл ня будзе даступны Вам потым, калі Вы выйдзеце з праграмы ўсталяваньня без яго загрузкі.
+
+Калі Вы гэта зробіце, Вы можаце '''[$2 ўвайсьці ў Вашую вікі]'''.",
+ 'config-download-localsettings' => 'Загрузіць LocalSettings.php',
+ 'config-help' => 'дапамога',
+);
+
+/** Bulgarian (Български)
+ * @author DCLXVI
+ */
+$messages['bg'] = array(
+ 'config-desc' => 'Инсталатор на МедияУики',
+ 'config-title' => 'Инсталиране на МедияУики $1',
+ 'config-information' => 'Информация',
+ 'config-localsettings-upgrade' => 'Беше открит файл <code>LocalSettings.php</code>.
+За надграждане на съществуващата инсталация, необходимо е в кутията по-долу да се въведе стойността на <code>$wgUpgradeKey</code>.
+Тази информация е налична в LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Беше открит файл LocalSettings.php.
+За надграждане на наличната инсталация, необходимо е да се стартира update.php',
+ 'config-localsettings-key' => 'Ключ за надграждане:',
+ 'config-localsettings-badkey' => 'Предоставеният ключ е неправилен.',
+ 'config-upgrade-key-missing' => 'Беше открита съществуваща инсталация на МедияУики.
+За надграждане на съществуващата инсталация, необходимо е да се постави следният ред в края на файла LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'Съществуващият файл LocalSettings.php изглежда непълен.
+Променливата $1 не е зададена.
+Необходимо е да се редактира файлът LocalSettings.php и да се зададе променливата, след което да се натисне "Продължаване".',
+ 'config-localsettings-connection-error' => 'Възникна грешка при свързване с базата от данни чрез данните, посочени в LocalSettings.php или AdminSettings.php. Необходимо е да се коригират тези настройки преди повторен опит за свързване.
+
+$1',
+ 'config-session-error' => 'Грешка при създаване на сесия: $1',
+ 'config-your-language' => 'Вашият език:',
+ 'config-your-language-help' => 'Избиране на език за използване по време на инсталацията.',
+ 'config-wiki-language' => 'Език на уикито:',
+ 'config-wiki-language-help' => 'Избиране на език, на който ще е основното съдържание на уикито.',
+ 'config-back' => '← Връщане',
+ 'config-continue' => 'Продължаване →',
+ 'config-page-language' => 'Език',
+ 'config-page-welcome' => 'Добре дошли в МедияУики!',
+ 'config-page-dbconnect' => 'Свързване с базата от данни',
+ 'config-page-upgrade' => 'Надграждане на съществуваща инсталация',
+ 'config-page-dbsettings' => 'Настройки на базата от данни',
+ 'config-page-name' => 'Име',
+ 'config-page-options' => 'Настройки',
+ 'config-page-install' => 'Инсталиране',
+ 'config-page-complete' => 'Готово!',
+ 'config-page-restart' => 'Рестартиране на инсталацията',
+ 'config-page-readme' => 'Информация за софтуера',
+ 'config-page-releasenotes' => 'Бележки за версията',
+ 'config-page-copying' => 'Лицензно споразумение',
+ 'config-page-upgradedoc' => 'Надграждане',
+ 'config-page-existingwiki' => 'Съществуващо уики',
+ 'config-help-restart' => 'Необходимо е потвърждение за изтриване на всички въведени и съхранени данни и започване отначало на процеса по инсталация.',
+ 'config-restart' => 'Да, започване отначало',
+ 'config-welcome' => '=== Проверка на средата ===
+Извършени бяха основни проверки, за да се провери дали средата е подходяща за инсталиране на МедияУики.
+Ако е необходима помощ по време на инсталацията, резултатите от направените проверки трябва също да бъдат предоставени.',
+ 'config-copyright' => "=== Авторски права и Условия ===
+
+$1
+
+Тази програма е свободен софтуер, който може да се променя и/или разпространява според Общия публичен лиценз на GNU, както е публикуван от Free Software Foundation във версия на Лиценза 2 или по-късна версия.
+
+Тази програма се разпространява с надеждата, че ще е полезна, но '''без каквито и да е гаранции'''; без дори косвена гаранция за '''продаваемост''' или '''прогодност за конкретна употреба'''.
+За повече подробности се препоръчва преглеждането на Общия публичен лиценз на GNU.
+
+Към програмата трябва да е приложено <doclink href=Copying>копие на Общия публичен лиценз на GNU</doclink>; ако не, можете да пишете на Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. или да [http://www.gnu.org/copyleft/gpl.html го прочетете онлайн].",
+ 'config-sidebar' => '* [http://www.mediawiki.org Сайт на МедияУики]
+* [http://www.mediawiki.org/wiki/Help:Contents Наръчник на потребителя]
+* [http://www.mediawiki.org/wiki/Manual:Contents Наръчник на администратора]
+* [http://www.mediawiki.org/wiki/Manual:FAQ ЧЗВ]
+----
+* <doclink href=Readme>Документация</doclink>
+* <doclink href=ReleaseNotes>Бележки за версията</doclink>
+* <doclink href=Copying>Авторски права</doclink>
+* <doclink href=UpgradeDoc>Обновяване</doclink>',
+ 'config-env-good' => 'Средата беше проверена.
+Инсталирането на МедияУики е възможно.',
+ 'config-env-bad' => 'Средата беше проверена.
+Не е възможна инсталация на МедияУики.',
+ 'config-env-php' => 'Инсталирана е версия на PHP $1.',
+ 'config-env-php-toolow' => 'Инсталирана е версия на PHP $1.
+МедияУики изисква версия PHP $2 или по-нова.',
+ 'config-unicode-using-utf8' => 'Използване на utf8_normalize.so от Brion Vibber за нормализация на Уникод.',
+ 'config-unicode-using-intl' => 'Използване на разширението [http://pecl.php.net/intl intl PECL] за нормализация на Уникод.',
+ 'config-unicode-pure-php-warning' => "'''Предупреждение''': [http://pecl.php.net/intl Разширението intl PECL] не е налично за справяне с нормализацията на Уникод, превключване към по-бавното изпълнение на чист PHP.
+Ако сайтът е с голям трафик, препоръчително е запознаването с [http://www.mediawiki.org/wiki/Unicode_normalization_considerations нормализацията на Уникод].",
+ 'config-no-db' => 'Не може да бъде открит подходящ драйвер за база от данни!',
+ 'config-no-fts3' => "'''Предупреждение''': SQLite е компилирана без [http://sqlite.org/fts3.html модула FTS3], затова възможностите за търсене няма да са достъпни.",
+ 'config-register-globals' => "'''Предупреждение: Настройката на PHP <code>[http://php.net/register_globals register_globals]</code> е включена.'''
+'''При възможност е препоръчително тя да бъде изключена.'''
+МедияУики ще работи, но сървърът е изложен на евентуални пропуски в сигурността.",
+ 'config-magic-quotes-runtime' => "'''Фатално: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] е активирана!'''
+Това може да повреди непредвидимо въвеждането на данните.
+Инсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
+ 'config-magic-quotes-sybase' => "'''Фатално: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] е активирана!'''
+Това може да повреди непредвидимо въвеждането на данните.
+Инсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
+ 'config-mbstring' => "'''Фатално: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] е активирана!'''
+Това може да повреди непредвидимо въвеждането на данните.
+Инсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
+ 'config-ze1' => "'''Фатално: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] е активирана!'''
+Тази настройка причинява ужасни грешки в МедияУики.
+Невъзможно е инсталирането и използването на МедияУики докато тази настройка не бъде изключена.",
+ 'config-safe-mode' => "'''Предупреждение:''' PHP работи в [http://www.php.net/features.safe-mode безопасен режим].
+Това може да създаде проблеми, особено ако качването на файлове е разрешено, както и при поддръжката на <code>math</code>.",
+ 'config-xml-bad' => 'Липсва XML модулът на PHP.
+МедияУики се нуждае от някои функции от този модул и няма да работи при наличната конфигурация.
+При Mandrake, необходимо е да се инсталира пакетът php-xml.',
+ 'config-pcre-no-utf8' => "'''Фатално''': Модулът PCRE на PHP изглежда е компилиран без поддръжка на PCRE_UTF8.
+За да функционира правилно, МедияУики изисква поддръжка на UTF-8.",
+ 'config-memory-raised' => '<code>memory_limit</code> на PHP е $1, увеличаване до $2.',
+ 'config-memory-bad' => "'''Предупреждение:''' <code>memory_limit</code> на PHP е $1.
+Стойността вероятно е твърде ниска.
+Възможно е инсталацията да се провали!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] е инсталиран',
+ 'config-apc' => '[http://www.php.net/apc APC] е инсталиран',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] е инсталиран',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] е инсталиран',
+ 'config-no-cache' => "'''Предупреждение:''' Не бяха открити [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC] [http://trac.lighttpd.net/xcache/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
+Обектното кеширане не е включено.",
+ 'config-diff3-bad' => 'GNU diff3 не беше намерен.',
+ 'config-imagemagick' => 'Открит е ImageMagick: <code>$1</code>.
+Преоразмеряването на картинки ще бъде включено ако качването на файлове бъде разрешено.',
+ 'config-gd' => 'Открита е вградена графичната библиотека GD.
+Ако качването на файлове бъде включено, ще бъде включена възможността за преоразмеряване на картинки.',
+ 'config-no-scaling' => 'Не са открити библиотеките GD или ImageMagick.
+Преоразмеряването на картинки ще бъде изключено.',
+ 'config-no-uri' => "'''Грешка:''' Не може да се определи текущия адрес.
+Инсталация беше прекратена.",
+ 'config-uploads-not-safe' => "'''Предупреждение:''' Папката по подразбиране за качване <code>$1</code> е уязвима от изпълнение на зловредни скриптове.
+Въпреки че МедияУики извършва проверка за заплахи в сигурността на всички качени файлове, силно препоръчително е да се [http://www.mediawiki.org/wiki/Manual:Security#Upload_security затвори тази уязвимост в сигурността] преди разрешаване за качване на файлове.",
+ 'config-db-type' => 'Тип на базата от данни:',
+ 'config-db-host' => 'Хост на базата от данни:',
+ 'config-db-host-help' => 'Ако базата от данни е на друг сървър, в кутията се въвежда името на хоста или IP адреса.
+
+Ако се използва споделен уеб хостинг, доставчикът на услугата би трябвало да е предоставил в документацията си коректния хост.
+
+Ако инсталацията протича на Windows-сървър и се използва MySQL, използването на "localhost" може да е неприемливо. В такива случаи се използва "127.0.0.1" за локален IP адрес.',
+ 'config-db-wiki-settings' => 'Идентифициране на това уики',
+ 'config-db-name' => 'Име на базата от данни:',
+ 'config-db-name-help' => 'Избира се име, което да идентифицира уикито.
+То не трябва да съдържа интервали.
+
+Ако се използва споделен хостинг, доставчикът на услугата би трябвало да е предоставил или име на базата от данни, която да бъде използвана, или да позволява създаването на бази от данни чрез контролния панел.',
+ 'config-db-name-oracle' => 'Схема на базата от данни:',
+ 'config-db-install-account' => 'Потребителска сметка за инсталацията',
+ 'config-db-username' => 'Потребителско име за базата от данни:',
+ 'config-db-password' => 'Парола за базата от данни:',
+ 'config-db-install-username' => 'Въвежда се потребителско име, което ще се използва за свързване с базата от данни по време на процеса по инсталация.
+Това не е потребителско име за сметка в МедияУики; това е потребителско име за базата от данни.',
+ 'config-db-install-password' => 'Въвежда се парола, която ще бъде използвана за свързване с базата от данни по време на инсталационния процес.
+Това не е парола за сметка в МедияУики; това е парола за базата от данни.',
+ 'config-db-install-help' => 'Въвеждат се потребителско име и парола, които ще бъдат използвани за свързване с базата от данни по време на инсталационния процес.',
+ 'config-db-account-lock' => 'Използване на същото потребителско име и парола по време на нормална работа',
+ 'config-db-wiki-account' => 'Потребителска сметка за нормална работа',
+ 'config-db-wiki-help' => 'Въвежда се потребителско име и парола, които ще се използват при нормалното функциониране на уикито.
+Ако сметката не съществува и използваната при инсталацията сметка има необходимите права, тази потребителска сметка ще бъде създадена с минималните необходими права за работа с уикито.',
+ 'config-db-prefix' => 'Представка за таблиците в базата от данни:',
+ 'config-db-prefix-help' => 'Ако е необходимо да се сподели базата от данни между няколко уикита или между МедияУики и друго уеб приложение, може да се добави представка пред имената на таблиците, за да се избегнат конфликти.
+Не се използват интервали.
+
+Това поле обикновено се оставя празно.',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 бинарно',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 с обратна съвестимост с UTF-8',
+ 'config-mysql-old' => 'Изисква се MySQL $1 или по-нова версия, наличната версия е $2.',
+ 'config-db-port' => 'Порт на базата от данни:',
+ 'config-db-schema' => 'Схема за МедияУики',
+ 'config-db-schema-help' => 'Схемата по-горе обикновено е коректна.
+Промени се извършват ако наистина е необходимо.',
+ 'config-sqlite-dir' => 'Директория за данни на SQLite:',
+ 'config-sqlite-dir-help' => "SQLite съхранява всички данни в един файл.
+
+По време на инсталацията уеб сървърът трябва да има права за писане в посочената директория.
+
+Тя '''не трябва''' да е достъпна през уеб, затова не е там, където са PHP файловете.
+
+Инсталаторът ще съхрани заедно с нея файл <code>.htaccess</code>, но ако този метод пропадне, някой може да придобие даостъп до суровите данни от базата от данни.
+Това включва сурови данни за потребителите (адреси за е-поща, хеширани пароли), както и изтрити версии на страници и друга чувствителна и с ограничен достъп информация от и за уикито.
+
+Базата от данни е препоръчително да се разположи на друго място, например в <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-support-info' => 'МедияУики поддържа следните системи за бази от данни:
+
+$1
+
+Ако не виждате желаната за използване система в списъка по-долу, следвайте инструкциите за активиране на поддръжка по-горе.',
+ 'config-support-mysql' => '* $1 е най-фобре поддържата система за база от данни, най-добре поддържана от МедияУики ([http://www.php.net/manual/en/mysql.installation.php Как се компилира PHP с поддръжка на MySQL])',
+ 'config-support-postgres' => '* $1 е популярна система за бази от данни с отворен изходен код, която е алтернатива на MySQL ([http://www.php.net/manual/en/pgsql.installation.php как се компилира PHP с поддръжка на PostgreSQL]). Възможно е все още да има грешки, затова не се препоръчва да се използва в общодостъпна среда.',
+ 'config-support-sqlite' => '* $1 е лека система за база от данни, която е много добре поддържана. ([http://www.php.net/manual/en/pdo.installation.php Как се компилира PHP с поддръжка на SQLite], използва PDO)',
+ 'config-support-oracle' => '* $1 е комерсиална корпоративна база от данни. ([http://www.php.net/manual/en/oci8.installation.php Как се компилира PHP с поддръжка на OCI8])',
+ 'config-header-mysql' => 'Настройки за MySQL',
+ 'config-header-postgres' => 'Настройки за PostgreSQL',
+ 'config-header-sqlite' => 'Настройки за SQLite',
+ 'config-header-oracle' => 'Настройки за Oracle',
+ 'config-invalid-db-type' => 'Невалиден тип база от данни',
+ 'config-missing-db-name' => 'Необходимо е да се въведе стойност за "Име на базата от данни"',
+ 'config-missing-db-host' => 'Необходимо е да се въведе стойност за "Хост на базата от данни"',
+ 'config-invalid-db-name' => 'Невалидно име на базата от данни "$1".
+Използват се само ASCII букви (a-z, A-Z), цифри (0-9), долни черти (_) и тирета (-).',
+ 'config-invalid-db-prefix' => 'Невалидна представка за базата от данни "$1".
+Позволени са само ASCII букви (a-z, A-Z), цифри (0-9), долни черти (_) и тирета (-).',
+ 'config-connection-error' => '$1.
+
+Необходимо е да се проверят хостът, потребителското име и паролата, след което да се опита отново.',
+ 'config-invalid-schema' => 'Невалидна схема за МедияУики "$1".
+Допустими са само ASCII букви (a-z, A-Z), цифри (0-9) и долни черти (_).',
+ 'config-db-sys-create-oracle' => 'Инсталаторът поддържа само сметка SYSDBA за създаване на нова сметка.',
+ 'config-db-sys-user-exists-oracle' => 'Потребителската сметка "$1" вече съществува. SYSDBA може да се използва само за създаване на нова сметка!',
+ 'config-postgres-old' => 'Изисква се PostgreSQL $1 или по-нова версия, наличната версия е $2.',
+ 'config-sqlite-name-help' => 'Избира се име, което да идентифицира уикито.
+Не се използват интервали или тирета.
+Това име ще се използва за име на файла за данни на SQLite.',
+ 'config-sqlite-mkdir-error' => 'Грешка при създаване на директорията за данни "$1".
+Проверете местоположението ѝ и опитайте отново.',
+ 'config-sqlite-readonly' => 'Файлът <code>$1</code> няма права за писане.',
+ 'config-sqlite-cant-create-db' => 'Файлът за базата от данни <code>$1</code> не може да бъде създаден.',
+ 'config-sqlite-fts3-downgrade' => 'Липсва поддръжката на FTS3 за PHP, извършен беше downgradе на таблиците',
+ 'config-can-upgrade' => "В базата от данни има таблици за МедияУики.
+За надграждането им за MediaWiki $1, натиска се '''Продължаване'''.",
+ 'config-upgrade-done' => "Обновяването приключи.
+
+Вече е възможно [$1 да използвате уикито].
+
+Ако е необходимо, възможно е файлът <code>LocalSettings.php</code> да бъде създаден отново чрез натискане на бутона по-долу.
+Това '''не е препоръчително действие''', освен ако не срещате затруднения с уикито.",
+ 'config-upgrade-done-no-regenerate' => 'Обновяването приключи.
+
+Вече е възможно [$1 да използвате уикито].',
+ 'config-regenerate' => 'Създаване на LocalSettings.php →',
+ 'config-show-table-status' => 'Заявката SHOW TABLE STATUS не сполучи!',
+ 'config-unknown-collation' => "'''Предупреждение:''' Базата от данни използва неразпозната колация.",
+ 'config-db-web-account' => 'Сметка за уеб достъп до базата от данни',
+ 'config-db-web-help' => 'Избиране на потребителско име и парола, които уеб сървърът ще използва да се свързва с базата от данни при обичайната работа на уикито.',
+ 'config-db-web-account-same' => 'Използване на същата сметка като при инсталацията.',
+ 'config-db-web-create' => 'Създаване на сметката ако все още не съществува',
+ 'config-db-web-no-create-privs' => 'Посочената сметка за инсталацията не разполага с достатъчно права за създаване на нова сметка.
+Необходимо е посочената сметка вече да съществува.',
+ 'config-mysql-engine' => 'Хранилище на данни:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' почти винаги е най-добрата възможност заради навременната си поддръжка.
+
+'''MyISAM''' може да е по-бърза при инсталации с един потребител или само за четене.
+Базите от данни MyISAM се повреждат по-често от InnoDB.",
+ 'config-mysql-charset' => 'Набор от символи в базата от данни:',
+ 'config-mysql-binary' => 'Бинарен',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "В '''бинарен режим''' МедияУики съхранява текстовете в UTF-8 в бинарни полета в базата от данни.
+Това е по-ефективно от UTF-8 режима на MySQL и позволява използването на пълния набор от символи в Уникод.
+
+В '''UTF-8 режим''' MySQL ще знае в кой набор от символи са данните от уикито и ще може да ги показва и променя по подходящ начин, но няма да позволява складиране на символи извън [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основния многоезичен набор].",
+ 'config-site-name' => 'Име на уикито:',
+ 'config-site-name-help' => 'Това име ще се показва в заглавната лента на браузъра и на различни други места.',
+ 'config-site-name-blank' => 'Необходимо е да се въведе име на уикито.',
+ 'config-project-namespace' => 'Именно пространство на проекта:',
+ 'config-ns-generic' => 'Проект',
+ 'config-ns-site-name' => 'Същото като името на уикито: $1',
+ 'config-ns-other' => 'Друго (уточняване)',
+ 'config-ns-other-default' => 'МоетоУики',
+ 'config-project-namespace-help' => 'Следвайки примера на Уикипедия, много уикита съхраняват страниците си с правила в "\'\'\'именно пространство на проекта\'\'\'", отделно от основното съдържание.
+Всички заглавия на страниците в това именно пространство започват с определена представка, която може да бъде зададена тук.
+Обикновено представката произлиза от името на уикито, но не може да съдържа символи като "#" или ":".',
+ 'config-ns-invalid' => 'Посоченото именно пространство "<nowiki>$1</nowiki>" е невалидно.
+Необходимо е да бъде посочено друго.',
+ 'config-admin-box' => 'Администраторска сметка',
+ 'config-admin-name' => 'Потребителско име:',
+ 'config-admin-password' => 'Парола:',
+ 'config-admin-password-confirm' => 'Парола (повторно):',
+ 'config-admin-help' => 'Въвежда се предпочитаното потребителско име, например "Иванчо Иванчев".
+Това ще е потребителското име, което администраторът ще използва за влизане в уикито.',
+ 'config-admin-name-blank' => 'Необходимо е да бъде въведено потребителско име на администратора.',
+ 'config-admin-name-invalid' => 'Посоченото потребителско име "<nowiki>$1</nowiki>" е невалидно.
+Необходимо е да се посочи друго.',
+ 'config-admin-password-blank' => 'Неовходимо е да се въведе парола за администраторската сметка.',
+ 'config-admin-password-same' => 'Паролата не трябва да е същата като потребителското име.',
+ 'config-admin-password-mismatch' => 'Двете въведени пароли не съвпадат.',
+ 'config-admin-email' => 'Адрес за електронна поща:',
+ 'config-admin-email-help' => 'Въвеждането на адрес за е-поща позволява получаване на е-писма от другите потребители на уикито, възстановяване на изгубена или забравена парола, оповестяване при промени в страниците от списъка за наблюдение. Това поле може да бъде оставено празно.',
+ 'config-admin-error-user' => 'Възникна вътрешна грешка при създаване на администратор с името "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Възникна вътрешна грешка при задаване на парола за администратора "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Въведен е невалиден адрес за електронна поща',
+ 'config-subscribe' => 'Абониране за [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce пощенския списък за нови версии].',
+ 'config-subscribe-help' => 'Това е пощенски списък с малко трафик, който се използва за съобщения при излизане на нови версии, както и за важни проблеми със сигурността.
+Абонирането е препоръчително, както и надграждането на инсталацията на МедияУики при излизането на нова версия.',
+ 'config-almost-done' => 'Инсталацията е почти готова!
+Възможно е пропускане на оставащата конфигурация и моментално инсталиране на уикито.',
+ 'config-optional-continue' => 'Задаване на допълнителни въпроси.',
+ 'config-optional-skip' => 'Достатъчно, инсталиране на уикито.',
+ 'config-profile' => 'Профил на потребителските права:',
+ 'config-profile-wiki' => 'Традиционно уики',
+ 'config-profile-no-anon' => 'Необходимо е създаване на сметка',
+ 'config-profile-fishbowl' => 'Само одобрени редактори',
+ 'config-profile-private' => 'Затворено уики',
+ 'config-profile-help' => "Уикитата функционират най-добре, когато позволяват на възможно най-много хора да ги редактират.
+В МедияУики лесно се преглеждат последните промени и се възстановяват пораженип от недобронамерени потребители.
+
+Въпреки това мнозина смятат МедияУики за полезен софтуер по различни начини и често е трудно да се убедят всички от предимствата на уики модела.
+Затова се предоставя възможност за избор.
+
+Уикитата от типа '''{{int:config-profile-wiki}}''' позволяват на всички потребители да редактират, дори и без регистрация.
+Уикитата от типа '''{{int:config-profile-no-anon}}''' позволяват достъп до страниците и редактирането им само след създаване на потребителска сметка.
+
+Уики, което е '''{{int:config-profile-fishbowl}}''' позволява на всички да преглеждат страниците, но само предварително одобрени редактори могат да редактират съдържанието.
+В '''{{int:config-profile-private}}''' само предварително одобрени потребители могат да четат и редактират съдържанието.
+
+Детайлно обяснение на конфигурациите на потребителските права е достъпно след инсталацията в [http://www.mediawiki.org/wiki/Manual:User_rights Наръчника за потребителски права].",
+ 'config-license' => 'Авторски права и лиценз:',
+ 'config-license-none' => 'Без лиценз',
+ 'config-license-cc-by-sa' => 'Криейтив Комънс Признание-Споделяне на споделеното',
+ 'config-license-cc-by-nc-sa' => 'Криейтив Комънс Признание-Некомерсиално-Споделяне на споделеното',
+ 'config-license-gfdl-old' => 'Лиценз за свободна документация на GNU 1.2',
+ 'config-license-gfdl-current' => 'Лиценз за свободна документация на GNU 1.3 или по-нов',
+ 'config-license-pd' => 'Обществено достояние',
+ 'config-license-cc-choose' => 'Избиране на друг лиценз от Криейтив Комънс',
+ 'config-license-help' => "Много публични уикита поставят всички приноси под [http://freedomdefined.org/Definition/Bg свободен лиценз].
+Това помага създаване на усещане за общност и насърчава дългосрочните приноси.
+Това не е необходимо за частни или корпоративни уикита.
+
+Ако е необходимо да се използват текстове от Уикипедия, както и Уикипедия да може да използва текстове от уикито, необходимо е да се избере лиценз '''Криейтив Комънс Признание-Споделяне на споделеното'''.
+
+Лицензът за свободна документация на GNU е старият лиценз на съдържанието на Уикипедия.
+Той все още е валиден лиценз, но някои негови условия правят по-сложни повторното използване и интерпретацията.",
+ 'config-email-settings' => 'Настройки за е-поща',
+ 'config-enable-email' => 'Разрешаване на изходящи е-писма',
+ 'config-enable-email-help' => 'За да работят възможностите за използване на е-поща, необходимо е [http://www.php.net/manual/en/mail.configuration.php настройките за поща на PHP] да бъдат конфигурирани правилно.
+Ако няма да се използват услугите за е-поща в уикито, те могат да бъдат изключени тук.',
+ 'config-email-user' => 'Позволяване на потребителите да си изпращат е-писма през уикито',
+ 'config-email-user-help' => 'Позволяване на потребителите да си изпращат е-писма ако са разрешили това в настройките си.',
+ 'config-email-usertalk' => 'Оповестяване при промяна на потребителската беседа',
+ 'config-email-usertalk-help' => 'Позволява на потребителите да получават оповестяване при промяна на беседата им, ако това е разрешено в настройките им.',
+ 'config-email-watchlist' => 'Оповестяване за списъка за наблюдение',
+ 'config-email-watchlist-help' => 'Позволява на потребителите да получават оповестяване за техните наблюдавани страници, ако това е разрешено в настройките им.',
+ 'config-email-auth' => 'Потвърждаване на адреса за електронна поща',
+ 'config-email-auth-help' => "Ако тази настройка е включена, потребителите трябва да потвърдят адреса си за е-поща чрез препратка, която им се изпраща при настройване или промяна.
+Само валидните адреси могат да получават е-писма от други потребители или да променят писмата за оповестяване.
+Настройването на това е '''препоръчително''' за публични уикита заради потенциални злоупотреби с възможностите за електронна поща.",
+ 'config-email-sender' => 'Адрес за обратна връзка:',
+ 'config-email-sender-help' => 'Въвежда се адрес за електронна поща, който ще се използва за обратен адрес при изходящи е-писма.
+Това е адресът, на който ще се получават върнатите и неполучени писма.
+Много е-пощенски сървъри изискват поне домейн името да е валидно.',
+ 'config-upload-settings' => 'Картинки и качване на файлове',
+ 'config-upload-enable' => 'Позволяне качването на файлове',
+ 'config-upload-help' => 'Качването на файлове е възможно да доведе до пробели със сигурността на сървъра.
+Повече информация по темата има в [http://www.mediawiki.org/wiki/Manual:Security раздела за сигурност] в Наръчника.
+
+За позволяване качването на файлове, необходимо е уебсървърът да може да записва в поддиректорията на МедияУики <code>images</code>.
+След като това условие е изпълнено, функционалността може да бъде активирана.',
+ 'config-upload-deleted' => 'Директория за изтритите файлове:',
+ 'config-upload-deleted-help' => 'Избиране на директория, в която ще се складират изтритите файлове.
+В най-добрия случай тя не трябва да е достъпна през уеб.',
+ 'config-logo' => 'Адрес на логото:',
+ 'config-logo-help' => 'Обликът по подразбиране на МедияУики вклчва място с размери 135х160 пиксела за лого в горния ляв ъгъл.
+Ако има наличен файл с подходящ размер, неговият адрес може да бъде посочен тук.
+
+Ако не е необходимо лого, полето се оставя празно.',
+ 'config-instantcommons' => 'Включване на Instant Commons',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] е функционалност, която позволява на уикитата да използват картинки, звуци и друга медиа от сайта на Уикимедия [http://commons.wikimedia.org/ Общомедия].
+За да е възможно това, МедияУики изисква достъп до Интернет.
+
+Повече информация за тази функционалност, както и инструкции за настройване за други уикита, различни от Общомедия, е налична в [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos наръчника].',
+ 'config-cc-again' => 'Повторно избиране...',
+ 'config-advanced-settings' => 'Разширена конфигурация',
+ 'config-cache-options' => 'Настройки за обектното кеширане:',
+ 'config-cache-help' => 'Обектното кеширане се използва за подобряване на скоростта на МедияУики чрез кеширане на често използваните данни.
+Силно препоръчително е на средните и големите сайтове да включат тази настройка, но малките също могат да се възползват от нея.',
+ 'config-cache-none' => 'Без кеширане (не се премахва от функционалността, но това влияе на скоростта на по-големи уикита)',
+ 'config-cache-accel' => 'PHP обектно кеширане (APC, eAccelerator, XCache или WinCache)',
+ 'config-cache-memcached' => 'Използване на Memcached (изисква допълнителни настройки и конфигуриране)',
+ 'config-memcached-servers' => 'Memcached сървъри:',
+ 'config-memcached-help' => 'Списък с IP адреси за използване за Memcached.
+Необходимо е да бъдат разделени по един на ред, както и да е посочен порта. Пример:
+127.0.0.1:11211
+192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Избран е Memcached като складиращ тип, но не са посочени сървъри.',
+ 'config-memcache-badip' => 'Беше въведен невалиден IP адрес за Memcached: $1.',
+ 'config-extensions' => 'Разширения',
+ 'config-install-alreadydone' => "'''Предупреждение:''' Изглежда вече сте инсталирали МедияУики и се опитвате да го инсталирате отново.
+Продължете към следващата страница.",
+ 'config-install-begin' => 'Инсталацията на МедияУики ще започне след натискане на бутона "{{int:config-continue}}".
+Ако желаете да направите промени, натиснете Връщане.',
+ 'config-install-step-done' => 'готово',
+ 'config-install-step-failed' => 'неуспешно',
+ 'config-install-extensions' => 'Добавяне на разширенията',
+ 'config-install-database' => 'Създаване на базата от данни',
+ 'config-install-pg-schema-not-exist' => 'PostgreSQL схемата не съществува',
+ 'config-install-pg-schema-failed' => 'Създаването на таблиците пропадна.
+Необходимо е потребител "$1" да има права за писане в схемата "$2".',
+ 'config-install-pg-plpgsql' => 'Проверяване за езика PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Необходимо е да се инсталира езикът PL/pgSQL в базата от данни $1',
+ 'config-pg-no-create-privs' => 'Посочената сметка за инсталацията не притежава достатъчно права за създаване на сметка.',
+ 'config-install-user' => 'Създаване на потребител за базата от данни',
+ 'config-install-user-alreadyexists' => 'Потребител „$1“ вече съществува',
+ 'config-install-user-create-failed' => 'Създаването на потребител „$1“ беше неуспешно: $2',
+ 'config-install-user-grant-failed' => 'Предоставянето на права на потребител "$1" беше неуспешно: $2',
+ 'config-install-tables' => 'Създаване на таблиците',
+ 'config-install-tables-exist' => "'''Предупреждение''': Таблиците за МедияУики изглежда вече съществуват.
+Пропускане на създаването им.",
+ 'config-install-tables-failed' => "'''Грешка''': Създаването на таблиците пропадна и върна следната грешка: $1",
+ 'config-install-interwiki' => 'Попълване на таблицата с междууикитата по подразбиране',
+ 'config-install-interwiki-list' => 'Файлът <code>interwiki.list</code> не можа да бъде открит.',
+ 'config-install-interwiki-exists' => "'''Предупреждение''': Таблицата с междууикита изглежда вече съдържа данни.
+Пропускане на списъка по подразбиране.",
+ 'config-install-stats' => 'Инициализиране на статистиките',
+ 'config-install-keys' => 'Генериране на таен ключ',
+ 'config-insecure-keys' => "'''Предупреждение:''' {{PLURAL:$2|Сигурният ключ, създаден по време на инсталацията, не е напълно надежден|Сигурните ключове, създадени по време на инсталацията, не са напълно надеждни}} $1 . Обмислете да {{PLURAL:$2|го|ги}} смените ръчно.",
+ 'config-install-sysop' => 'Създаване на администраторска сметка',
+ 'config-install-subscribe-fail' => 'Невъзможно беше абонирането за mediawiki-announce',
+ 'config-install-mainpage' => 'Създаване на Началната страница със съдържание по подразбиране',
+ 'config-install-extension-tables' => 'Създаване на таблици за включените разширения',
+ 'config-install-mainpage-failed' => 'Вмъкването на Началната страница беше невъзможно: $1',
+ 'config-install-done' => "'''Поздравления!'''
+Инсталирането на МедияУики приключи успешно.
+
+Инсталаторът създаде файл <code>LocalSettings.php</code>.
+Той съдържа всичката необходима основна конфигурация на уикито.
+
+Необходимо е той да бъде изтеглен и поставен в основната директория на уикито (директорията, в която е и index.php). Изтеглянето би трябвало да започне автоматично.
+
+Ако изтеглянето не започне автоматично или е било прекратено, файлът може да бъде изтеглен чрез щракване на препратката по-долу:
+
+$3
+
+'''Забележка''': Ако това не бъде извършено сега, генерираният конфигурационен файл няма да е достъпен на по-късен етап ако не бъде изтеглен сега или инсталацията приключи без изтеглянето му.
+
+Когато файлът вече е в основната директория, '''[$2 уикито ще е достъпно на този адрес]'''.",
+ 'config-download-localsettings' => 'Изтегляне на LocalSettings.php',
+ 'config-help' => 'помощ',
+);
+
+/** Breton (Brezhoneg)
+ * @author Fohanno
+ * @author Fulup
+ * @author Gwendal
+ * @author Y-M D
+ */
+$messages['br'] = array(
+ 'config-desc' => 'Poellad staliañ MediaWIki',
+ 'config-title' => 'Staliadur MediaWiki $1',
+ 'config-information' => 'Titouroù',
+ 'config-localsettings-upgrade' => 'Kavet ez eus bet ur restr <code>LocalSettings.php</code>.
+Evit hizivaat ar staliadur-se, merkit an talvoud <code>$wgUpgradeKey</code> er voest dindan.
+E gavout a rit e LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Dinoet ez eus bet ur restr LocalSettings.php.
+Evit lakaat ar staliadur-mañ a-live, implijit --upgrade=yes, mar plij.',
+ 'config-localsettings-key' => "Alc'hwez hizivaat :",
+ 'config-localsettings-badkey' => "Direizh eo an alc'hwez merket ganeoc'h",
+ 'config-upgrade-key-missing' => 'Kavet ez eus bet ur staliadur kent eus MediaWiki.
+Evit hizivaat ar staliadur-se, ouzhpennit al linenn da-heul e traoñ ho restr LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => "Diglok e seblant bezañ ar restr LocalSettings.php zo anezhi dija.
+An argemmenn $1 n'eo ket termenet.
+Kemmit LocalSettings.php evit ma vo termenet an argemmenn-se, ha klikit war « Kenderc'hel ».",
+ 'config-localsettings-connection-error' => "C'hoarvezet ez eus ur fazi en ur gevreañ ouzh an diaz roadennoù oc'h implijout an arventennoù diferet e LocalSettings.php pe AdminSettings.php. Reizhit an arventennoù-se hag esaeit en-dro.
+
+$1",
+ 'config-session-error' => "Fazi e-ser loc'hañ an dalc'h : $1",
+ 'config-no-session' => "Kolle teo bet roadennoù ho talc'h !
+Gwiriit ar restr php.ini ha bezit sur emañ staliet <code>session.save_path</code> en ur c'havlec'h a zere.",
+ 'config-your-language' => 'Ho yezh :',
+ 'config-your-language-help' => 'Dibabit ur yezh da implijout e-pad an argerzh staliañ.',
+ 'config-wiki-language' => 'Yezh ar wiki :',
+ 'config-wiki-language-help' => 'Diuzañ ar yezh a vo implijet ar muiañ er wiki.',
+ 'config-back' => '← Distreiñ',
+ 'config-continue' => "Kenderc'hel →",
+ 'config-page-language' => 'Yezh',
+ 'config-page-welcome' => 'Degemer mat e MediaWiki !',
+ 'config-page-dbconnect' => "Kevreañ d'an diaz roadennoù",
+ 'config-page-upgrade' => 'Hizivaat ar staliadur a zo dioutañ',
+ 'config-page-dbsettings' => 'Arventennoù an diaz roadennoù',
+ 'config-page-name' => 'Anv',
+ 'config-page-options' => 'Dibarzhioù',
+ 'config-page-install' => 'Staliañ',
+ 'config-page-complete' => 'Graet !',
+ 'config-page-restart' => 'Adlañsañ ar staliadur',
+ 'config-page-readme' => 'Lennit-me',
+ 'config-page-releasenotes' => 'Notennoù stumm',
+ 'config-page-copying' => 'O eilañ',
+ 'config-page-upgradedoc' => 'O hizivaat',
+ 'config-page-existingwiki' => 'Wiki zo anezhañ dija',
+ 'config-help-restart' => "Ha c'hoant hoc'h eus da ziverkañ an holl roadennoù hoc'h eus ebarzhet ha da adlañsañ an argerzh staliañ ?",
+ 'config-restart' => "Ya, adloc'hañ anezhañ",
+ 'config-welcome' => "=== Gwiriadennoù a denn d'an endro ===
+Rekis eo un nebeud gwiriadennoù diazez da welet hag azas eo an endro evit gallout staliañ MediaWiki.
+Dleout a rafec'h merkañ disoc'hoù ar gwiriadennoù-se m'hoc'h eus ezhomm skoazell e-pad ar staliadenn.",
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki Degemer]
+* [http://www.mediawiki.org/wiki/Help:Contents Pajenn-stur an implijer]
+* [http://www.mediawiki.org/wiki/Manual:Contents Pajenn-stur ar merour]
+* [http://www.mediawiki.org/wiki/Manual:FAQ FAG]',
+ 'config-env-good' => 'Gwiriet eo bet an endro.
+Gallout a rit staliañ MediaWiki.',
+ 'config-env-bad' => "Gwiriet eo bet an endro.
+Ne c'hallit ket staliañ MediaWiki.",
+ 'config-env-php' => 'Staliet eo PHP $1.',
+ 'config-env-php-toolow' => "Staliet eo PHP $1.
+Nemet eo rekis PHP $2 pe nevesoc'h evit MediaWiki.",
+ 'config-unicode-using-utf8' => "Oc'h implijout utf8_normalize.so gant Brion Vibber evit ar reolata Unicode.",
+ 'config-unicode-using-intl' => "Oc'h implijout [http://pecl.php.net/intl an astenn PECL intl] evit ar reolata Unicode.",
+ 'config-no-db' => "Ne c'haller ket kavout ur sturier diaz roadennoù dereat !",
+ 'config-ze1' => "'''Fazi diremed : [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mod] zo gweredekaet !'''
+An dibarzh-mañ zo kaoz da zrein euzhus gant MediaWiki.
+Ne c'hallit ket staliañ nag implijout MediaWiki keit ha m'eo gweredekaet an dibarzh-mañ.",
+ 'config-memory-raised' => '<code>memory_limit</code> ar PHP zo $1, kemmet e $2.',
+ 'config-memory-bad' => "'''Diwallit :''' Da $1 emañ arventenn <code>memory_limit</code> PHP.
+Re izel eo moarvat.
+Marteze e c'hwito ar staliadenn !",
+ 'config-xcache' => 'Staliet eo [http://trac.lighttpd.net/xcache/ XCache]',
+ 'config-apc' => 'Staliet eo [http://www.php.net/apc APC]',
+ 'config-eaccel' => 'Staliet eo [http://eaccelerator.sourceforge.net/ eAccelerator]',
+ 'config-wincache' => 'Staliet eo [http://www.iis.net/download/WinCacheForPhp WinCache]',
+ 'config-diff3-bad' => "N'eo ket bet kavet GNU diff3.",
+ 'config-no-uri' => "'''Fazi :''' N'eus ket tu da anavezout URI ar skript red.
+Staliadur nullet.",
+ 'config-db-type' => 'Doare an diaz roadennoù :',
+ 'config-db-host' => 'Anv implijer an diaz roadennoù :',
+ 'config-db-host-oracle' => 'TNS an diaz roadennoù :',
+ 'config-db-wiki-settings' => 'Anavezout ar wiki-mañ',
+ 'config-db-name' => 'Anv an diaz roadennoù :',
+ 'config-db-name-oracle' => 'Brastres diaz roadennoù :',
+ 'config-db-install-account' => 'Kont implijer evit ar staliadur',
+ 'config-db-username' => 'Anv implijer an diaz roadennoù :',
+ 'config-db-password' => 'Ger-tremen an diaz roadennoù :',
+ 'config-db-install-username' => "Ebarzhit an anv implijer a vo implijet da gevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.
+N'eo ket anv implijer ar gont MediaWiki, an anv implijer evit ho tiaz roadennoù eo.",
+ 'config-db-install-password' => "Ebarzhit ar ger-tremen a vo implijet da gevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.
+N'eo ket ar ger-tremen evit ar gont MediaWiki, ar ger-tremen evit ho tiaz roadennoù eo.",
+ 'config-db-install-help' => 'Merkañ anv an implijer hag ar ger-tremen a vo implijet evit kevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.',
+ 'config-db-account-lock' => 'Implijout ar memes anv implijer ha ger-tremen e-kerzh oberiadurioù boutin',
+ 'config-db-wiki-account' => 'Kont implijer evit oberiadurioù boutin',
+ 'config-db-prefix' => 'Rakrann taolennoù an diaz roadennoù :',
+ 'config-db-charset' => 'Strobad arouezennoù an diaz roadennoù',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binarel',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 kilkenglotus UTF-8',
+ 'config-mysql-old' => "Rekis eo MySQL $1 pe ur stumm nevesoc'h; ober a rit gant $2.",
+ 'config-db-port' => 'Porzh an diaz roadennoù :',
+ 'config-db-schema' => 'Brastres evit MediaWiki',
+ 'config-sqlite-dir' => "Kavlec'h roadennoù SQLite :",
+ 'config-oracle-def-ts' => 'Esaouenn stokañ ("tablespace") dre ziouer :',
+ 'config-oracle-temp-ts' => "Esaouenn stokañ (''tablespace'') da c'hortoz :",
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => "Skoret eo ar reizhiadoù diaz titouroù da-heul gant MediaWiki :
+
+$1
+
+Ma ne welit ket amañ dindan ar reizhiad diaz titouroù a fell deoc'h ober ganti, heuilhit an titouroù a-us (s.o. al liammoù) evit gweredekaat ar skorañ.",
+ 'config-support-mysql' => '* $1 eo an dibab kentañ evit MediaWiki hag an hini skoret ar gwellañ ([http://www.php.net/manual/en/mysql.installation.php penaos kempunañ PHP gant skor MySQL])',
+ 'config-support-postgres' => "* $1 zo ur reizhiad diaz titouroù brudet ha digor hag a c'hall ober evit MySQL ([http://www.php.net/manual/en/pgsql.installation.php penaos kempunañ PHP gant skor PostgreSQL])",
+ 'config-support-sqlite' => "* $1 zo ur reizhiad diaz titouroù skañv skoret eus ar c'hentañ. ([http://www.php.net/manual/en/pdo.installation.php Penaos kempunañ PHP gant skor SQLite], implijout a ra PDO)",
+ 'config-support-oracle' => '* $1 zo un diaz titouroù kenwerzhel. ([http://www.php.net/manual/en/oci8.installation.php Penaos kempunañ PHP gant skor OCI8])',
+ 'config-header-mysql' => 'Arventennoù MySQL',
+ 'config-header-postgres' => 'Arventennoù PostgreSQL',
+ 'config-header-sqlite' => 'Arventennoù SQLite',
+ 'config-header-oracle' => 'Arventennoù Oracle',
+ 'config-invalid-db-type' => 'Direizh eo ar seurt diaz roadennoù',
+ 'config-missing-db-name' => 'Ret eo deoc\'h merkañ un dalvoudenn evit "Anv an diaz titouroù"',
+ 'config-missing-db-host' => 'Ret eo deoc\'h merkañ un dalvoudenn evit "Ostiz an diaz titouroù"',
+ 'config-missing-db-server-oracle' => 'Ret eo deoc\'h merkañ un dalvoudenn evit "Anv TNS an diaz titouroù"',
+ 'config-invalid-db-server-oracle' => 'Direizh eo anv TNS an diaz titouroù "$1".
+Ober hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù islinennañ (_) ha pikoù (.).',
+ 'config-invalid-db-name' => 'Direizh eo anv an diaz titouroù "$1".
+Ober hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù islinennañ (_) ha tiredoù (-).',
+ 'config-invalid-db-prefix' => 'Direizh eo rakger an diaz titouroù "$1".
+Ober hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù islinennañ (_) ha tiredoù (-).',
+ 'config-connection-error' => '$1.
+
+Gwiriit anv an ostiz, an anv implijer, ar ger-tremen ha klaskit en-dro.',
+ 'config-invalid-schema' => 'Chema direizh evit MediaWiki "$1".
+Grit hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9) hag arouezennoù islinennañ (_).',
+ 'config-postgres-old' => "Rekis eo PostgreSQL $1 pe ur stumm nevesoc'h; ober a rit gant $2.",
+ 'config-sqlite-mkdir-error' => 'Ur fazi zo bet e-ser krouiñ ar c\'havlec\'h roadennoù "$1".
+Gwiriañ al lec\'hiadur ha klask en-dro.',
+ 'config-sqlite-readonly' => "N'haller ket skrivañ er restr <code>$1</code>.",
+ 'config-sqlite-cant-create-db' => "N'haller ket krouiñ restr an diaz roadennoù <code>$1</code>.",
+ 'config-upgrade-done-no-regenerate' => 'Hizivadenn kaset da benn.
+
+Gallout a rit [$1 kregiñ da implijout ho wiki].',
+ 'config-regenerate' => 'Adgenel LocalSettings.php →',
+ 'config-show-table-status' => "C'hwitet ar reked SHOW TABLE STATUS !",
+ 'config-db-web-account' => 'Kont an diaz roadennoù evit ar voned Kenrouedad',
+ 'config-db-web-account-same' => 'Ober gant an hevelep kont hag an hini implijet evit ar staliañ',
+ 'config-db-web-create' => "Krouiñ ar gont ma n'eus ket anezhi c'hoazh",
+ 'config-mysql-engine' => 'Lusker stokañ :',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => 'Strobad arouezennoù an diaz roadennoù :',
+ 'config-mysql-binary' => 'Binarel',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Anv ar wiki :',
+ 'config-site-name-blank' => "Lakait anv ul lec'hienn .",
+ 'config-project-namespace' => 'Esaouenn anv ar raktres :',
+ 'config-ns-generic' => 'Raktres',
+ 'config-ns-site-name' => 'Hevelep anv hag hini ar wiki : $1',
+ 'config-ns-other' => 'All (spisaat)',
+ 'config-ns-other-default' => 'MaWiki',
+ 'config-admin-box' => 'Kont merour',
+ 'config-admin-name' => "Hoc'h anv :",
+ 'config-admin-password' => 'Ger-tremen :',
+ 'config-admin-password-confirm' => 'Adskrivañ ar ger-tremen :',
+ 'config-admin-help' => 'Merkit hoc\'h anv implijer amañ, da skouer "Yann Vlog".
+Hemañ eo an anv a implijot evit kevreañ d\'ar wiki-mañ.',
+ 'config-admin-name-blank' => 'Lakait anv ur merour.',
+ 'config-admin-name-invalid' => 'Direizh eo an anv implijer diferet « <nowiki>$1</nowiki> ».
+Diferit un anv implijer all.',
+ 'config-admin-password-blank' => 'Reiñ ur ger-tremen evit kont ar merour.',
+ 'config-admin-password-same' => "Ne c'hall ket ar ger-tremen bezañ heñvel ouzh anv ar gont.",
+ 'config-admin-password-mismatch' => "Ne glot ket ar gerioù-tremen hoc'h eus merket an eil gant egile.",
+ 'config-admin-email' => "Chomlec'h postel :",
+ 'config-admin-email-help' => "Merkit ur chomlec'h postel amañ evit gallout resev posteloù a-berzh implijerien all eus ar wiki, adderaouekaat ho ker-tremen ha bezañ kelaouet eus ar c'hemmoù degaset d'ar pajennoù zo en ho roll evezhiañ.",
+ 'config-admin-error-user' => 'Fazi diabarzh en ur grouiñ ur merer gant an anv "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Fazi diabarzh o lakaat ur ger-tremen evit ar merour « <nowiki>$1</nowiki> » : <pre>$2</pre>',
+ 'config-admin-error-bademail' => "Ebarzhet hoc'h eus ur chomlec'h postel direizh.",
+ 'config-subscribe' => 'Koumanantit da [https://lists.wikimedia.org/mailman/listinfo/mediawiki-listenn kemennadoù evit ar stummoù nevez].',
+ 'config-almost-done' => "Kazi echu eo !
+Gellout a rit tremen ar c'hefluniadur nevez ha staliañ ar wiki war-eeun.",
+ 'config-optional-continue' => "Sevel muioc'h a goulennoù ouzhin.",
+ 'config-optional-skip' => 'Aet on skuizh, staliañ ar wiki hepken.',
+ 'config-profile' => 'Profil ar gwirioù implijer :',
+ 'config-profile-wiki' => 'Wiki hengounel',
+ 'config-profile-no-anon' => 'Krouidigezh ur gont ret',
+ 'config-profile-fishbowl' => 'Embanner aotreet hepken',
+ 'config-profile-private' => 'Wiki prevez',
+ 'config-license' => 'Copyright hag aotre-implijout:',
+ 'config-license-none' => 'Aotre ebet en traoñ pajenn',
+ 'config-license-gfdl-old' => 'Aotre implijout teuliaouiñ frank GNU 1.2',
+ 'config-license-gfdl-current' => "Aotre implijout teuliaouiñ frank GNU 1.3 pe nevesoc'h",
+ 'config-license-pd' => 'Domani foran',
+ 'config-license-cc-choose' => 'Dibabit un aotre-implijout Creative Commons personelaet',
+ 'config-email-settings' => 'Arventennoù ar postel',
+ 'config-enable-email' => 'Gweredekaat ar posteloù a ya kuit',
+ 'config-email-user' => 'Gweredekaat ar posteloù a implijer da implijer',
+ 'config-email-user-help' => "Aotren a ra an holl implijerien da gas posteloù an eil d'egile mard eo bet gweredekaet an arc'hwel ganto en ho penndibaboù.",
+ 'config-email-watchlist' => "Gweredekaat ar c'hemenn listenn evezhiañ",
+ 'config-email-auth' => 'Gweredekaat an dilesadur dre bostel',
+ 'config-email-sender' => "Chomlec'h postel respont :",
+ 'config-email-sender-help' => "Merkit ar chomlec'h postel da vezañ implijet da chomlec'h distreiñ ar posteloù a ya er-maez.
+Di e vo kaset ar posteloù distaolet.
+Niverus eo ar servijerioù postel a c'houlenn da nebeutañ un [http://fr.wikipedia.org/wiki/Nom_de_domaine anv domani] reizh.",
+ 'config-upload-settings' => 'Pellgargañ skeudennoù ha restroù',
+ 'config-upload-enable' => 'Gweredekaat ar pellgargañ restroù',
+ 'config-upload-deleted' => "Kavlec'h evit ar restroù dilamet :",
+ 'config-logo' => 'URL al logo :',
+ 'config-instantcommons' => "Gweredekaat ''InstantCommons''",
+ 'config-cc-again' => 'Dibabit adarre...',
+ 'config-advanced-settings' => 'Kefluniadur araokaet',
+ 'config-cache-accel' => 'Krubuilhañ traezoù PHP (APC, eAccelerator, XCache pe WinCache)',
+ 'config-cache-memcached' => 'Implijout Memcached (en deus ezhomm bezañ staliet ha kefluniet)',
+ 'config-memcached-servers' => 'Servijerioù Memcached :',
+ 'config-memcached-help' => "Roll ar chomlec'hioù IP da implijout evit Memcached.
+Ret eo dispartiañ anezho gant virgulennoù ha diferañ ar porzh da implijout (da skouer : 127.0.0.1:11211, 192.168.1.25:11211).",
+ 'config-extensions' => 'Astennoù',
+ 'config-install-alreadydone' => "'''Diwallit''': Staliet hoc'h eus MediaWiki dija war a seblant hag emaoc'h o klask e staliañ c'hoazh.
+Kit d'ar bajenn war-lerc'h, mar plij.",
+ 'config-install-step-done' => 'graet',
+ 'config-install-step-failed' => "c'hwitet",
+ 'config-install-extensions' => 'En ur gontañ an astennoù',
+ 'config-install-database' => 'Krouiñ an diaz roadennoù',
+ 'config-install-pg-schema-failed' => "C'hwitet eo krouidigezh an taolennoù.
+Gwiriit hag-eñ e c'hall an implijer « $1 » skrivañ er brastres « $2 ».",
+ 'config-install-pg-commit' => "O wiriekaat ar c'hemmoù",
+ 'config-install-pg-plpgsql' => 'O wiriañ ar yezh PL/pgSQL',
+ 'config-pg-no-plpgsql' => "Ret eo deoc'h staliañ ar yezh PL/pgSQL en diaz roadennoù $1",
+ 'config-install-user' => 'O krouiñ an diaz roadennoù implijer',
+ 'config-install-tables' => 'Krouiñ taolennoù',
+ 'config-install-tables-failed' => "'''Fazi :''' c'hwitet eo krouidigezh an daolenn gant ar fazi-mañ : $1",
+ 'config-install-interwiki-list' => "Ne c'haller ket kavout ar restr <code>interwiki.list</code>.",
+ 'config-install-stats' => 'O sevel ar stadegoù',
+ 'config-install-keys' => "Genel an alc'hwez kuzh",
+ 'config-install-sysop' => 'Krouidigezh kont ar merour',
+ 'config-install-subscribe-fail' => "Ne c'haller ket koumanantiñ da mediawiki-announce",
+ 'config-install-mainpage' => "O krouiñ ar bajenn bennañ gant un endalc'had dre ziouer",
+ 'config-install-mainpage-failed' => "Ne c'haller ket ensoc'hañ ar bajenn bennañ: $1",
+ 'config-download-localsettings' => 'Pellgargañ LocalSettings.php',
+ 'config-help' => 'skoazell',
+);
+
+/** Bosnian (Bosanski)
+ * @author CERminator
+ */
+$messages['bs'] = array(
+ 'config-desc' => 'Instalacija za MediaWiki',
+ 'config-title' => 'MediaWiki $1 instalacija',
+ 'config-information' => 'Informacija',
+ 'config-localsettings-upgrade' => 'Otkrivena je datoteka <code>LocalSettings.php</code>.
+Da biste unaprijedili vaš softver, molimo vas upišite vrijednost od <code>$wgUpgradeKey</code> u okvir ispod.
+Naći ćete ga u LocalSettings.php.',
+ 'config-localsettings-key' => 'Ključ za nadgradnju:',
+ 'config-session-error' => 'Greška pri pokretanju sesije: $1',
+ 'config-no-session' => 'Vaši podaci sesije su izgubljeni!
+Provjerite vaš php.ini i provjerite da li je <code>session.save_path</code> postavljen na pravilni direktorijum.',
+ 'config-your-language' => 'Vaš jezik:',
+ 'config-your-language-help' => 'Odaberite jezik koji ćete koristiti tokom procesa instalacije.',
+ 'config-wiki-language' => 'Wiki jezik:',
+ 'config-wiki-language-help' => 'Odaberite jezik na kojem će wiki biti najvećim dijelim pisana.',
+ 'config-back' => '← Nazad',
+ 'config-continue' => 'Nastavi →',
+ 'config-page-language' => 'Jezik',
+ 'config-page-welcome' => 'Dobrodošli u MediaWiki!',
+ 'config-page-dbconnect' => 'Poveži sa bazom podataka',
+ 'config-page-upgrade' => 'Unaprijedi postojeću instalaciju',
+ 'config-page-dbsettings' => 'Postavke baze podataka',
+ 'config-page-name' => 'Naziv',
+ 'config-page-options' => 'Opcije',
+ 'config-page-install' => 'Instaliraj',
+ 'config-page-complete' => 'Završeno!',
+ 'config-page-restart' => 'Ponovi instalaciju ispočetka',
+ 'config-page-readme' => 'Pročitaj me',
+ 'config-page-releasenotes' => 'Bilješke izdanja',
+ 'config-page-copying' => 'Kopiram',
+ 'config-page-upgradedoc' => 'Nadograđujem',
+ 'config-help-restart' => 'Da li želite očistiti sve spremljene podatke koje ste unijeli i da započnete ponovo proces instalacije?',
+ 'config-restart' => 'Da, pokreni ponovo',
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki Početna strana]
+* [http://www.mediawiki.org/wiki/Help:Contents Vodič za korisnike]
+* [http://www.mediawiki.org/wiki/Manual:Contents Vodič za administratore]
+* [http://www.mediawiki.org/wiki/Manual:FAQ NPP]
+----
+* <doclink href=Readme>Pročitaj me</doclink>
+* <doclink href=ReleaseNotes>Napomene izdanja</doclink>
+* <doclink href=Copying>Kopiranje</doclink>
+* <doclink href=UpgradeDoc>Poboljšavanje</doclink>',
+ 'config-env-good' => 'Okruženje je provjereno.
+Možete instalirati MediaWiki.',
+ 'config-env-php' => 'PHP $1 je instaliran.',
+ 'config-no-db' => 'Nije mogao biti pronađen podgodan drajver za bazu podataka!',
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] je instaliran',
+ 'config-apc' => '[http://www.php.net/apc APC] je instaliran',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] je instaliran',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] je instaliran',
+ 'config-diff3-bad' => 'GNU diff3 nije pronađen.',
+ 'config-db-name' => 'Naziv baze podataka:',
+ 'config-db-name-oracle' => 'Šema baze podataka:',
+ 'config-header-mysql' => 'Postavke MySQL',
+ 'config-header-postgres' => 'Postavke PostgreSQL',
+ 'config-header-sqlite' => 'Postavke SQLite',
+ 'config-header-oracle' => 'Postavke Oracle',
+ 'config-invalid-db-type' => 'Nevaljana vrsta baze podataka',
+ 'config-upgrade-done' => "Nadogradnja završena.
+
+Sada možete [$1 početi koristiti vašu wiki].
+
+Ako želite regenerisati vašu datoteku <code>LocalSettings.php</code>, kliknite na dugme ispod.
+Ovo '''nije preporučeno''' osim ako nemate problema s vašom wiki.",
+ 'config-admin-name' => 'Vaše ime:',
+ 'config-admin-password' => 'Šifra:',
+);
+
+/** Chechen (Нохчийн)
+ * @author Sasan700
+ */
+$messages['ce'] = array(
+ 'config-no-fts3' => "'''Тергам бе''': SQLite гулйина хуттург йоцуш [http://sqlite.org/fts3.html FTS3] — лахар болхбеш хир дац оцу бухца.",
+);
+
+/** Czech (Česky) */
+$messages['cs'] = array(
+ 'config-information' => 'Informace',
+ 'config-continue' => 'Pokračovat →',
+ 'config-page-language' => 'Jazyk',
+ 'config-page-name' => 'Název',
+ 'config-page-options' => 'Nastavení',
+ 'config-page-install' => 'Instalovat',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Věštba',
+ 'config-admin-name' => 'Vaše jméno:',
+ 'config-admin-email' => 'E-mailová adresa:',
+ 'config-email-settings' => 'Nastavení e-mailu',
+ 'config-install-step-failed' => 'selhaly',
+);
+
+/** German (Deutsch)
+ * @author Kghbln
+ * @author LWChris
+ * @author Purodha
+ * @author The Evil IP address
+ * @author Umherirrender
+ */
+$messages['de'] = array(
+ 'config-desc' => 'Das MediaWiki-Installationsprogramm',
+ 'config-title' => 'Installation von MediaWiki $1',
+ 'config-information' => 'Informationen',
+ 'config-localsettings-upgrade' => 'Eine Datei <code>LocalSettings.php</code> wurde gefunden.
+Um die vorhandene Installation aktualisieren zu können, muss der Wert des Parameters <code>$wgUpgradeKey</code> im folgenden Eingabefeld angegeben werden.
+Der Parameterwert befindet sich in der Datei LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Eine Datei <code>LocalSettings.php</code> wurde gefunden.
+Um die vorhandene Installation zu aktualisieren, muss die Datei <code>update.php</code> ausgeführt werden.',
+ 'config-localsettings-key' => 'Aktualisierungsschlüssel:',
+ 'config-localsettings-badkey' => 'Der angegebene Aktualisierungsschlüssel ist falsch.',
+ 'config-upgrade-key-missing' => 'Eine MediaWiki-Installation wurde gefunden.
+Um die vorhandene Installation aktualisieren zu können, muss die unten angegebene Codezeile in die Datei LocalSettings.php an deren Ende eingefügt werden:
+
+$1',
+ 'config-localsettings-incomplete' => 'Die vorhandene Datei LocalSettings.php scheint unvollständig zu sein.
+Die Variable <code>$1</code> wurde nicht definiert.
+Die Datei LocalSettings.php muss entsprechend geändert werden, so dass sie definiert ist. Klicke danach auf „Weiter“.',
+ 'config-localsettings-connection-error' => 'Beim Verbindungsversuch zur Datenbank ist, unter Verwendung der in den Dateien LocalSettings.php oder AdminSettings.php hinterlegten Einstellungen, ein Fehler aufgetreten. Diese Einstellungen müssen korrigiert werden. Danach kann ein erneuter Versuch unternommen werden.
+
+$1',
+ 'config-session-error' => 'Fehler beim Starten der Sitzung: $1',
+ 'config-session-expired' => 'Die Sitzungsdaten scheinen abgelaufen zu sein.
+Sitzungen sind für einen Zeitraum von $1 konfiguriert.
+Dieser kann durch Anhebung des Parameters <code>session.gc_maxlifetime</code> in der Datei <code>php.ini</code> erhöht werden.
+Den Installationsvorgang erneut starten.',
+ 'config-no-session' => 'Die Sitzungsdaten sind verloren gegangen!
+Die Datei <code>php.ini</code> muss geprüft und es muss dabei sichergestellt werden, dass der Parameter <code>session.save_path</code> auf das richtige Verzeichnis verweist.',
+ 'config-your-language' => 'Sprache:',
+ 'config-your-language-help' => 'Bitte die Sprache auswählen, die während des Installationsvorgangs verwendet werden soll.',
+ 'config-wiki-language' => 'Sprache des Wikis:',
+ 'config-wiki-language-help' => 'Bitte die Hauptbearbeitungssprache des Wikis auswählen',
+ 'config-back' => '← Zurück',
+ 'config-continue' => 'Weiter →',
+ 'config-page-language' => 'Sprache',
+ 'config-page-welcome' => 'Willkommen bei MediaWiki!',
+ 'config-page-dbconnect' => 'Mit der Datenbank verbinden',
+ 'config-page-upgrade' => 'Eine vorhandene Installation aktualisieren',
+ 'config-page-dbsettings' => 'Datenbankeinstellungen',
+ 'config-page-name' => 'Name',
+ 'config-page-options' => 'Optionen',
+ 'config-page-install' => 'Installieren',
+ 'config-page-complete' => 'Fertig!',
+ 'config-page-restart' => 'Installationsvorgang erneut starten',
+ 'config-page-readme' => 'Lies mich',
+ 'config-page-releasenotes' => 'Versionsinfos (en)',
+ 'config-page-copying' => 'Kopie der Lizenz',
+ 'config-page-upgradedoc' => 'Aktualisiere',
+ 'config-page-existingwiki' => 'Vorhandenes Wiki',
+ 'config-help-restart' => 'Sollen alle bereits eingegebene Daten gelöscht und der Installationsvorgang erneut gestartet werden?',
+ 'config-restart' => 'Ja, erneut starten',
+ 'config-welcome' => '=== Prüfung der Installationsumgebung ===
+Basisprüfungen werden durchgeführt, um festzustellen, ob die Installationsumgebung für die Installation von MediaWiki geeignet ist.
+Die Ergebnisse dieser Prüfung sollten angegeben werden, sofern während des Installationsvorgangs Hilfe benötigt und erfragt wird.',
+ 'config-copyright' => "=== Lizenz und Nutzungsbedingungen ===
+
+$1
+
+Dieses Programm ist freie Software, d. h. es kann, gemäß den Bedingungen der von der Free Software Foundation veröffentlichten ''GNU General Public License'', weiterverteilt und/ oder modifiziert werden. Dabei kann die Version 2, oder nach eigenem Ermessen, jede neuere Version der Lizenz verwendet werden.
+
+Dieses Programm wird in der Hoffnung verteilt, dass es nützlich sein wird, allerdings '''ohne jegliche Garantie''' und sogar ohne die implizierte Garantie einer '''Marktgängigkeit''' oder '''Eignung für einen bestimmten Zweck'''. Hierzu sind weitere Hinweise in der ''GNU General Public License'' enthalten.
+
+Eine <doclink href=Copying>Kopie der ''GNU General Public License''</doclink> sollte zusammen mit diesem Programm verteilt worden sein. Sofern dies nicht der Fall war, kann eine Kopie bei der Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftlich angefordert oder auf deren Website [http://www.gnu.org/copyleft/gpl.html online gelesen] werden.",
+ 'config-sidebar' => '* [http://www.mediawiki.org Website von MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Nutzeranleitung]
+* [http://www.mediawiki.org/wiki/Manual:Contents Administratorenanleitung]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Häufig gestellte Fragen]
+----
+* <doclink href=Readme>Lies mich</doclink>
+* <doclink href=ReleaseNotes>Versionsinformationen</doclink>
+* <doclink href=Copying>Lizenzbestimmungen</doclink>
+* <doclink href=UpgradeDoc>Aktualisierung</doclink>',
+ 'config-env-good' => 'Die Installationsumgebung wurde geprüft.
+MediaWiki kann installiert werden.',
+ 'config-env-bad' => 'Die Installationsumgebung wurde geprüft.
+MediaWiki kann nicht installiert werden.',
+ 'config-env-php' => 'PHP $1 ist installiert.',
+ 'config-env-php-toolow' => 'PHP $1 ist installiert.
+Allerdings benötigt MediaWiki PHP $2 oder höher.',
+ 'config-unicode-using-utf8' => 'Zur Unicode-Normalisierung wird Brion Vibbers <code>utf8_normalize.so</code> eingesetzt.',
+ 'config-unicode-using-intl' => 'Zur Unicode-Normalisierung wird die [http://pecl.php.net/intl PECL-Erweiterung intl] eingesetzt.',
+ 'config-unicode-pure-php-warning' => "'''Warnung:''' Die [http://pecl.php.net/intl PECL-Erweiterung intl] ist für die Unicode-Normalisierung nicht verfügbar, so dass stattdessen die langsame pure-PHP-Implementierung genutzt wird.
+Sofern eine Website mit großer Benutzeranzahl betrieben wird, sollten weitere Informationen auf der Webseite [http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-Normalisierung (en)] gelesen werden.",
+ 'config-unicode-update-warning' => "'''Warnung:''' Die installierte Version des Unicode-Normalisierungswrappers nutzt einer ältere Version der Bibliothek [http://site.icu-project.org/ des ICU-Projekts].
+Diese sollte [http://www.mediawiki.org/wiki/Unicode_normalization_considerations aktualisiert] werden, sofern auf die Verwendung von Unicode Wert gelegt wird.",
+ 'config-no-db' => 'Es konnte kein adäquater Datenbanktreiber gefunden werden!',
+ 'config-no-db-help' => 'Es muss ein Datenbanktreiber für PHP installiert werden.
+Die folgenden Datenbanksysteme werden unterstützt: $1
+
+Sofern ein gemeinschaftlich genutzter Server für das Hosting verwendet wird, muss der Hoster gefragt werden einen adäquaten Datenbanktreiber zu installieren.
+Sofern PHP selbst kompiliert wurde, muss es mit es neu konfiguriert werden, wobei der Datenbankclient zu aktivierten ist. Hierzu kann beispielsweise <code>./configure --with-mysql</code> ausgeführt werden.
+Sofern PHP über die Paketverwaltung einer Debian- oder Ubuntu-Installation installiert wurde, muss das „php5-mysql“-Paket nachinstalliert werden.',
+ 'config-no-fts3' => "'''Warnung:''' SQLite wurde ohne das [http://sqlite.org/fts3.html FTS3-Modul] kompiliert, so dass keine Suchfunktionen zur Verfügung stehen.",
+ 'config-register-globals' => "'''Warnung: Der Parameter <code>[http://php.net/register_globals register_globals]</code> von PHP ist aktiviert.'''
+'''Sie sollte deaktiviert werden, sofern dies möglich ist.'''
+Die MediaWiki-Installation wird zwar laufen, wobei aber der Server für potentielle Sicherheitsprobleme anfällig ist.",
+ 'config-magic-quotes-runtime' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]</code> von PHP ist aktiviert!'''
+Diese Einstellung führt zu unvorhersehbaren Problemen bei der Dateneingabe.
+MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
+ 'config-magic-quotes-sybase' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> von PHP ist aktiviert!'''
+Diese Einstellung führt zu unvorhersehbaren Problemen bei der Dateneingabe.
+MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
+ 'config-mbstring' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> von PHP ist aktiviert!'''
+Diese Einstellung verursacht Fehler und führt zu unvorhersehbaren Problemen bei der Dateneingabe.
+MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
+ 'config-ze1' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]</code> von PHP ist aktiviert!'''
+Diese Einstellung führt zu großen Fehlern bei MediaWiki.
+MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
+ 'config-safe-mode' => "'''Warnung:''' Der Funktion <code>[http://www.php.net/features.safe-mode Safe Mode]</code> von PHP ist aktiviert.
+Dies kann zu Problemen führen, insbesondere wenn das Hochladen von Dateien möglich sein, bzw. der Auszeichner <code>math</code> genutzt werden soll.",
+ 'config-xml-bad' => 'Das XML-Modul von PHP fehlt.
+MediaWiki benötigt Funktionen, die dieses Modul bereitstellt und wird in der bestehenden Konfiguration nicht funktionieren.
+Sofern Mandriva genutzt wird, muss noch das „php-xml“-Paket installiert werden.',
+ 'config-pcre' => 'Das PHP-Modul für die PCRE-Unterstützung wurde nicht gefunden.
+MediaWiki benötigt allerdings perl-kompatible reguläre Ausdrücke, um lauffähig zu sein.',
+ 'config-pcre-no-utf8' => "'''Fataler Fehler: Das PHP-Modul PCRE scheint ohne PCRE_UTF8-Unterstützung kompiliert worden zu sein.'''
+MediaWiki benötigt die UTF-8-Unterstützung, um fehlerfrei lauffähig zu sein.",
+ 'config-memory-raised' => 'Der PHP-Parameter <code>memory_limit</code> betrug $1 und wurde auf $2 erhöht.',
+ 'config-memory-bad' => "'''Warnung:''' Der PHP-Parameter <code>memory_limit</code> beträgt $1.
+Dieser Wert ist wahrscheinlich zu niedrig.
+Der Installationsvorgang könnte daher scheitern!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] ist installiert',
+ 'config-apc' => '[http://www.php.net/apc APC] ist installiert',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] ist installiert',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] ist installiert',
+ 'config-no-cache' => "'''Warnung:''' [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] konnten nicht gefunden werden.
+Das Objektcaching ist daher nicht aktiviert.",
+ 'config-diff3-bad' => 'GNU diff3 wurde nicht gefunden.',
+ 'config-imagemagick' => 'ImageMagick wurde gefunden: <code>$1</code>.
+Miniaturansichten von Bildern werden möglich sein, sobald das Hochladen von Dateien aktiviert wurde.',
+ 'config-gd' => 'Die im System integrierte GD-Grafikbibliothek wurde gefunden.
+Miniaturansichten von Bildern werden möglich sein, sobald das Hochladen von Dateien aktiviert wurde.',
+ 'config-no-scaling' => 'Weder die GD-Grafikbibliothek noch ImageMagick wurden gefunden.
+Miniaturansichten von Bildern sind daher nicht möglich.',
+ 'config-no-uri' => "'''Fehler:''' Die aktuelle URL konnte nicht ermittelt werden.
+Der Installationsvorgang wurde daher abgebrochen.",
+ 'config-uploads-not-safe' => "'''Warnung:''' Das Standardverzeichnis für hochgeladene Dateien <code>$1</code> ist für die willkürliche Ausführung von Skripten anfällig.
+Obwohl MediaWiki die hochgeladenen Dateien auf Sicherheitsrisiken überprüft, wird dennoch dringend empfohlen diese [http://www.mediawiki.org/wiki/Manual:Security#Upload_security Sicherheitslücke] zu schließen, bevor das Hochladen von Dateien aktiviert wird.",
+ 'config-brokenlibxml' => 'Das System nutzt eine Kombination aus PHP- und libxml2-Versionen, die fehleranfällig ist und versteckte Datenfehler bei MediaWiki und anderen Webanwendungen verursachen kann.
+PHP muss auf Version 5.2.9 oder später sowie libxml2 auf die Version 2.7.3 oder später aktualisiert werden, um das Problem zu lösen. Installationsabbruch ([http://bugs.php.net/bug.php?id=45996 siehe hierzu die Fehlermeldung bei PHP]).',
+ 'config-using531' => 'MediaWiki kann nicht zusammen mit PHP $1 verwendet werden. Grund hierfür ist ein Fehler im Zusammenhang mit den Verweisparametern zu <code>__call()</code>.
+PHP muss auf Version 5.3.2 oder höher oder 5.3.0 oder niedriger aktualisiert werden, um das Problem zu beheben.
+Die Installation wurde abgebrochen.',
+ 'config-db-type' => 'Datenbanksystem:',
+ 'config-db-host' => 'Datenbankserver:',
+ 'config-db-host-help' => 'Sofern sich die Datenbank auf einem anderen Server befindet, ist hier der Servername oder die entsprechende IP-Adresse anzugeben.
+
+Sofern ein gemeinschaftlich genutzter Server verwendet wird, sollte der Hoster den zutreffenden Servernamen in seiner Dokumentation angegeben haben.
+
+Sofern auf einem Windows-Server installiert und MySQL genutzt wird, funktioniert der Servername „localhost“ voraussichtlich nicht. Wenn nicht, sollte „127.0.0.1“ oder die lokale IP-Adresse angegeben werden.',
+ 'config-db-host-oracle' => 'Datenbank-TNS:',
+ 'config-db-host-oracle-help' => 'Einen gültigen [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm „Local Connect“-Namen] angeben. Die „tnsnames.ora“-Datei muss von dieser Installation erkannt werden können.<br />Sofern die Client-Bibliotheken für Version 10g oder neuer verwendet werden, kann auch [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm „Easy Connect“] zur Namensgebung genutzt werden.',
+ 'config-db-wiki-settings' => 'Bitte identifiziere dieses Wiki',
+ 'config-db-name' => 'Datenbankname:',
+ 'config-db-name-help' => 'Bitte einen Namen angeben, mit dem das Wiki identifiziert werden kann.
+Dabei sollten keine Leerzeichen verwendet werden.
+
+Sofern ein gemeinschaftlich genutzter Server verwendet wird, sollte der Hoster den Datenbanknamen angegeben oder aber die Erstellung einer Datenbank über ein entsprechendes Interface gestattet haben.',
+ 'config-db-name-oracle' => 'Datenbankschema:',
+ 'config-db-account-oracle-warn' => 'Es gibt drei von MediaWiki unterstützte Möglichkeiten Oracle als Datenbank einzurichten:
+
+Sofern das Datenbankbenutzerkonto während des Installationsvorgangs erstellt werden soll, muss ein Datenbankbenutzerkonto mit der SYSDBA-Berechtigung zusammen mit den entsprechenden Anmeldeinformationen angegeben werden, mit dem dann über das Web auf die Datenbank zugegriffen werden kann. Alternativ kann man auch lediglich ein einzelnes manuell angelegtes Datenbankbenutzerkonto angeben, mit dem über das Web auf die Datenbank zugegriffen werden kann, sofern dieses über die Berechtigung zur Erstellung von Datenbankschemen verfügt. Zudem ist es möglich zwei Datenbankbenutzerkonten anzugeben von denen eines die Berechtigung zur Erstellung von Datenbankschemen hat und das andere, um mit ihm über das Web auf die Datenbank zuzugreifen.
+
+Ein Skript zum Anlegen eines Datenbankbenutzerkontos mit den notwendigen Berechtigungen findet man unter dem Pfad „…/maintenance/oracle/“ dieser MediaWiki-Installation. Es ist dabei zu bedenken, dass die Verwendung eines Datenbankbenutzerkontos mit beschränkten Berechtigungen die Nutzung der Wartungsfunktionen für das Standarddatenbankbenutzerkonto deaktiviert.',
+ 'config-db-install-account' => 'Benutzerkonto für die Installation',
+ 'config-db-username' => 'Name des Datenbankbenutzers:',
+ 'config-db-password' => 'Passwort des Datenbankbenutzers:',
+ 'config-db-password-empty' => 'Bitte ein Passwort für den neuen Datenbankbenutzer angeben: $1
+Obzwar es möglich ist Datenbankbenutzer ohne Passwort anzulegen, so ist dies aber nicht sicher.',
+ 'config-db-install-username' => 'Den Benutzernamen angeben, der für die Verbindung mit der Datenbank während des Installationsvorgangs genutzt werden soll. Es handelt sich dabei nicht um den Benutzernamen für das MediaWiki-Konto, sondern um den Benutzernamen der vorgesehenen Datenbank.',
+ 'config-db-install-password' => 'Das Passwort angeben, das für die Verbindung mit der Datenbank während des Installationsvorgangs genutzt werden soll. Es handelt sich dabei nicht um das Passwort für das MediaWiki-Konto, sondern um das Passwort der vorgesehenen Datenbank.',
+ 'config-db-install-help' => 'Benutzername und Passwort, die während des Installationsvorgangs, für die Verbindung mit der Datenbank, genutzt werden sollen, sind nun anzugeben.',
+ 'config-db-account-lock' => 'Derselbe Benutzername und das Passwort müssen während des Normalbetriebs des Wikis verwendet werden.',
+ 'config-db-wiki-account' => 'Benutzerkonto für den normalen Betrieb',
+ 'config-db-wiki-help' => 'Bitte Benutzernamen und Passwort angeben, die der Webserver während des Normalbetriebes dazu verwenden soll, eine Verbindung zum Datenbankserver herzustellen.
+Sofern ein entsprechendes Benutzerkonto nicht vorhanden ist und das Benutzerkonto für den Installationsvorgang über ausreichende Berechtigungen verfügt, wird dieses Benutzerkonto automatisch mit den Mindestberechtigungen zum Normalbetrieb des Wikis angelegt.',
+ 'config-db-prefix' => 'Datenbanktabellenpräfix:',
+ 'config-db-prefix-help' => 'Sofern eine Datenbank für mehrere Wikiinstallationen oder eine Wikiinstallation und eine andere Programminstallation genutzt werden soll, muss ein weiterer Datenbanktabellenpräfix angegeben werden, um Datenbankprobleme zu vermeiden.
+Es können keine Leerzeichen verwendet werden.
+
+Gewöhnlich bleibt dieses Datenfeld leer.',
+ 'config-db-charset' => 'Datenbankzeichensatz',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binär',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 abwärtskompatibles UTF-8',
+ 'config-charset-help' => "'''Warnung:''' Sofern '''abwärtskompatibles UTF-8''' bei MySQL 4.1+ verwendet und anschließend die Datenbank mit <code>mysqldump</code> gesichert wird, könnten alle nicht mit ASCII-codierten Zeichen beschädigt werden, was zu irreversiblen Schäden der Datensicherung führt!
+
+Im '''binären Modus''' speichert MediaWiki UTF-8 Texte in der Datenbank in binär kodierte Datenfelder.
+Dies ist effizienter als der UTF-8-Modus von MySQL und ermöglicht so die Verwendung jeglicher Unicode-Zeichen.
+Im '''UTF-8-Modus''' wird MySQL den Zeichensatz der Daten erkennen und sie richtig anzeigen und konvertieren,
+allerdings können keine Zeichen außerhalb des [http://de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
+ 'config-mysql-old' => 'MySQL $1 oder höher wird benötigt. MySQL $2 ist momentan vorhanden.',
+ 'config-db-port' => 'Datenbankport:',
+ 'config-db-schema' => 'Datenschema für MediaWiki',
+ 'config-db-schema-help' => 'Dieses Datenschema ist in der Regel allgemein verwendbar.
+Nur Änderungen daran vornehmen, sofern es gute Gründe dafür gibt.',
+ 'config-sqlite-dir' => 'SQLite-Datenverzeichnis:',
+ 'config-sqlite-dir-help' => "SQLite speichert alle Daten in einer einzigen Datei.
+
+Das für sie vorgesehene Verzeichnis muss während des Installationsvorgangs beschreibbar sein.
+
+Es sollte '''nicht'' über das Web zugänglich sein, was der Grund ist, warum die Datei nicht dort abgelegt wird, wo sich die PHP-Dateien befinden.
+
+Das Installationsprogramm wird mit der Datei zusammen eine zusätzliche <code>.htaccess</code>-Datei erstellen. Sofern dies scheitert, können Dritte auf die Datendatei zugreifen.
+Dies umfasst die Nutzerdaten (E-Mail-Adressen, Passwörter, etc.) wie auch gelöschte Seitenversionen und andere vertrauliche Daten, die im Wiki gespeichert sind.
+
+Es ist daher zu erwägen die Datendatei an gänzlich anderer Stelle abzulegen, beispielsweise im Verzeichnis <code>./var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Standardtabellenraum:',
+ 'config-oracle-temp-ts' => 'Temporärer Tabellenraum:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki unterstützt die folgenden Datenbanksysteme:
+
+$1
+
+Sofern nicht das Datenbanksystem angezeigt wird, das verwendet werden soll, gibt es oben einen Link zur Anleitung mit Informationen, wie dieses aktiviert werden kann.',
+ 'config-support-mysql' => '* $1 ist das von MediaWiki primär unterstützte Datenbanksystem ([http://www.php.net/manual/en/mysql.installation.php Anleitung zur Kompilierung von PHP mit MySQL-Unterstützung (en)])',
+ 'config-support-postgres' => '* $1 ist ein beliebtes Open-Source-Datenbanksystem und eine Alternative zu MySQL ([http://www.php.net/manual/de/pgsql.installation.php Anleitung zur Kompilierung von PHP mit PostgreSQL-Unterstützung]). Es gibt allerdings einige kleinere Implementierungsfehler, so dass von der Nutzung in einer Produktivumgebung abgeraten wird.',
+ 'config-support-sqlite' => '* $1 ist ein verschlanktes Datenbanksystem, das auch gut unterstützt wird ([http://www.php.net/manual/de/pdo.installation.php Anleitung zur Kompilierung von PHP mit SQLite-Unterstützung], verwendet PHP Data Objects (PDO))',
+ 'config-support-oracle' => '* $1 ist eine kommerzielle Unternehmensdatenbank ([http://www.php.net/manual/en/oci8.installation.php Anleitung zur Kompilierung von PHP mit OCI8-Unterstützung (en)])',
+ 'config-header-mysql' => 'MySQL-Einstellungen',
+ 'config-header-postgres' => 'PostgreSQL-Einstellungen',
+ 'config-header-sqlite' => 'SQLite-Einstellungen',
+ 'config-header-oracle' => 'Oracle-Einstellungen',
+ 'config-invalid-db-type' => 'Unzulässiges Datenbanksystem',
+ 'config-missing-db-name' => 'Bei „Datenbankname“ muss ein Wert angegeben werden.',
+ 'config-missing-db-host' => 'Bei „Datenbankhost“ muss ein Wert angegeben werden.',
+ 'config-missing-db-server-oracle' => 'Für das „Datenbank-TNS“ muss ein Wert eingegeben werden',
+ 'config-invalid-db-server-oracle' => 'Ungültiges Datenbank-TNS „$1“.
+Es dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9) und Unterstriche (_) und Punkte (.) verwendet werden.',
+ 'config-invalid-db-name' => 'Ungültiger Datenbankname „$1“.
+Es dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9), Unter- (_) sowie Bindestriche (-) verwendet werden.',
+ 'config-invalid-db-prefix' => 'Ungültiger Datenbanktabellenpräfix „$1“.
+Es dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9), Unter- (_) sowie Bindestriche (-) verwendet werden.',
+ 'config-connection-error' => '$1.
+
+Bitte unten angegebenen Servernamen, Benutzernamen sowie das Passwort überprüfen und es danach erneut versuchen.',
+ 'config-invalid-schema' => 'Ungültiges Datenschema für MediaWiki „$1“.
+Es dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9) und Unterstriche (_) verwendet werden.',
+ 'config-db-sys-create-oracle' => 'Das Installationsprogramm unterstützt nur die Verwendung eines Datenbankbenutzerkontos mit SYSDBA-Berechtigung zum Anlegen eines neuen Datenbankbenutzerkontos.',
+ 'config-db-sys-user-exists-oracle' => 'Das Datenbankbenutzerkonto „$1“ ist bereits vorhanden. Ein Datenbankbenutzerkontos mit SYSDBA-Berechtigung kann nur zum Anlegen eines neuen Datenbankbenutzerkontos genutzt werden.',
+ 'config-postgres-old' => 'PostgreSQL $1 oder höher wird benötigt. PostgreSQL $2 ist momentan vorhanden.',
+ 'config-sqlite-name-help' => 'Bitten einen Namen angeben, mit dem das Wiki identifiziert werden kann.
+Dabei bitte keine Leerzeichen oder Bindestriche verwenden.
+Dieser Name wird für die SQLite-Datendateinamen genutzt.',
+ 'config-sqlite-parent-unwritable-group' => 'Das Datenverzeichnis <code><nowiki>$1</nowiki></code> kann nicht erzeugt werden, da das übergeordnete Verzeichnis <code><nowiki>$2</nowiki></code> nicht für den Webserver beschreibbar ist.
+
+Das Installationsprogramm konnte den Benutzer bestimmen, mit dem Webserver ausgeführt wird.
+Schreibzugriff auf das <code><nowiki>$3</nowiki></code>-Verzeichnis muss für diesen ermöglicht werden, um den Installationsvorgang fortsetzen zu können.
+
+Auf einem Unix- oder Linux-System:
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Das Datenverzeichnis <code><nowiki>$1</nowiki></code> kann nicht erzeugt werden, da das übergeordnete Verzeichnis <code><nowiki>$2</nowiki></code> nicht für den Webserver beschreibbar ist.
+
+Das Installationsprogramm konnte den Benutzer bestimmen, mit dem Webserver ausgeführt wird.
+Schreibzugriff auf das <code><nowiki>$3</nowiki></code>-Verzeichnis muss global für diesen und andere Benutzer ermöglicht werden, um den Installationsvorgang fortsetzen zu können.
+
+Auf einem Unix- oder Linux-System:
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Fehler beim Erstellen des Datenverzeichnisses „$1“.
+
+Bitte den Speicherort überprüfen und es danach erneut versuchen.',
+ 'config-sqlite-dir-unwritable' => 'Das Verzeichnis „$1“ ist nicht beschreibbar.
+Bitte die Zugriffsberechtigungen so ändern, dass dieses Verzeichnis für den Webserver beschreibbar ist und es danach erneut versuchen.',
+ 'config-sqlite-connection-error' => '$1.
+
+Bitte unten angegebenes Datenverzeichnis sowie den Datenbanknamen überprüfen und es danach erneut versuchen.',
+ 'config-sqlite-readonly' => 'Die Datei <code>$1</code> ist nicht beschreibbar.',
+ 'config-sqlite-cant-create-db' => 'Die Datenbankdatei <code>$1</code> konnte nicht erzeugt werden.',
+ 'config-sqlite-fts3-downgrade' => 'PHP verfügt nicht über FTS3-Unterstützung. Die Tabellen wurden zurückgestuft.',
+ 'config-can-upgrade' => "Es wurden MediaWiki-Tabellen in dieser Datenbank gefunden.
+Um sie auf MediaWiki $1 zu aktualisieren, bitte auf '''Weiter''' klicken.",
+ 'config-upgrade-done' => "Die Aktualisierung ist abgeschlossen.
+
+Das Wiki kann nun [$1 genutzt werden].
+
+Sofern die Datei <code>LocalSettings.php</code> neu erzeugt werden soll, bitte auf die Schaltfläche unten klicken.
+Dies wird '''nicht empfohlen''', es sei denn, es treten Probleme mit dem Wiki auf.",
+ 'config-upgrade-done-no-regenerate' => 'Die Aktualisierung ist abgeschlossen.
+
+Das Wiki kann nun [$1 genutzt werden].',
+ 'config-regenerate' => 'LocalSettings.php neu erstellen →',
+ 'config-show-table-status' => 'Die Abfrage SHOW TABLE STATUS ist gescheitert!',
+ 'config-unknown-collation' => "'''Warnung:''' Die Datenbank nutzt eine unbekannte Kollation.",
+ 'config-db-web-account' => 'Datenbankkonto für den Webzugriff',
+ 'config-db-web-help' => 'Bitte Benutzernamen und Passwort auswählen, die der Webserver während des Normalbetriebes dazu verwenden soll, eine Verbindung zum Datenbankserver herzustellen.',
+ 'config-db-web-account-same' => 'Dasselbe Konto wie während des Installationsvorgangs verwenden',
+ 'config-db-web-create' => 'Sofern nicht bereits vorhanden, muss nun das Konto erstellt werden',
+ 'config-db-web-no-create-privs' => 'Das angegebene und für den Installationsvorgang vorgesehene Konto verfügt nicht über ausreichend Berechtigungen, um ein Konto zu erstellen.
+Das hier angegebene Konto muss bereits vorhanden sein.',
+ 'config-mysql-engine' => 'Speicher-Engine:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' ist fast immer die bessere Wahl, da es gleichzeitige Zugriffe gut unterstützt.
+
+'''MyISAM''' ist in Einzelnutzerumgebungen sowie bei schreibgeschützten Wikis schneller.
+Bei MyISAM-Datenbanken treten tendenziell häufiger Fehler auf als bei InnoDB-Datenbanken.",
+ 'config-mysql-charset' => 'Datenbankzeichensatz:',
+ 'config-mysql-binary' => 'binär',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "Im '''binären Modus''' speichert MediaWiki UTF-8 Texte in der Datenbank in binär kodierte Datenfelder.
+Dies ist effizienter als der UTF-8-Modus von MySQL und ermöglicht so die Verwendung jeglicher Unicode-Zeichen.
+
+Im '''UTF-8-Modus''' wird MySQL den Zeichensatz der Daten erkennen und sie richtig anzeigen und konvertieren,
+allerdings können keine Zeichen außerhalb des [http://de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
+ 'config-site-name' => 'Name des Wikis:',
+ 'config-site-name-help' => 'Er wird in der Titelleiste des Browsers, wie auch verschiedenen anderen Stellen, genutzt.',
+ 'config-site-name-blank' => 'Sitenamen angeben.',
+ 'config-project-namespace' => 'Name des Projektnamensraums:',
+ 'config-ns-generic' => 'Projekt',
+ 'config-ns-site-name' => 'Entspricht dem Namen des Wikis: $1',
+ 'config-ns-other' => 'Sonstige (bitte angeben)',
+ 'config-ns-other-default' => 'MeinWiki',
+ 'config-project-namespace-help' => "Dem Beispiel von Wikipedia folgend, unterscheiden viele Wikis zwischen den Seiten für Inhalte und denen für Richtlinien. Letztere werden im „'''Projektnamensraum'''“ hinterlegt.
+Alle Seiten dieses Namensraumes verfügen über einen Seitenpräfix, der nun an dieser Stelle angegeben werden kann.
+Traditionell steht dieser Seitenpräfix mit dem Namen des Wikis in einem engen Zusammenhang. Dabei können bestimmte Sonderzeichen wie „#“ oder „:“ nicht verwendet werden.",
+ 'config-ns-invalid' => 'Der angegebene Namensraum „<nowiki>$1</nowiki>“ ist ungültig.
+Bitte einen abweichenden Projektnamensraum angeben.',
+ 'config-ns-conflict' => 'Der angegebene Namensraum „<nowiki>$1</nowiki>“ verursacht Problem mit dem Standardnamensraum von MediaWiki.
+Bitte einen abweichenden Projektnamensraum angeben.',
+ 'config-admin-box' => 'Administratorkonto',
+ 'config-admin-name' => 'Name:',
+ 'config-admin-password' => 'Passwort:',
+ 'config-admin-password-confirm' => 'Passwort wiederholen:',
+ 'config-admin-help' => 'Bitte den bevorzugten Benutzernamen angeben, beispielsweise „Knut Wuchtig“.
+Dies ist der Name, der benötigt wird, um sich im Wiki anzumelden.',
+ 'config-admin-name-blank' => 'Bitte den Benutzernamen für den Administratoren angeben.',
+ 'config-admin-name-invalid' => 'Der angegebene Benutzername „<nowiki>$1</nowiki>“ ist ungültig.
+Bitte einen abweichenden Benutzernamen angeben.',
+ 'config-admin-password-blank' => 'Bitte das Passwort für das Administratorkonto angeben.',
+ 'config-admin-password-same' => 'Das Passwort darf nicht mit dem Benutzernamen übereinstimmen.',
+ 'config-admin-password-mismatch' => 'Die beiden Passwörter stimmen nicht überein.',
+ 'config-admin-email' => 'E-Mail-Adresse:',
+ 'config-admin-email-help' => 'Bitte hier eine E-Mail-Adresse angeben, die den E-Mail-Empfang von anderen Benutzern des Wikis, das Zurücksetzen des Passwortes sowie Benachrichtigungen zu Änderungen an beobachteten Seiten ermöglicht. Diese Feld kann leer gelassen werden.',
+ 'config-admin-error-user' => 'Es ist beim Erstellen des Administrators mit dem Namen „<nowiki>$1</nowiki>“ ein interner Fehler aufgetreten.',
+ 'config-admin-error-password' => 'Es ist beim Setzen des Passworts für den Administrator „<nowiki>$1</nowiki>“ ein interner Fehler aufgetreten: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Es wurde eine ungültige E-Mail-Adresse angegeben',
+ 'config-subscribe' => 'Bitte die Mailingliste [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mitteilungen zu Versionsveröffentlichungen] abonnieren.',
+ 'config-subscribe-help' => 'Es handelt sich hierbei um eine Mailingliste mit wenigen Aussendungen, die für Mitteilungen zu Versionsveröffentlichungen, einschließlich wichtiger Sicherheitsveröffentlichungen, genutzt wird.
+Diese Mailingliste sollte abonniert werden. Zudem sollte die MediaWiki-Installation stets aktualisiert werden, sobald eine neue Programmversion veröffentlicht wurde.',
+ 'config-almost-done' => 'Der Vorgang ist fast abgeschlossen!
+Die verbliebenen Konfigurationseinstellungen können übersprungen und das Wiki umgehend installiert werden.',
+ 'config-optional-continue' => 'Sollen weitere Konfigurationseinstellungen vorgenommen werden?',
+ 'config-optional-skip' => 'Nein, das Wiki soll nun installiert werden.',
+ 'config-profile' => 'Profil der Benutzerberechtigungen:',
+ 'config-profile-wiki' => 'offenes Wiki',
+ 'config-profile-no-anon' => 'Erstellung eines Benutzerkontos erforderlich',
+ 'config-profile-fishbowl' => 'ausschließlich berechtigte Bearbeiter',
+ 'config-profile-private' => 'geschlossenes Wiki',
+ 'config-profile-help' => "Wikis sind am nützlichsten, wenn so viele Menschen als möglich Bearbeitungen vornehmen können.
+Mit MediaWiki ist es einfach die letzten Änderungen nachzuvollziehen und unbrauchbare Bearbeitungen, beispielsweise von unbedarften oder böswilligen Benutzern, rückgängig zu machen.
+
+Allerdings finden etliche Menschen Wikis auch mit anderen Bearbeitungskonzepten sinnvoll. Manchmal ist es auch nicht einfach alle Beteiligten vollständig von den Vorteilen des „Wiki-Prinzips” zu überzeugen. Darum ist eine Auswahl möglich.
+
+Ein '''{{int:config-profile-wiki}}''' ermöglicht es jedermann, sogar ohne über ein Benutzerkonto zu verfügen, Bearbeitungen vorzunehmen.
+Ein Wiki bei dem die '''{{int:config-profile-no-anon}}''' ist, bietet höhere Verantwortlichkeit des Einzelnen für seine Bearbeitungen, könnte allerdings Personen mit gelegentlichen Bearbeitungen abschrecken. Ein Wiki mit '''{{int:config-profile-fishbowl}}''' gestattet es nur ausgewählten Benutzern Bearbeitungen vorzunehmen. Allerdings kann dabei die Allgemeinheit die Seiten immer noch betrachten und Änderungen nachvollziehen. Ein '''{{int:config-profile-private}}''' gestattet es nur ausgewählten Benutzern, Seiten zu betrachten sowie zu bearbeiten.
+
+Komplexere Konzepte zur Zugriffssteuerung können erst nach abgeschlossenem Installationsvorgang eingerichtet werden. Hierzu gibt es weitere Informationen auf der Website mit der [http://www.mediawiki.org/wiki/Manual:User_rights entsprechenden Anleitung].",
+ 'config-license' => 'Lizenz:',
+ 'config-license-none' => 'Keine Lizenzangabe in der Fußzeile',
+ 'config-license-cc-by-sa' => 'Creative Commons „Namensnennung, Weitergabe unter gleichen Bedingungen“',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons „Namensnennung, nicht kommerziell, Weitergabe unter gleichen Bedingungen“',
+ 'config-license-cc-0' => 'Creative Commons „Zero“',
+ 'config-license-gfdl-old' => 'GNU-Lizenz für freie Dokumentation 1.2',
+ 'config-license-gfdl-current' => 'GNU-Lizenz für freie Dokumentation 1.3 oder höher',
+ 'config-license-pd' => 'Gemeinfreiheit',
+ 'config-license-cc-choose' => 'Eine benutzerdefinierte Creative-Commons-Lizenz auswählen',
+ 'config-license-help' => 'Viele öffentliche Wikis publizieren alle Beiträge unter einer [http://freedomdefined.org/Definition/De freien Lizenz].
+Dies trägt dazu bei ein Gefühl von Gemeinschaft zu schaffen und ermutigt zu längerfristiger Mitarbeit.
+Dahingegen ist im Allgemeinen eine freie Lizenz auf geschlossenen Wikis nicht notwendig.
+
+Sofern man Texte aus der Wikipedia verwenden möchte und umgekehrt, sollte die Creative Commons-Lizens „Namensnennung, Weitergabe unter gleichen Bedingungen“ gewählt werden.
+
+Die GNU-Lizenz für freie Dokumentation ist die ehemalige Lizenz der Wikipedia.
+Sie ist noch immer gültig, beinhaltet aber einige Bedingungen, welche die Wiederverwendung und deren Interpretation erschweren.',
+ 'config-email-settings' => 'E-Mail-Einstellungen',
+ 'config-enable-email' => 'Ausgehende E-Mails ermöglichen',
+ 'config-enable-email-help' => 'Sofern die E-Mail-Funktionen genutzt werden sollen, müssen die entsprechenden [http://www.php.net/manual/en/mail.configuration.php PHP-E-Mail-Einstellungen] richtig konfiguriert werden.
+Für den Fall, dass die E-Mail-Funktionen nicht benötigt werden, können sie hier deaktiviert werden.',
+ 'config-email-user' => 'E-Mail-Versand von Benutzer zu Benutzer aktivieren',
+ 'config-email-user-help' => 'Allen Benutzern ermöglichen, sich gegenseitig E-Mails zu schicken, sofern sie es in ihren Einstellungen aktiviert haben.',
+ 'config-email-usertalk' => 'Benachrichtigungen zu Änderungen an Benutzerdiskussionsseiten ermöglichen',
+ 'config-email-usertalk-help' => 'Ermöglicht es Benutzern, Benachrichtigungen zu Änderungen an ihren Benutzerdiskussionsseiten zu erhalten, sofern sie dies in ihren Einstellungen aktiviert haben.',
+ 'config-email-watchlist' => 'Benachrichtigungen zu Änderungen an Seiten auf der Beobachtungsliste ermöglichen',
+ 'config-email-watchlist-help' => 'Ermöglicht es Benutzern, Benachrichtigungen zu Änderungen an Seiten auf ihrer Beobachtungsliste zu erhalten, sofern sie dies in ihren Einstellungen aktiviert haben.',
+ 'config-email-auth' => 'E-Mail-Authentifizierung ermöglichen',
+ 'config-email-auth-help' => "Sofern diese Funktion aktiviert ist, müssen Benutzer ihre E-Mail-Adresse bestätigen, indem sie den Bestätigungslink nutzen, der ihnen immer dann zugesandt wird, wenn sie ihre E-Mail-Adresse angeben oder ändern.
+Nur bestätigte E-Mail-Adressen können Nachrichten von anderen Benutzer oder Benachrichtigungsmitteilungen erhalten.
+Die Aktivierung dieser Funktion wird bei offenen Wikis, mit Hinblick auf möglichen Missbrauch der E-Mailfunktionen, '''empfohlen'''.",
+ 'config-email-sender' => 'E-Mail-Adresse für Antworten:',
+ 'config-email-sender-help' => 'Bitte hier die E-Mail-Adresse angeben, die als Absenderadresse bei ausgehenden E-Mails eingesetzt werden soll.
+Rücklaufende E-Mails werden an diese E-Mail-Adresse gesandt.
+Bei viele E-Mail-Servern muss der Teil der E-Mail-Adresse mit der Domainangabe korrekt sein.',
+ 'config-upload-settings' => 'Hochladen von Bildern und Dateien',
+ 'config-upload-enable' => 'Das Hochladen von Dateien ermöglichen',
+ 'config-upload-help' => 'Das Hochladen von Dateien macht den Server für potentielle Sicherheitsprobleme anfällig.
+Weitere Informationen hierzu sollen im [http://www.mediawiki.org/wiki/Manual:Security Abschnitt Sicherheit] der Anleitung gelesen werden.
+
+Um das Hochladen von Dateien zu ermöglichen, muss der Zugriff auf das Unterverzeichnis <code>./images</code> so geändert werden, das es für den Webserver beschreibbar ist.
+Hernach kann diese Option aktiviert werden.',
+ 'config-upload-deleted' => 'Verzeichnis für gelöschte Dateien:',
+ 'config-upload-deleted-help' => 'Bitte ein Verzeichnis auswählen, in dem gelöschte Dateien archiviert werden sollen.
+Idealerweise sollte es nicht über das Internet zugänglich sein.',
+ 'config-logo' => 'URL des Logos:',
+ 'config-logo-help' => 'Die Standardoberfläche von MediaWiki verfügt, in der oberen linken Ecke, über Platz für eine Logo mit den Maßen 135x160 Pixel.
+Bitte ein Logo in entsprechender Größe hochladen und die zugehörige URL an dieser Stelle angeben.
+
+Sofern kein Logo benötigt wird, kann dieses Datenfeld leer bleiben.',
+ 'config-instantcommons' => '„InstantCommons“ aktivieren',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons InstantCommons] ist eine Funktion, die es Wikis ermöglicht, Bild-, Klang- und andere Mediendateien zu nutzen, die auf der Website [http://commons.wikimedia.org/ Wikimedia Commons] verfügbar sind.
+Um diese Funktion zu nutzen, muss MediaWiki eine Verbindung ins Internet herstellen können.
+
+Weitere Informationen zu dieser Funktion, einschließlich der Anleitung, wie andere Wikis als Wikimedia Commons eingerichtet werden können, gibt es im [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos Handbuch].',
+ 'config-cc-error' => 'Der Creativ-Commons-Lizenzassistent konnte keine Lizenz ermitteln.
+Die Lizenz ist daher jetzt manuell einzugeben.',
+ 'config-cc-again' => 'Erneut auswählen…',
+ 'config-cc-not-chosen' => 'Die gewünschte Creative-Commons-Lizenz auswählen und dann auf „weiter“ klicken.',
+ 'config-advanced-settings' => 'Erweiterte Konfiguration',
+ 'config-cache-options' => 'Einstellungen für die Zwischenspeicherung von Objekten:',
+ 'config-cache-help' => 'Objektcaching wird dazu genutzt die Geschwindigkeit von MediaWiki zu verbessern, indem häufig genutzte Daten zwischengespeichert werden.
+Mittelgroße bis große Wikis werden sehr ermutigt dies zu nutzen, aber auch für kleine Wikis ergeben sich erkennbare Vorteile.',
+ 'config-cache-none' => 'Kein Objektcaching (es wird keine Funktion entfernt, allerdings kann die Geschwindigkeit größerer Wikis beeinflusst werden)',
+ 'config-cache-accel' => 'Objektcaching von PHP (APC, eAccelerator, XCache or WinCache)',
+ 'config-cache-memcached' => 'Memchached Cacheserver nutzen (erfordert einen zusätzliche Installationsvorgang mitsamt Konfiguration)',
+ 'config-memcached-servers' => 'Memcached Cacheserver',
+ 'config-memcached-help' => 'Liste der für Memcached nutzbaren IP-Adressen.
+Es sollte eine je Zeile mitsamt des vorgesehenen Ports angegeben werden. Beispiele:
+127.0.0.1:11211
+192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Memcached wurde als Cacheserver ausgewählt. Dabei wurde allerdings kein Server angegeben.',
+ 'config-memcache-badip' => 'Es wurde für Memcached eine ungültige IP-Adresse angegeben: $1',
+ 'config-memcache-noport' => 'Es wurde kein Port zur Nutzung durch den Memcached Cacheserver angegeben: $1
+Sofern der Port unbekannt ist, ist 11211 die Standardangabe.',
+ 'config-memcache-badport' => 'Der Ports für den Memcached Cacheserver sollten zwischen $1 und $2 liegen',
+ 'config-extensions' => 'Erweiterungen',
+ 'config-extensions-help' => 'Die obig angegebenen Erweiterungen wurden im Verzeichnis <code>./extensions</code> gefunden.
+
+Sie könnten zusätzliche Konfigurierung erfordern, können aber bereits jetzt aktiviert werden.',
+ 'config-install-alreadydone' => "'''Warnung:''' Es wurde eine vorhandene MediaWiki-Installation gefunden.
+Es muss daher mit den nächsten Seite weitergemacht werden.",
+ 'config-install-begin' => 'Durch Drücken von „{{int:config-continue}}“ wird die Installation von MediaWiki gestartet.
+Sofern Änderungen vorgenommen werden sollen, kann man auf „Zurück“ klicken.',
+ 'config-install-step-done' => 'erledigt',
+ 'config-install-step-failed' => 'gescheitert',
+ 'config-install-extensions' => 'Einschließlich Erweiterungen',
+ 'config-install-database' => 'Datenbank wird eingerichtet',
+ 'config-install-pg-schema-not-exist' => 'Das PostgesSQL-Schema ist nicht vorhanden',
+ 'config-install-pg-schema-failed' => 'Das Erstellen der Datentabellen ist gescheitert.
+Es muss sichergestellt sein, dass der Benutzer „$1“ Schreibzugriff auf das Datenschema „$2“ hat.',
+ 'config-install-pg-commit' => 'Änderungen anwenden',
+ 'config-install-pg-plpgsql' => 'Suche nach der Datenbanksprache PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Für Datenbank $1 muss die Datenbanksprache PL/pgSQL installiert werden',
+ 'config-pg-no-create-privs' => 'Das für die Installation angegeben Konto verfügt nicht über ausreichende Berechtigungen, um ein Datenbanknutzerkonto zu erstellen.',
+ 'config-install-user' => 'Datenbankbenutzer wird erstellt',
+ 'config-install-user-alreadyexists' => 'Datenbankbenutzer „$1“ ist bereits vorhanden',
+ 'config-install-user-create-failed' => 'Das Anlegen des Datenbankbenutzers „$1“ ist gescheitert: $2',
+ 'config-install-user-grant-failed' => 'Die Gewährung der Berechtigung für Datenbankbenutzer „$1“ ist gescheitert: $2',
+ 'config-install-tables' => 'Datentabellen werden erstellt',
+ 'config-install-tables-exist' => "'''Warnung:''' Es wurden MediaWiki-Datentabellen gefunden.
+Die Erstellung wurde übersprungen.",
+ 'config-install-tables-failed' => "'''Fehler:''' Die Erstellung der Datentabellen ist aufgrund des folgenden Fehlers gescheitert: $1",
+ 'config-install-interwiki' => 'Interwikitabellen werden eingerichtet',
+ 'config-install-interwiki-list' => 'Die Datei <code>interwiki.list</code> konnte nicht gefunden werden.',
+ 'config-install-interwiki-exists' => "'''Warnung:''' Es wurden Interwikitabellen mit Daten gefunden.
+Die Standardliste wird übersprungen.",
+ 'config-install-stats' => 'Initialisierung der Statistiken',
+ 'config-install-keys' => 'Erstellung der Geheimschlüssel',
+ 'config-insecure-keys' => "'''Warnung:''' {{PLURAL:$2|Der Geheimschlüssel|Die Geheimschlüssel}} $1 {{PLURAL:$2|der|die}} während des Installationsvorgangs generiert wurde, ist nicht sehr sicher. {{PLURAL:$2|Er sollte|Sie sollten}} manuell geändert werden.",
+ 'config-install-sysop' => 'Administratorkonto wird erstellt',
+ 'config-install-subscribe-fail' => 'Abonnierung von „mediawiki-announce“ ist gescheitert',
+ 'config-install-mainpage' => 'Erstellung der Hauptseite mit Standardinhalten',
+ 'config-install-extension-tables' => 'Erstellung der Tabellen für die aktivierten Erweiterungen',
+ 'config-install-mainpage-failed' => 'Die Hauptseite konnte nicht erstellt werden: $1',
+ 'config-install-done' => "'''Herzlichen Glückwunsch!'''
+MediaWiki wurde erfolgreich installiert.
+
+Das Installationsprogramm hat die Datei <code>LocalSettings.php</code> erzeugt.
+Sie enthält alle Konfigurationseinstellungen.
+
+Diese Datei muss nun heruntergeladen und anschließend in das Stammverzeichnis der MediaWiki-Installation hochgeladen werden. Dies ist dasselbe Verzeichnis, in dem sich auch die Datei <code>index.php</code> befindet. Das Herunterladen sollte automatisch gestartet worden sein.
+
+Sofern dies nicht der Fall war, oder das Herunterladen unterbrochen wurde, kann der Vorgang durch einen Klick auf untenstehenden Link erneut gestartet werden:
+
+$3
+
+'''Hinweis:''' Sofern das Herunterladen der Konfigurationsdatei nicht jetzt durchgeführt wird, wird sie zu einem späteren Zeitpunkt nach dem Beenden des Installationsprogramms nicht mehr zur Verfügung stehen.
+
+Sobald dies alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''.",
+ 'config-download-localsettings' => 'LocalSettings.php herunterladen',
+ 'config-help' => 'Hilfe',
+);
+
+/** Esperanto (Esperanto)
+ * @author Yekrats
+ */
+$messages['eo'] = array(
+ 'config-your-language' => 'Via lingvo:',
+ 'config-your-language-help' => 'Elekti lingvon uzi dum la instalada procezo.',
+ 'config-wiki-language' => 'Lingvo de la vikio:',
+ 'config-wiki-language-help' => 'Elekti la ĉefe skribotan lingvon de la vikio.',
+ 'config-page-welcome' => 'Bonvenon al MediaWiki!',
+ 'config-page-dbsettings' => 'Agordoj de la datumbazo',
+ 'config-page-name' => 'Nomo',
+ 'config-page-options' => 'Agordoj',
+ 'config-page-install' => 'Instali',
+ 'config-page-complete' => 'Farita!',
+);
+
+/** Spanish (Español)
+ * @author Crazymadlover
+ * @author Danke7
+ * @author Platonides
+ * @author Sanbec
+ * @author Translationista
+ */
+$messages['es'] = array(
+ 'config-desc' => 'El instalador para MediaWiki',
+ 'config-title' => 'MediaWiki $1 instalación',
+ 'config-information' => 'Información',
+ 'config-localsettings-upgrade' => "'''Atención''': Se ha encontrado un fichero de configuración <code>LocalSettings.php</code>.
+Para actualizar MediaWiki mueva <code>LocalSettings.php</code> a un lugar seguro y ejecute de nuevo el instalador.",
+ 'config-session-error' => 'Error comenzando sesión: $1',
+ 'config-session-expired' => 'Tus datos de sesión parecen haber expirado.
+Las sesiones están configuradas por una duración de $1.
+Puedes incrementar esto configurando <code>session.gc_maxlifetime</code> en php.ini.
+Reiniciar el proceso de instalación.',
+ 'config-no-session' => 'Se han perdido los datos de sesión.
+Verifica tu php.ini y comprueba que <code>session.save_path</code> está establecido en un directorio apropiado.',
+ 'config-your-language' => 'Tu idioma:',
+ 'config-your-language-help' => 'Seleccionar un idioma a usar durante el proceso de instalación.',
+ 'config-wiki-language' => 'Idioma del wiki:',
+ 'config-wiki-language-help' => 'Seleccionar el idioma en el que el wiki será escrito predominantemente.',
+ 'config-back' => '← Atrás',
+ 'config-continue' => 'Continuar →',
+ 'config-page-language' => 'Idioma',
+ 'config-page-welcome' => 'Bienvenido a MediaWiki!',
+ 'config-page-dbconnect' => 'Conectar a la base de datos',
+ 'config-page-upgrade' => 'Actualizar instalación existente',
+ 'config-page-dbsettings' => 'Configuración de la base de datos',
+ 'config-page-name' => 'Nombre',
+ 'config-page-options' => 'Opciones',
+ 'config-page-install' => 'Instalar',
+ 'config-page-complete' => 'Completo!',
+ 'config-page-restart' => 'Reiniciar instalación',
+ 'config-page-readme' => 'Léeme',
+ 'config-page-releasenotes' => 'Notas de la versión',
+ 'config-page-copying' => 'Copiando',
+ 'config-page-upgradedoc' => 'Actualizando',
+ 'config-help-restart' => '¿Deseas borrar todos los datos que has ingresado hasta ahora y reiniciar el proceso de instalación desde el principio?',
+ 'config-restart' => 'Sí, reiniciarlo',
+ 'config-welcome' => '=== Comprobación del entorno ===
+Se realiza comprobacioens básicas para ver si el entorno es adecuado para la instalación de MediaWiki.
+Deberás suministrar los resultados de tales comprobaciones si necesitas ayuda durante la instalación.',
+ 'config-copyright' => "=== Derechos de autor y Términos de uso ===
+
+$1
+
+Este programa es software libre; puedes redistribuirlo y/o modificarlo en los términos de la Licencia Pública General de GNU, tal como aparece publicada por la Fundación para el Software Libre, tanto la versión 2 de la Licencia, como cualquier versión posterior (según prefiera).
+
+Este programa es distribuido en la esperanza de que sea útil, pero '''sin cualquier garantía'''; inclusive, sin la garantía implícita de la '''posibilidad de ser comercializado''' o de '''idoneidad para cualquier finalidad específica'''.
+Consulte la licencia *GNU General *Public *License para más detalles.
+
+En conjunto con este programa debe haber recibido <doclink href=Copying>una copia de la Licencia Pública General de GNU</doclink>; si no la recibió, pídala por escrito a Fundación para el Software Libre, Inc., 51 Franklin Street, Fifth Floor, Boston, ME La 02110-1301, USA o [http://www.gnu.org/copyleft/gpl.html léala en internet].",
+ 'config-sidebar' => '* [http://www.mediawiki.org Página principal de MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Guía del usuario]
+* [http://www.mediawiki.org/wiki/Manual:Contents Guía del administrador]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Preguntas frecuentes]',
+ 'config-env-good' => 'El entorno ha sido comprobado.
+Puedes instalar MediaWiki.',
+ 'config-env-bad' => 'El entorno ha sido comprobado.
+No puedes instalar MediaWiki.',
+ 'config-env-php' => 'PHP $1 está instalado.',
+ 'config-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 PECL] no está disponible para efectuar la normalización Unicode.
+Si tu web tiene un alto volumen de tráfico, te recomendamos leer acerca de [http://www.mediawiki.org/wiki/Unicode_normalization_considerations normalización Unicode].",
+ 'config-unicode-update-warning' => "'''Warning''': La versión instalada del contenedor de normalización Unicode usa una versión anterior de la biblioteca del [http://site.icu-project.org/ proyecto ICU].
+Deberás [http://www.mediawiki.org/wiki/Unicode_normalization_considerations actualizar] si realmente deseas usar Unicode.",
+ 'config-no-db' => 'No fue posible encontrar un controlador adecuado para la base de datos.',
+ 'config-no-db-help' => 'Necesitará instalar un controlador de base de datos para PHP.
+Estos son los tipos de base de datos compatibles: $1.
+
+Si tu web está en un alojamiento compartido, solicita a tu proveedor la instalación de un controlador de base de datos ocmpatible.
+Si has compilado tú mismo(a) el PHP, reconfigúralo con un cliente de base de datos habilitado, por ejemplo mediante <code>./configure --with-mysql</code>.
+Si instalaste el PHP a partir de un paquete Debian o Ubuntu, entonces necesitarás instalar también el módulo php5-mysql.',
+ 'config-no-fts3' => "'''Advertencia''': SQLite está compilado sin el [http://sqlite.org/fts3.html módulo FTS3]. Las funcionalidades de búsqueda no estarán disponibles en esta instalación.",
+ 'config-register-globals' => "'''Advertencia: La opción de <code>[http://php.net/register_globals register_globals]</code> de PHP está habilitada.'''
+'''Desactívela si puede.'''
+MediaWiki funcionará, pero tu servidor quedará expuesto a vulnerabilidades de seguridad potenciales.",
+ 'config-magic-quotes-runtime' => "'''Fatal: ¡[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está activada!'''
+Esta opción causa la imprevisible corrupción de la entrada de datos.
+No puedes instalar o utilizar MediaWiki a menos que esta opción esté inhabilitada.",
+ 'config-magic-quotes-sybase' => "'''Fatal: ¡[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está activada!'''
+Esta opción causa la imprevisible corrupción de la entrada de datos.
+No puedes instalar o utilizar MediaWiki a menos que esta opción esté inhabilitada.",
+ 'config-mbstring' => "'''Fatal: La opción [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está activada!'''
+Esta opción causa errores y puede corromper los datos de una forma imprevisible.
+No se puede instalar o usar MediaWiki a menos que esta opción sea desactivada.",
+ 'config-ze1' => "'''Fatal: ¡La opción [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] está activada!'''
+Esta opción causa problemas significativos en MediaWiki.
+No se puede instalar o usar MediaWiki a menos que esta opción sea desactivada.",
+ 'config-safe-mode' => "'''Advertencia:''' El [http://www.php.net/features.safe-mode modo seguro] de PHP está activado.
+Este modo puede causar problemas, especialmente en la carga de archivosy en compatibilidad con <code>math</code>.",
+ 'config-xml-bad' => 'Falta el módulo XML de PHP.
+MediaWiki necesita funciones en este módulo y no funcionará con esta configuración.
+Si está ejecutando Mandrake, instale el paquete php-xml.',
+ 'config-pcre' => 'Parece faltar el módulo de compatibilidad PCRE.
+MediaWiki necesita que las funciones de expresiones regulares compatibles con Perl estén funcionando.',
+ 'config-memory-raised' => 'el parámetro <code>memory_limit</code> de PHP es $1, aumentada a $2.',
+ 'config-memory-bad' => "'''Advertencia:''' El parámetro <code>memory_limit</code> de PHP es $1.
+Probablemente este valor es demasiado bajo.
+¡La instalación podrá fallar!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] está instalado',
+ 'config-apc' => '[http://www.php.net/apc APC] está instalado',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] está instalado',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] está instalado',
+ 'config-no-cache' => "'''Advertencia:''' No pudo encontrarse [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
+El caché de objetos no está habilitado.",
+ 'config-diff3-bad' => 'GNU diff3 no se encuentra.',
+ 'config-imagemagick' => 'ImageMagick encontrado: <code>$1</code>.
+La miniaturización de imágenes se habilitará si habilitas las cargas.',
+ 'config-gd' => 'Se ha encontrado una biblioteca de gráficos GD integrada.
+La miniaturización de imágenes se habilitará si habilitas las subidas.',
+ 'config-no-scaling' => 'No se ha encontrado ninguma biblioteca GD o ImageMagik.
+Se inhabilitará la miniaturización de imágenes.',
+ 'config-no-uri' => "'''Error:''' No se pudo determinar el URI actual.
+Instalación abortada.",
+ 'config-db-type' => 'Tipo de base de datos',
+ 'config-db-host' => 'Servidor de la base de datos:',
+ 'config-db-wiki-settings' => 'Identifique este wiki',
+ 'config-db-name' => 'Nombre de base de datos:',
+ 'config-db-install-account' => 'Cuenta de usuario para instalación',
+ 'config-db-username' => 'Nombre de usuario de base de datos:',
+ 'config-db-password' => 'contraseña de base de datos:',
+ 'config-db-install-help' => 'Ingresar el nombre de usuario y la contraseña que será usada para conectar a la base de datos durante el proceso de instalación.',
+ 'config-db-account-lock' => 'Usar el mismo nombre de usuario y contraseña durante operación normal',
+ 'config-db-wiki-account' => 'Usar cuenta para operación normal',
+ 'config-db-wiki-help' => 'Introduce el nombre de usuario y la contraseña que serán usados para acceder a la base de datos durante la operación normal del wiki.
+Si esta cuenta no existe y la cuenta de instalación tiene suficientes privilegios, se creará esta cuenta de usuario con los privilegios mínimos necesarios para la operación normal del wiki.',
+ 'config-db-prefix' => 'Prefijo para las tablas de la base de datos:',
+ 'config-db-charset' => 'Conjunto de caracteres de la base de datos',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binario',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 retrocompatible UTF-8',
+ 'config-mysql-old' => 'Se necesita MySQL $1 o una versión más reciente. Tienes la versión $2.',
+ 'config-db-port' => 'Puerto de la base de datos:',
+ 'config-db-schema' => 'Esquema para MediaWiki',
+ 'config-db-schema-help' => 'Normalmente, los esquemas arriba son los correctos.
+Altéralos sólo si tienes la seguridad de que necesitas alterarlos.',
+ 'config-sqlite-dir' => 'Directorio de datos SQLite:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki es compatible con los siguientes sistemas de bases de datos:
+
+$1
+
+Si no encuentras en el listado el sistema de base de datos que estás intentando utilizar, sigue las instrucciones vinculadas arriba para habilitar la compatibilidad.',
+ 'config-support-mysql' => '* $1 es la base de datos mayoritaria para MediaWiki y la que goza de mayor compatibilidad ([http://www.php.net/manual/es/mysql.installation.php cómo compilar PHP con compatibilidad MySQL])',
+ 'config-support-postgres' => '* $1 es una popular base de datos de código abierto, alternativa a MySQL. ([http://www.php.net/manual/es/pgsql.installation.php cómo compilar PHP con compatibilidad PostgreSQL])',
+ 'config-support-sqlite' => '* $1 es una base de datos ligera con gran compatibilidad con MediaWiki. ([http://www.php.net/manual/es/pdo.installation.php Cómo compilar PHP con compatibilidad SQLite], usa PDO)',
+ 'config-support-oracle' => '* $1 es una base de datos comercial a nivel empresarial ([http://www.php.net/manual/es/oci8.installation.php cómo compilar PHP con compatibilidad con OCI8])',
+ 'config-header-mysql' => 'Configuración de MySQL',
+ 'config-header-postgres' => 'Configuración de PostgreSQL',
+ 'config-header-sqlite' => 'Configuración de SQLite',
+ 'config-header-oracle' => 'Configuración de Oracle',
+ 'config-invalid-db-type' => 'Tipo de base de datos inválida',
+ 'config-missing-db-name' => 'Debes introducir un valor para "Nombre de la base de datos"',
+ 'config-invalid-db-name' => 'El nombre de la base de datos "$1" es inválido.
+Usa sólo caracteres ASCII: letras (a-z, A-Z), guarismos (0-9) y guiones bajos (_).',
+ 'config-invalid-db-prefix' => 'El prefijo de la base de datos "$1" es inválido.
+Use sólo carateres ASCII: letras (a-z, A-Z), guarismos (0-9) y guiones bajos (_).',
+ 'config-connection-error' => '$1.
+
+Verifique el servidor, el nombre de usuario y la contraseña, e intente de nuevo.',
+ 'config-invalid-schema' => 'El esquema de la base de datos "$1" es inválido.
+Use sólo carateres ASCII: letras (a-z, A-Z), guarismos (0-9) y guiones bajos (_).',
+ 'config-postgres-old' => 'Se necesita PostgreSQL $1 o una versión más reciente; tienes la versión $2.',
+ 'config-sqlite-name-help' => 'Elige el nombre que identificará tu wiki.
+No uses espacios o guiones.
+Este nombre será usado como nombre del archivo de datos de SQLite.',
+ 'config-sqlite-mkdir-error' => 'Error al crear el directorio de datos "$1".
+Comprueba la ubicación e inténtalo de nuevo.',
+ 'config-sqlite-dir-unwritable' => 'No se puede escribir en el directorio "$1".
+Modifica los permisos para que el servidor web pueda escribir en él y vuelve a intentarlo.',
+ 'config-sqlite-connection-error' => '$1.
+
+Verifique el directório de datos y el nombre de la base de datos mostrada a continuación e inténtalo nuevamente.',
+ 'config-sqlite-readonly' => 'El archivo <code>$1</code> no se puede escribir.',
+ 'config-sqlite-cant-create-db' => 'No fue posible crear el archivo de la base de datos <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'El PHP no tiene compatibilidad FTS3. actualizando tablas a una versión anterior',
+ 'config-can-upgrade' => "Esta base de datos contiene tablas de MediaWiki.
+Para actualizarlas a MediaWiki $1, haz clic en '''Continuar'''.",
+ 'config-regenerate' => 'Regenerar LocalSettings.php →',
+ 'config-show-table-status' => 'SHOW TABLE STATUS ha fallado!',
+ 'config-unknown-collation' => "'''Advertencia:''' La base de datos está utilizando una intercalación no reconocida.",
+ 'config-db-web-account' => 'Cuenta de base de datos para acceso Web',
+ 'config-db-web-help' => 'Elige el usuario y contraseña que el servidor Web usará para conectarse al servidor de la base de datos durante el fincionamiento normal del wiki.',
+ 'config-db-web-account-same' => 'Utilizar la misma cuenta que en la instalación',
+ 'config-db-web-create' => 'Crear la cuenta si no existe',
+ 'config-db-web-no-create-privs' => 'La cuenta que has especificado para la instalación no tiene privilegios suficientes para crear una cuenta.
+La cuenta que especifiques aquí debe existir.',
+ 'config-mysql-engine' => 'Motor de almacenamiento:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' es casi siempre la mejor opción, dado que soporta bien los accesos simultáneos.
+
+'''MyISAM''' es más rápido en instalaciones de usuario único o de sólo lectura.
+Las bases de datos MyISAM tienden a corromperse más a menudo que las bases de datos InnoDB.",
+ 'config-mysql-charset' => 'Conjunto de caracteres de la base de datos:',
+ 'config-mysql-binary' => 'Binario',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Nombre del wiki:',
+ 'config-site-name-help' => 'Esto aparecerá en la barra de título del navegador y en varios otros lugares.',
+ 'config-site-name-blank' => 'Ingresar un nombre de sitio.',
+ 'config-project-namespace' => 'Espacio de nombre de proyecto:',
+ 'config-ns-generic' => 'Proyecto',
+ 'config-ns-site-name' => 'Igual como el nombre del wiki: $1',
+ 'config-ns-other' => 'Otro (especificar)',
+ 'config-ns-other-default' => 'MiWiki',
+ 'config-ns-invalid' => 'El espacio de nombre especificado "<nowiki>$1</nowiki>" no es válido.
+Especifica un espacio de nombre de proyecto diferente.',
+ 'config-admin-box' => 'Cuenta de administrador',
+ 'config-admin-name' => 'Tu nombre:',
+ 'config-admin-password' => 'Contraseña:',
+ 'config-admin-password-confirm' => 'Repita la contraseña:',
+ 'config-admin-help' => 'Escribe aquí el nombre de usuario que desees, como por ejemplo "Pedro Bloggs".
+Este es el nombre que usarás para entrar al wiki.',
+ 'config-admin-name-blank' => 'Introduce un nombre de usuario de administrador.',
+ 'config-admin-name-invalid' => 'El nombre de usuario especificado "<nowiki>$1</nowiki>" no es válido.
+Especifique un nombre de usuario diferente.',
+ 'config-admin-password-blank' => 'Introduzca una contraseña para la cuenta de administrador.',
+ 'config-admin-password-same' => 'La contraseña no debe ser la misma que el nombre de usuario.',
+ 'config-admin-password-mismatch' => 'Las dos contraseñas que ingresaste no coinciden.',
+ 'config-admin-email' => 'Dirección de correo electrónico:',
+ 'config-admin-email-help' => 'Introduce aquí un correo electrónico que te permita recibir mensajes de otros usuarios del wiki, vuelve a configurar tu contraseña y recibe notificaciones de cambios realizados a tus páginas vigiladas.',
+ '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-subscribe' => 'Suscribirse para recibir [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce avisos de nuevas versiones].',
+ 'config-subscribe-help' => 'Esta es una lista de divulgación de bajo volumen para anuncios de lanzamiento de versiones nuevas, incluyendo anuncios de seguridad importantes.
+Te recomendamos suscribirte y actualizar tu instalación MediaWiki cada vez que se lance una nueva versión.',
+ 'config-almost-done' => '¡Ya casi has terminado!
+Ahora puedes saltarte el resto de pasos e instalar el wiki con valores predeterminados.',
+ 'config-optional-continue' => 'Hazme más preguntas.',
+ 'config-optional-skip' => 'Ya estoy aburrido, sólo instala el wiki.',
+ 'config-profile' => 'Perfil de derechos de usuario:',
+ 'config-profile-wiki' => 'Wiki tradicional',
+ 'config-profile-no-anon' => 'Creación de cuenta requerida',
+ 'config-profile-fishbowl' => 'Sólo editores autorizados',
+ 'config-profile-private' => 'Wiki privado',
+ 'config-license' => 'Copyright and licencia:',
+ 'config-license-none' => 'Pie sin licencia',
+ 'config-license-cc-by-sa' => 'Creative Commons Reconocimiento Compartir Igual',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Reconocimiento Compartir Igual no comercial',
+ 'config-license-gfdl-old' => 'GNU Licencia de Documentación Libre 1.2',
+ 'config-license-gfdl-current' => 'Licencia de documentación libre GNU 1.3 o más reciente',
+ 'config-license-pd' => 'Dominio Público',
+ 'config-license-cc-choose' => 'Selecciona una licencia personalizada de Creative Commons',
+ 'config-email-settings' => 'Configuración de correo electrónico',
+ 'config-enable-email' => 'Activar el envío de e-mails',
+ 'config-enable-email-help' => 'Si quieres que el correo electrónico funcione, la [http://www.php.net/manual/en/mail.configuration.php configuración PHP de correo electrónico] debe ser la correcta.
+Si no quieres la funcionalidad de correo electrónico, puedes desactivarla aquí.',
+ 'config-email-user' => 'Habilitar correo electrónico de usuario a usuario',
+ 'config-email-user-help' => 'Permitir que todos los usuarios intercambien correos electrónicos si lo han activado en sus preferencias.',
+ 'config-email-usertalk' => 'Activar notificaciones de páginas de discusión de usuarios',
+ 'config-email-usertalk-help' => 'Permitir a los usuarios recibir notificaciones de cambios en la página de discusión de usuario, si lo han activado en sus preferencias.',
+ 'config-email-watchlist' => 'Activar notificación de alteraciones a la páginas vigiladas',
+ 'config-email-watchlist-help' => 'Permitir a los usuarios recibir notificaciones de cambios en la páginas que vigilan, si lo han activado en sus preferencias.',
+ 'config-email-auth' => 'Activar autenticación del correo electrónico',
+ 'config-email-sender' => 'Dirección de correo electrónico de retorno:',
+ 'config-email-sender-help' => 'Introduce la dirección de correo electrónico que será usada como dirección de retorno en los mensajes electrónicos de salida.
+Aquí llegarán los correos electrónicos que no lleguen a su destino.
+Muchos servidores de correo electrónico exigen que por lo menos la parte del nombre del dominio sea válida.',
+ 'config-upload-settings' => 'Cargas de imágenes y archivos',
+ 'config-upload-enable' => 'Habilitar la subida de archivos',
+ 'config-upload-deleted' => '*Directório para los archivos eliminados:',
+ 'config-upload-deleted-help' => 'Elige un directorio en el que guardar los archivos eliminados.
+Lo ideal es una carpeta no accesible desde la red.',
+ 'config-logo' => 'URL del logo :',
+ 'config-instantcommons' => 'Habilitar Instant Commons',
+ 'config-cc-error' => 'El selector de licencia de Creative Commons no dio resultado.
+Escribe el nombre de la licencia manualmente.',
+ 'config-cc-again' => 'Elegir otra vez...',
+ 'config-cc-not-chosen' => 'Elige la licencia Creative Commons que desees y haz clic en "continuar".',
+ 'config-advanced-settings' => 'Configuración avanzada',
+ 'config-cache-options' => 'Configuración de la caché de objetos:',
+ 'config-cache-help' => 'El almacenamiento en caché de objetos se utiliza para mejorar la velocidad de MediaWiki mediante el almacenamiento en caché los datos usados más frecuentemente.
+A los sitios medianos y grandes se les recomienda que permitirlo. También es beneficioso para los sitios pequeños.',
+ 'config-cache-none' => 'Sin almacenamiento en caché (no se pierde ninguna funcionalidad, pero la velocidad puede resentirse en sitios grandes)',
+ 'config-cache-accel' => 'Almacenamiento en caché de objetos PHP (APC, eAccelerator, XCache o WinCache)',
+ 'config-cache-memcached' => 'Utilizar Memcached (necesita ser instalado y configurado aparte)',
+ 'config-memcached-servers' => 'Servidores Memcached:',
+ 'config-memcached-help' => 'Lista de direcciones IP que serán usadas para Memcached.
+Deben ser separadas por comas y especificar el puerto a utilizar (por ejemplo: 127.0.0.1:11211, 192.168.1.25:11211).',
+ 'config-extensions' => 'Extensiones',
+ 'config-extensions-help' => 'Se ha detectado en tu directorio <code>./extensions</code> las extensiones listadas arriba.
+
+Puede que necesiten configuraciones adicionales, pero puedes habilitarlas ahora.',
+ 'config-install-alreadydone' => "'''Aviso:''' Parece que ya habías instalado MediaWiki y estás intentando instalarlo nuevamente.
+Pasa a la próxima página, por favor.",
+ 'config-install-step-done' => 'hecho',
+ 'config-install-step-failed' => 'falló',
+ 'config-install-extensions' => 'Extensiones inclusive',
+ 'config-install-database' => 'Configurando la base de datos',
+ 'config-install-pg-schema-failed' => 'La creación de las tablas ha fallado.
+Asegúrate de que el usuario "$1" puede escribir en el esquema "$2".',
+ 'config-install-user' => 'Creando el usuario de la base de datos',
+ 'config-install-user-grant-failed' => 'La concesión de permisos para el usuario "$1" ha fallado: $2',
+ 'config-install-tables' => 'Creando tablas',
+ 'config-install-tables-exist' => "'''Advertencia''': Al parecer, las tablas de MediaWiki ya existen. Saltándose su creación.",
+ 'config-install-tables-failed' => "'''Error''': La creación de las tablas falló con el siguiente error: $1",
+ 'config-install-interwiki' => 'Llenando la tabla interwiki predeterminada',
+ 'config-install-interwiki-list' => 'No se pudo encontrar el archivo <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Advertencia''': La tabla de interwikis parece ya contener entradas.
+Se omitirá la lista predeterminada.",
+ 'config-install-keys' => 'Generación de clave secreta',
+ 'config-install-sysop' => 'Creando cuenta de usuario del administrador',
+);
+
+/** Basque (Euskara)
+ * @author An13sa
+ */
+$messages['eu'] = array(
+ 'config-desc' => 'MediaWiki instalatzailea',
+ 'config-title' => 'MediaWiki $1 instalazioa',
+ 'config-information' => 'Informazioa',
+ 'config-session-error' => 'Saio hasierako errorea: $1',
+ 'config-your-language' => 'Zure hizkuntza:',
+ 'config-your-language-help' => 'Aukeratu instalazio prozesuan erabiliko den hizkuntza',
+ 'config-wiki-language' => 'Wiki hizkuntza:',
+ 'config-back' => '← Atzera',
+ 'config-continue' => 'Jarraitu →',
+ 'config-page-language' => 'Hizkuntza',
+ 'config-page-welcome' => 'Ongi etorri MediaWikira!',
+ 'config-page-dbconnect' => 'Datu-basera konektatu',
+ 'config-page-dbsettings' => 'Datu-basearen ezarpenak',
+ 'config-page-name' => 'Izena',
+ 'config-page-options' => 'Aukerak',
+ 'config-page-install' => 'Instalatu',
+ 'config-page-complete' => 'Bukatua!',
+ 'config-page-restart' => 'Instalazioa berriz hasi',
+ 'config-page-readme' => 'Irakur nazazu',
+ 'config-page-copying' => 'Kopiatzea',
+ 'config-page-upgradedoc' => 'Eguneratu',
+ 'config-restart' => 'Bai, berriz hasi',
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki nagusia]
+* [http://www.mediawiki.org/wiki/Help:Contents Erabiltzaileentzako Gida]
+* [http://www.mediawiki.org/wiki/Manual:Contents Administratzaileentzako Gida]
+* [http://www.mediawiki.org/wiki/Manual:FAQ MEG]',
+ 'config-env-php' => 'PHP $1 instalatuta dago.',
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] instalatuta dago',
+ 'config-apc' => '[http://www.php.net/apc APC] instalatuta dago',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] instalatuta dago',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] instalatuta dago',
+ 'config-diff3-bad' => 'GNU diff3 ez da aurkitu.',
+ 'config-db-type' => 'Datu-base mota:',
+ 'config-db-wiki-settings' => 'Wiki hau identifikatu',
+ 'config-db-name' => 'Datu-base izena:',
+ 'config-db-username' => 'Datu-base lankide izena:',
+ 'config-db-password' => 'Datu-base pasahitza:',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 bitarra',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-header-mysql' => 'MySQL hobespenak',
+ 'config-header-postgres' => 'PostgreSQL hobespenak',
+ 'config-header-sqlite' => 'SQLite hobespenak',
+ 'config-header-oracle' => 'Oracle hobespenak',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-binary' => 'Bitarra',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Wikiaren izena:',
+ 'config-project-namespace' => 'Proiektuaren izen-tartea:',
+ 'config-ns-generic' => 'Proiektua',
+ 'config-ns-other' => 'Bestelakoa (zehaztu)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-admin-box' => 'Administratzaile kontua',
+ 'config-admin-name' => 'Zure izena:',
+ 'config-admin-password' => 'Pasahitza:',
+ 'config-admin-password-confirm' => 'Pasahitza berriz:',
+ 'config-admin-email' => 'E-posta helbidea:',
+ 'config-profile-wiki' => 'Wiki tradizionala',
+ 'config-profile-private' => 'Wiki pribatua',
+ 'config-license' => 'Copyright eta lizentzia:',
+ 'config-license-pd' => 'Domeinu Askea',
+ 'config-email-settings' => 'E-posta hobespenak',
+ 'config-logo' => 'Logo URL:',
+ 'config-install-step-done' => 'egina',
+);
+
+/** Persian (فارسی)
+ * @author Mjbmr
+ */
+$messages['fa'] = array(
+ 'config-your-language' => 'زبان شما:',
+ 'config-wiki-language' => 'زبان ویکی:',
+ 'config-page-language' => 'زبان',
+);
+
+/** Finnish (Suomi)
+ * @author Centerlink
+ * @author Crt
+ * @author Nike
+ * @author Olli
+ * @author Str4nd
+ */
+$messages['fi'] = array(
+ 'config-desc' => 'MediaWiki-asennin',
+ 'config-title' => 'MediaWikin version $1 asennus',
+ 'config-information' => 'Tiedot',
+ 'config-localsettings-upgrade' => '<code>LocalSettings.php</code>-tiedosto on havaittu.
+Syötä kohdan <code>$wgUpgradeKey</code> arvo alla olevaan kenttään päivittääksesi asennuksen.
+Löydät sen LocalSettings.php-tiedostosta.',
+ 'config-session-error' => 'Istunnon aloittaminen epäonnistui: $1',
+ 'config-session-expired' => 'Istuntotietosi näyttävät olevan vanhentuneita.
+Istuntojen elinajaksi on määritelty $1.
+Voit muuttaa tätä asetusta vaihtamalla kohtaa <code>session.gc_maxlifetime</code> php.ini -tiedostossa.
+Käynnistä asennusprosessi uudelleen.',
+ 'config-your-language' => 'Asennuksen kieli',
+ 'config-your-language-help' => 'Valitse kieli, jota haluat käyttää asennuksen ajan.',
+ 'config-wiki-language' => 'Wikin kieli',
+ 'config-wiki-language-help' => 'Valitse kieli, jota wikissä tullaan etupäässä käyttämään.',
+ 'config-back' => '← Takaisin',
+ 'config-continue' => 'Jatka →',
+ 'config-page-language' => 'Kieli',
+ 'config-page-welcome' => 'Tervetuloa MediaWikiin!',
+ 'config-page-dbconnect' => 'Tietokantaan yhdistäminen',
+ 'config-page-upgrade' => 'Olemassa olevan asennuksen päivitys',
+ 'config-page-dbsettings' => 'Tietokannan asetukset',
+ 'config-page-name' => 'Nimi',
+ 'config-page-options' => 'Asetukset',
+ 'config-page-install' => 'Asenna',
+ 'config-page-complete' => 'Valmis!',
+ 'config-page-restart' => 'Aloita asennus alusta',
+ 'config-page-readme' => 'Lue minut',
+ 'config-page-releasenotes' => 'Julkaisun tiedot',
+ 'config-page-copying' => 'Kopiointi',
+ 'config-page-upgradedoc' => 'Päivittäminen',
+ 'config-help-restart' => 'Haluatko poistaa kaikki annetut tiedot ja aloittaa asennuksen alusta?',
+ 'config-restart' => 'Kyllä',
+ 'config-welcome' => '=== Ympäristön tarkistukset ===
+Varmistetaan MediaWikin asennettavuus tähän ympäristöön.
+Sinun pitäisi antaa näiden tarkistusten tulokset, jos tarvitset apua asennuksen aikana.',
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWikin kotisivu]
+* [http://www.mediawiki.org/wiki/Help:Contents Käyttöopas]
+* [http://www.mediawiki.org/wiki/Manual:Contents Hallintaopas]
+* [http://www.mediawiki.org/wiki/Manual:FAQ UKK]',
+ 'config-env-good' => 'Asennusympäristö on tarkastettu.
+Voit asentaa MediaWikin.',
+ 'config-env-bad' => 'Asennusympäristö on tarkastettu.
+Et voi asentaa MediaWikiä.',
+ 'config-env-php' => 'PHP $1 on asennettu.',
+ 'config-no-db' => 'Sopivaa tietokanta-ajuria ei löytynyt!',
+ 'config-no-db-help' => 'Sinun täytyy asentaa tietokanta-ajuri PHP:lle.
+Seuraavat tietokantatyypit on tuettu: $1.
+
+Jos käytät jaettua sivutilaa, kysy palveluntarjoajalta, josko se voisi asentaa sopivan tietokanta-ajurin.
+Jos olet kääntänyt PHP:n itse, asenna se tietokantaohjelman kanssa, esimerkiksi käyttäen koodia <code>./configure --with-mysql</code>.
+Jos olet asentanut PHP:n Debian tai Ubuntu-paketista, sinun täytyy asentaa myös php5-mysql-moduuli.',
+ 'config-safe-mode' => "'''Varoitus:''' PHP:n [http://www.php.net/features.safe-mode safe mode] -tila on aktiivinen.
+Se voi aiheuttaa ongelmia erityisesti tiedostojen tallentamisen ja matemaattisten kaavojen kanssa.",
+ 'config-pcre' => 'PCRE-tukimoduuli puuttuu.
+MediaWiki vaatii toimiakseen Perl-yhteensopivat säännölliset lausekkeet.',
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] on asennettu',
+ 'config-apc' => '[http://www.php.net/apc APC] on asennettu.',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] on asennettu',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] on asennettu',
+ 'config-diff3-bad' => 'GNU diff3:a ei löytynyt.',
+ 'config-db-type' => 'Tietokannan tyyppi',
+ 'config-db-host' => 'Tietokantapalvelin',
+ 'config-db-name' => 'Tietokannan nimi',
+ 'config-db-username' => 'Tietokannan käyttäjätunnus',
+ 'config-db-password' => 'Tietokannan salasana',
+ 'config-db-install-help' => 'Anna käyttäjätunnus ja salasana, joita käytetään asennuksen aikana.',
+ 'config-db-account-lock' => 'Käytä samaa tunnusta ja salasanaa myös asennuksen jälkeen',
+ 'config-db-prefix' => 'Tietokantataulujen etuliite',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0, binääri',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0, UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0, taaksepäin yhteensopiva UTF-8',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-header-mysql' => 'MySQL-asetukset',
+ 'config-header-postgres' => 'PostgreSQL-asetukset',
+ 'config-header-sqlite' => 'SQLite-asetukset',
+ 'config-header-oracle' => 'Oracle-asetukset',
+ 'config-invalid-db-type' => 'Virheellinen tietokantatyyppi',
+ 'config-missing-db-name' => 'Kenttä »Tietokannan nimi» on pakollinen',
+ 'config-invalid-db-name' => '”$1” ei kelpaa tietokannan nimeksi.
+Se voi sisältää vain kirjaimia (a-z, A-Z), numeroita (0-9) ja alaviivan (_).',
+ 'config-invalid-db-prefix' => '”$1” ei kelpaa tietokannan etuliitteeksi.
+Se voi sisältää vain kirjaimia (a-z, A-Z), numeroita (0-9) ja alaviivan (_).',
+ 'config-postgres-old' => 'MediaWiki tarvitsee PostgreSQL:n version $1 tai uudemman. Nykyinen versio on $2.',
+ 'config-sqlite-name-help' => 'Valitse nimi joka yksilöi tämän wikin.
+Älä käytä välilyöntejä tai viivoja.
+Nimeä käytetään SQlite-tietokannan tiedostonimessä.',
+ 'config-sqlite-dir-unwritable' => 'Hakemistoon ”$1” kirjoittaminen epäonnistui.
+Muuta hakemiston käyttöoikeuksia siten, että palvelinohjelmisto voi kirjoittaa siihen ja koita uudelleen.',
+ 'config-sqlite-readonly' => 'Tiedostoon <code>$1</code> ei voi kirjoittaa.',
+ 'config-sqlite-fts3-downgrade' => 'PHP:stä puuttuu FTS3-tuki. Poistetaan ominaisuus käytöstä tietokantatauluista.',
+ 'config-show-table-status' => 'Kysely SHOW TABLE STATUS epäonnistui!',
+ 'config-mysql-engine' => 'Tallennusmoottori',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-binary' => 'Binääri',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Wikin nimi',
+ 'config-project-namespace' => 'Projektinimiavaruus:',
+ 'config-ns-generic' => 'Projekti',
+ 'config-admin-name' => 'Nimesi',
+ 'config-admin-password' => 'Salasana',
+ 'config-admin-password-confirm' => 'Salasana uudelleen',
+ 'config-admin-name-blank' => 'Anna ylläpitäjän käyttäjänimi.',
+ 'config-admin-email' => 'Sähköpostiosoite',
+ 'config-profile-private' => 'Yksityinen wiki',
+ 'config-install-step-done' => 'tehty',
+ 'config-install-step-failed' => 'epäonnistui',
+ 'config-help' => 'ohje',
+);
+
+/** French (Français)
+ * @author Aadri
+ * @author Crochet.david
+ * @author Hashar
+ * @author IAlex
+ * @author Jean-Frédéric
+ * @author McDutchie
+ * @author Peter17
+ * @author Sherbrooke
+ * @author Verdy p
+ * @author Yumeki
+ */
+$messages['fr'] = array(
+ 'config-desc' => 'Le programme d’installation de MediaWiki',
+ 'config-title' => 'Installation de MediaWiki $1',
+ 'config-information' => 'Informations',
+ 'config-localsettings-upgrade' => 'Un fichier <code>LocalSettings.php</code> a été détecté.
+Pour mettre à jour cette installation, veuillez saisir la valeur de <code>$wgUpgradeKey</code> dans le champ ci-dessous.
+Vous la trouverez dans LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Un fichier LocalSettings.php a été détecté.
+Pour mettre à niveau cette installation, veuillez exécuter update.php',
+ 'config-localsettings-key' => 'Clé de mise à jour :',
+ 'config-localsettings-badkey' => 'La clé que vous avez fournie est incorrecte',
+ 'config-upgrade-key-missing' => 'Une installation existante de MediaWiki a été détectée.
+
+Pour mettre à jour cette installation, veuillez ajouter la ligne suivante à la fin de votre fichier LocalSettings.php
+
+$1',
+ 'config-localsettings-incomplete' => 'Le fichier LocalSettings.php existant semble être incomplet.
+La variable $1 n’est pas définie.
+Veuillez modifier LocalSettings.php de sorte que cette variable soit définie, puis cliquer sur « Continuer ».',
+ 'config-localsettings-connection-error' => 'Une erreur est survenue lors de la connexion à la base de données en utilisant la configuration spécifiée dans LocalSettings.php ou AdminSettings.php. Veuillez corriger cette configuration puis réessayer.
+
+$1',
+ 'config-session-error' => 'Erreur lors du démarrage de la session : $1',
+ 'config-session-expired' => "↓Les données de votre session semblent avoir expiré.
+Les sessions sont configurées pour une durée de $1.
+Vous pouvez l'augmenter en configurant <code>session.gc_maxlifetime</code> dans le fichier php.ini.
+Redémarrer le processus d'installation.",
+ 'config-no-session' => 'Les données de votre session ont été perdues !
+Vérifiez votre fichier php.ini et assurez-vous que <code>session.save_path</code> contient le chemin d’un répertoire approprié.',
+ 'config-your-language' => 'Votre langue :',
+ 'config-your-language-help' => "Sélectionnez la langue à utiliser pendant le processus d'installation.",
+ 'config-wiki-language' => 'Langue du wiki :',
+ 'config-wiki-language-help' => 'Sélectionner la langue dans laquelle le wiki sera principalement écrit.',
+ 'config-back' => '← Retour',
+ 'config-continue' => 'Continuer →',
+ 'config-page-language' => 'Langue',
+ 'config-page-welcome' => 'Bienvenue sur MediaWiki !',
+ 'config-page-dbconnect' => 'Se connecter à la base de données',
+ 'config-page-upgrade' => 'Mettre à jour l’installation existante',
+ 'config-page-dbsettings' => 'Paramètres de la base de données',
+ 'config-page-name' => 'Nom',
+ 'config-page-options' => 'Options',
+ 'config-page-install' => 'Installer',
+ 'config-page-complete' => 'Terminé !',
+ 'config-page-restart' => 'Redémarrer l’installation',
+ 'config-page-readme' => 'Lisez-moi',
+ 'config-page-releasenotes' => 'Notes de version',
+ 'config-page-copying' => 'Copie',
+ 'config-page-upgradedoc' => 'Mise à jour',
+ 'config-page-existingwiki' => 'Wiki existant',
+ 'config-help-restart' => "Voulez-vous effacer toutes les données enregistrées que vous avez entrées et relancer le processus d'installation ?",
+ 'config-restart' => 'Oui, le relancer',
+ 'config-welcome' => "=== Vérifications liées à l’environnement ===
+Des vérifications de base sont effectuées pour voir si cet environnement est adapté à l'installation de MediaWiki.
+Vous devriez indiquer les résultats de ces vérifications si vous avez besoin d’aide lors de l’installation.",
+ 'config-copyright' => "=== Droit d'auteur et conditions ===
+
+$1
+
+Ce programme est un logiciel libre : vous pouvez le redistribuer et/ou le modifier selon les termes de la Licence Publique Générale GNU telle que publiée par la Free Software Foundation (version 2 de la Licence, ou, à votre choix, toute version ultérieure).
+
+Ce programme est distribué dans l’espoir qu’il sera utile, mais '''sans aucune garantie''' : sans même les garanties implicites de '''commerciabilité''' ou d’'''adéquation à un usage particulier'''.
+Voir la Licence Publique Générale GNU pour plus de détails.
+
+Vous devriez avoir reçu <doclink href=Copying>une copie de la Licence Publique Générale GNU</doclink> avec ce programme ; dans le cas contraire, écrivez à la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ou [http://www.gnu.org/copyleft/gpl.html lisez-le en ligne].",
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki Accueil]
+* [http://www.mediawiki.org/wiki/Help:Contents Guide de l’utilisateur]
+* [http://www.mediawiki.org/wiki/Manual:Contents Guide de l’administrateur]
+* [http://www.mediawiki.org/wiki/Manual:FAQ FAQ]
+----
+* <doclink href=Readme>Lisez-moi</doclink>
+* <doclink href=ReleaseNotes>Notes de pblication</doclink>
+* <doclink href=Copying>Copie</doclink>
+* <doclink href=UpgradeDoc>Mise à jour</doclink>',
+ 'config-env-good' => 'L’environnement a été vérifié.
+Vous pouvez installer MediaWiki.',
+ 'config-env-bad' => 'L’environnement a été vérifié.
+vous ne pouvez pas installer MediaWiki.',
+ 'config-env-php' => 'PHP $1 est installé.',
+ 'config-env-php-toolow' => 'PHP $1 est installé.
+Cependant, MediaWiki requiert PHP $2 ou plus haut.',
+ 'config-unicode-using-utf8' => 'Utilisation de utf8_normalize.so par Brion Vibber pour la normalisation Unicode.',
+ 'config-unicode-using-intl' => "Utilisation de [http://pecl.php.net/intl l'extension PECL intl] pour la normalisation Unicode.",
+ 'config-unicode-pure-php-warning' => "'''Attention''': L'[http://pecl.php.net/intl extension PECL intl] n'est pas disponible pour la normalisation d’Unicode, retour à la version lente implémentée en PHP.
+Si vous utilisez un site web très fréquenté, vous devriez lire ceci : [http://www.mediawiki.org/wiki/Unicode_normalization_considerations ''Unicode normalization''] (en anglais).",
+ 'config-unicode-update-warning' => "'''Attention''': La version installée du ''wrapper'' de normalisation Unicode utilise une vieille version de la [http://site.icu-project.org/ bibliothèque logicielle ''ICU Project''].
+Vous devriez faire une [http://www.mediawiki.org/wiki/Unicode_normalization_considerations mise à jour] (texte en anglais) si l'usage d'Unicode vous semble important.",
+ 'config-no-db' => 'Impossible de trouver un pilote de base de données approprié !',
+ 'config-no-db-help' => "Vous avez besoin d'installer un pilote de base de données pour PHP.
+Les types de base de données suivants sont supportés: $1.
+
+Si vous êtes en hébergement mutualisé, demandez à votre fournisseur d'hébergement pour installer un pilote de base de données appropriée.
+Si vous avez compilé PHP vous-même, reconfigurez-le en activant un client de base de données, par exemple en utilisant <code>./configure --with-mysql</code>.
+Si vous avez installé PHP à partir d'un paquet Debian ou Ubuntu, vous devez également installer le module php5-mysql.",
+ 'config-no-fts3' => "'''Attention :''' SQLite est compilé sans le module [http://sqlite.org/fts3.html FTS3] ; les fonctions de recherche ne seront pas disponibles sur ce moteur.",
+ 'config-register-globals' => "'''Attention : l'option <code>[http://php.net/register_globals register_globals]</code> de PHP est activée.'''
+'''Désactivez-la si vous le pouvez.'''
+MediaWiki fonctionnera, mais votre serveur sera exposé à de potentielles failles de sécurité.",
+ 'config-magic-quotes-runtime' => "'''Erreur fatale : [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] est activé !'''
+Cette option corrompt les données de manière imprévisible.
+Vous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
+ 'config-magic-quotes-sybase' => "'''Erreur fatale : [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybasee] est activé !'''
+Cette option corrompt les données de manière imprévisible.
+Vous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
+ 'config-mbstring' => "'''Erreur fatale : [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] est activé !'''
+Cette option provoque des erreurs et peut corrompre les données de manière imprévisible.
+Vous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
+ 'config-ze1' => "'''Erreur fatale : [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mod] est activé !'''
+Cette option provoque des bugs horribles avec MediaWiki.
+Vous ne pouvez pas installer ou utiliser MediaWiki tant que cette option est activée.",
+ 'config-safe-mode' => "'''Attention : le « [http://www.php.net/features.safe-mode safe mode] » est activé !'''
+Ceci peut causer des problèmes, en particulier si vous utilisez le téléversement de fichiers et le support de <code>math</code>.",
+ 'config-xml-bad' => 'Le module XML de PHP est manquant.
+MediaWiki requiert des fonctions de ce module et ne fonctionnera pas avec cette configuration.
+Si vous êtes sous Mandrake, installez le paquet php-xml.',
+ 'config-pcre' => "Le module de support PCRE semble manquer.
+MediaWiki requiert les fonctions d'expression régulière compatible avec Perl.",
+ 'config-pcre-no-utf8' => "'''Erreur fatale''': Le module PCRE de PHP semble être compilé sans le support PCRE_UTF8.
+MédiaWiki nécessite la gestion d’UTF-8 pour fonctionner correctement.",
+ 'config-memory-raised' => 'Le paramètre <code>memory_limit</code> de PHP était à $1, porté à $2.',
+ 'config-memory-bad' => "'''Attention :''' Le paramètre <code>memory_limit</code> de PHP est à $1.
+Cette valeur est probablement trop faible.
+Il est possible que l’installation échoue !",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] est installé',
+ 'config-apc' => '[http://www.php.net/apc APC] est installé',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] est installé',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] est installé',
+ 'config-no-cache' => "'''Attention :''' Impossible de trouver [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
+La mise en cache d'objets n'est pas activée.",
+ 'config-diff3-bad' => 'GNU diff3 introuvable.',
+ 'config-imagemagick' => "ImageMagick trouvé : <code>$1</code>.
+La miniaturisation d'images sera activée si vous activez le téléversement de fichiers.",
+ 'config-gd' => "La bibliothèque graphique GD intégrée a été trouvée.
+La miniaturisation d'images sera activée si vous activez le téléversement de fichiers.",
+ 'config-no-scaling' => "Impossible de trouver la bibliothèque GD ou ImageMagick.
+La miniaturisation d'images sera désactivé.",
+ 'config-no-uri' => "'''Erreur :''' Impossible de déterminer l'URI du script actuel.
+Installation avortée.",
+ 'config-uploads-not-safe' => "'''Attention:''' Votre répertoire par défaut pour les téléchargements, <code>$1</code>, est vulnérable, car il peut exécuter n'importe quel script.
+Bien que MediaWiki vérifie tous les fichiers téléchargés, il est fortement recommandé de [http://www.mediawiki.org/wiki/Manual:Security#Upload_security fermer cette vulnérabilité de sécurité] (texte en anglais) avant d'activer les téléchargements.",
+ 'config-brokenlibxml' => 'Votre système utilise une combinaison de versions de PHP et libxml2 qui est boguée et peut engendrer des corruptions cachées de données dans MediaWiki et d’autres applications web.
+Veuillez mettre à jour votre système vers PHP 5.2.9 ou plus récent et libxml2 2.7.3 ou plus récent ([http://bugs.php.net/bug.php?id=45996 bogue déposé auprès de PHP]).
+Installation interrompue.',
+ 'config-using531' => 'MediaWiki ne peut pas être utilisé avec PHP $1 à cause d’un bogue affectant les paramètres passés par référence à <code>__call()</code>.
+Veuillez mettre à jour votre système vers PHP 5.3.2 ou plus récent ou revenir à PHP 5.3.0 pour résoudre ce problème.
+Installation interrompue.',
+ 'config-db-type' => 'Type de base de données :',
+ 'config-db-host' => 'Nom d’hôte de la base de données :',
+ 'config-db-host-help' => "Si votre serveur de base de données est sur un serveur différent, saisissez ici son nom d’hôte ou son adresse IP.
+
+Si vous utilisez un hébergement mutualisé, votre hébergeur doit vous avoir fourni le nom d’hôte correct dans sa documentation.
+
+Si vous installez sur un serveur Windows et utilisez MySQL, « localhost » peut ne pas fonctionner comme nom de serveur. S'il ne fonctionne pas, essayez « 127.0.0.1 » comme adresse IP locale.",
+ 'config-db-host-oracle' => 'Nom TNS de la base de données :',
+ 'config-db-host-oracle-help' => 'Entrez un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nom de connexion locale] valide ; un fichier tnsnames.ora doit être visible par cette installation.<br /> Si vous utilisez les bibliothèques clientes version 10g ou plus récentes, vous pouvez également utiliser la méthode de nommage [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Identifier ce wiki',
+ 'config-db-name' => 'Nom de la base de données :',
+ 'config-db-name-help' => "Choisissez un nom qui identifie votre wiki.
+Il ne doit pas contenir d'espaces.
+
+Si vous utilisez un hébergement web partagé, votre hébergeur vous fournira un nom spécifique de base de données à utiliser, ou bien vous permet de créer des bases de données via un panneau de contrôle.",
+ 'config-db-name-oracle' => 'Schéma de base de données :',
+ 'config-db-account-oracle-warn' => "Il existe trois scénarios pris en charge pour l’installation d'Oracle comme backend de base :
+
+Si vous souhaitez créer un compte de base de données dans le cadre de la procédure d’installation, veuillez fournir un compte avec le rôle de SYSDBA comme compte de base de données pour l’installation et spécifiez les informations d’identification souhaitées pour le compte d'accès au web, sinon vous pouvez créer le compte d’accès web manuellement et fournir uniquement ce compte (si elle a exigé des autorisations nécessaires pour créer les objets de schéma) ou fournir deux comptes différents, l’un avec les privilèges de créer et une restreinte pour l’accès web.
+
+Un script pour créer un compte avec des privilèges requis peut être trouvé dans le répertoire « entretien/oracle/ » de cette installation. N’oubliez pas que le fait de l’utilisation d’un compte limité désactive toutes les fonctionnalités d’entretien avec le compte par défaut.",
+ 'config-db-install-account' => "Compte d'utilisateur pour l'installation",
+ 'config-db-username' => 'Nom d’utilisateur de la base de données :',
+ 'config-db-password' => 'Mot de passe de la base de données :',
+ 'config-db-password-empty' => "Veuillez entrer un mot de passe pour le nouvel compte de la base de données : $1.
+Bien qu'il soit possible de créer un compte sans mot de passe, ce n'est pas recommandé pour des questions de sécurité.",
+ 'config-db-install-username' => "Entrez le nom d’utilisateur qui sera utilisé pour se connecter à la base de données pendant le processus d'installation. Il ne s’agit pas du nom d’utilisateur du compte MediaWiki, mais du nom d’utilisateur pour votre base de données.",
+ 'config-db-install-password' => "Entrez le mot de passe qui sera utilisé pour se connecter à la base de données pendant le processus d'installation. Il ne s’agit pas du mot de passe du compte MediaWiki, mais du mot de passe pour votre base de données.",
+ 'config-db-install-help' => "Entrez le nom d'utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le processus d'installation.",
+ 'config-db-account-lock' => "Utiliser le même nom d'utilisateur et le même mot de passe pendant le fonctionnement habituel",
+ 'config-db-wiki-account' => "Compte d'utilisateur pour le fonctionnement habituel",
+ 'config-db-wiki-help' => "Entrez le nom d'utilisateur et le mot de passe qui seront utilisés pour se connecter à la base de données pendant le fonctionnement habituel du wiki.
+Si le compte n'existe pas, et le compte d'installation dispose de privilèges suffisants, ce compte d'utilisateur sera créé avec les privilèges minimum requis pour faire fonctionner le wiki.",
+ 'config-db-prefix' => 'Préfixe des tables de la base de données :',
+ 'config-db-prefix-help' => "Si vous avez besoin de partager une base de données entre plusieurs wikis, ou entre MediaWiki et une autre application Web, vous pouvez choisir d'ajouter un préfixe à tous les noms de table pour éviter les conflits.
+Ne pas utiliser des espaces.
+
+Ce champ est généralement laissé vide.",
+ 'config-db-charset' => 'Jeu de caractères de la base de données',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binaire',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 rétrocompatible UTF-8',
+ 'config-charset-help' => "'''Attention:''' Si vous utilisez ''backwards-compatible UTF-8'' sur MySQL 4.1+, et ensuite sauvegardez la base de données avec <code>mysqldump</code>, cela peut détruire tous les caractères non-ASCII, ce qui rend inutilisable vos copies de sauvegarde de façon irréversible !
+
+En ''mode binaire'', MediaWiki stocke le texte UTF-8 dans des champs binaires de la base de données. C'est plus efficace que le ''mode UTF-8'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.
+En ''mode UTF-8'', MySQL connaîtra le jeu de caractères de vos données et pourra présenter et convertir les données de manière appropriée, mais il ne vous laissera pas stocker les caractères au-dessus du [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingue de base] (en anglais).",
+ 'config-mysql-old' => 'MySQL $1 ou version ultérieure est requis, vous avez $2.',
+ 'config-db-port' => 'Port de la base de données :',
+ 'config-db-schema' => 'Schéma pour MediaWiki',
+ 'config-db-schema-help' => "Les schémas ci-dessus sont généralement corrects.
+Ne les changez que si vous êtes sûr que c'est nécessaire.",
+ 'config-sqlite-dir' => 'Dossier des données SQLite :',
+ 'config-sqlite-dir-help' => "SQLite stocke toutes les données dans un fichier unique.
+
+Le répertoire que vous inscrivez doit être accessible en écriture par le serveur lors de l'installation.
+
+Il '''ne faut pas''' qu'il soit accessible via le web, c'est pourquoi il n'est pas à l'endroit où vos fichiers PHP sont.
+
+L'installateur écrira un fichier <code>.htaccess</code> en même temps, mais s'il y a échec, quelqu'un peut accéder à votre base de données.
+Cela comprend les données des utilisateurs (adresses de courriel, mots de passe hachés) ainsi que des révisions supprimées et d'autres données confidentielles du wiki.
+
+Envisagez de placer la base de données ailleurs, par exemple dans <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => "Espace de stockage (''tablespace'') par défaut :",
+ 'config-oracle-temp-ts' => "Espace de stockage (''tablespace'') temporaire :",
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => "MediaWiki supporte ces systèmes de bases de données :
+
+$1
+
+Si vous ne voyez pas le système de base de données que vous essayez d'utiliser ci-dessous, alors suivez les instructions ci-dessus (voir liens) pour activer le support.",
+ 'config-support-mysql' => '* $1 est le premier choix pour MediaWiki et est mieux pris en charge ([http://www.php.net/manual/en/mysql.installation.php how to compile PHP with MySQL support])',
+ 'config-support-postgres' => "* $1 est un système de base de données populaire et ''open source'' qui peut être une alternative à MySQL ([http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support])",
+ 'config-support-sqlite' => '* $1 est un système de base de données léger qui est bien supporté. ([http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], utilise PDO)',
+ 'config-support-oracle' => '* $1 est un système commercial de gestion de base de données d’entreprise. ([Http://www.php.net/manual/en/oci8.installation.php Comment compiler PHP avec le support OCI8])',
+ 'config-header-mysql' => 'Paramètres de MySQL',
+ 'config-header-postgres' => 'Paramètres de PostgreSQL',
+ 'config-header-sqlite' => 'Paramètres de SQLite',
+ 'config-header-oracle' => 'Paramètres d’Oracle',
+ 'config-invalid-db-type' => 'Type de base de données non valide',
+ 'config-missing-db-name' => 'Vous devez saisir une valeur pour « Nom de la base de données »',
+ 'config-missing-db-host' => "Vous devez entrer une valeur pour « l'hôte de la base de données »",
+ 'config-missing-db-server-oracle' => 'Vous devez saisir une valeur pour le « Nom TNS de la base de données »',
+ 'config-invalid-db-server-oracle' => 'Le nom TNS de la base de données (« $1 ») est invalide.
+Il ne peut contenir que des lettres latines de base (a-z, A-Z), des chiffres (0-9), des caractères de soulignement (_) et des points (.).',
+ 'config-invalid-db-name' => 'Nom de la base de données invalide (« $1 »).
+Il ne peut contenir que des lettres latines (a-z, A-Z), des chiffres (0-9), des caractères de soulignement (_) et des tirets (-).',
+ 'config-invalid-db-prefix' => 'Préfixe de la base de données non valide « $1 ».
+Il ne peut contenir que des lettres latines (a-z, A-Z), des chiffres (0-9), des caractères de soulignement (_) et des tirets (-).',
+ 'config-connection-error' => '$1.
+
+Vérifier le nom d’hôte, le nom d’utilisateur et le mot de passe ci-dessous puis réessayer.',
+ 'config-invalid-schema' => 'Schéma invalide pour MediaWiki « $1 ».
+Utilisez seulement des lettres latines (a-z, A-Z), des chiffres (0-9) et des caractères de soulignement (_).',
+ 'config-db-sys-create-oracle' => "L'installateur ne reconnaît que les compte SYSDBA lors de la création d'un nouveau compte.",
+ 'config-db-sys-user-exists-oracle' => 'Le compte « $1 » existe déjà. Un SYSDBA peut seulement servir à créer un nouveau comtpe.',
+ 'config-postgres-old' => 'PostgreSQL $1 ou version ultérieure est requis, vous avez $2.',
+ 'config-sqlite-name-help' => "Choisir un nom qui identifie votre wiki.
+Ne pas utiliser des espaces ou des traits d'union.
+Il sera utilisé pour le fichier de données SQLite.",
+ 'config-sqlite-parent-unwritable-group' => "Impossible de créer le répertoire de données <nowiki><code>$1</code></nowiki>, parce que le répertoire parent <nowiki><code>$2</code></nowiki> n'est pas accessible en écriture par le serveur Web.
+
+L'utilisateur du serveur web est connu.
+Rendre le répertoire <nowiki><code>$3</code></nowiki> accessible en écriture pour continuer.
+Sur un système UNIX/Linux, saisir :
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>",
+ 'config-sqlite-parent-unwritable-nogroup' => "Impossible de créer le répertoire de données <nowiki><code>$1</code></nowiki>, parce que le répertoire parent <nowiki><code>$2</code></nowiki> n'est pas accessible en écriture par le serveur Web.
+
+L'utilisateur du serveur web est inconnu.
+Rendre le répertoire <nowiki><code>$3</code></nowiki> globalement accessible en écriture pour continuer.
+Sur un système UNIX/Linux, saisir :
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>",
+ 'config-sqlite-mkdir-error' => "Erreur de création du répertoire de données « $1 ».
+Vérifiez l'emplacement et essayez à nouveau.",
+ 'config-sqlite-dir-unwritable' => "Impossible d'écrire dans le répertoire « $1 ».
+Changer les permissions de sorte que le serveur peut y écrire et essayez à nouveau.",
+ 'config-sqlite-connection-error' => '$1.
+
+Vérifier le répertoire des données et le nom de la base de données ci-dessous et réessayer.',
+ 'config-sqlite-readonly' => "Le fichier <code>$1</code> n'est pas accessible en écriture.",
+ 'config-sqlite-cant-create-db' => 'Impossible de créer le fichier de base de données <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'PHP ne vient pas avec FTS3, les tables sont diminuées.',
+ 'config-can-upgrade' => "Il y a des tables MediaWiki dans cette base de données.
+Pour les mettre au niveau de MediaWiki $1, cliquez sur '''Continuer'''.",
+ 'config-upgrade-done' => "Mise à jour complétée.
+
+Vous pouvez maintenant [$1 commencer à utiliser votre wiki].
+
+Si vous souhaitez régénérer votre fichier <code>LocalSettings.php</code>, cliquez sur le bouton ci-dessous.
+Ce '''n'est pas recommandé''' sauf si vous rencontrez des problèmes avec votre wiki.",
+ 'config-upgrade-done-no-regenerate' => 'Mise à jour terminée.
+
+Vous pouvez maintenant [$1 commencer à utiliser votre wiki].',
+ 'config-regenerate' => 'Regénérer LocalSettings.php →',
+ 'config-show-table-status' => 'Échec de la requête SHOW TABLE STATUS !',
+ 'config-unknown-collation' => "'''Attention:''' La base de données effectue un classement alphabétique (''collation'') inconnu.",
+ 'config-db-web-account' => "Compte de la base de données pour l'accès Web",
+ 'config-db-web-help' => "Sélectionnez le nom d'utilisateur et le mot de passe que le serveur web utilisera pour se connecter au serveur de base de données pendant le fonctionnement habituel du wiki.",
+ 'config-db-web-account-same' => "Utilisez le même compte que pour l'installation",
+ 'config-db-web-create' => "Créez le compte s'il n'existe pas déjà",
+ 'config-db-web-no-create-privs' => "Le compte que vous avez spécifié pour l'installation n'a pas de privilèges suffisants pour créer un compte.
+Le compte que vous spécifiez ici doit déjà exister.",
+ 'config-mysql-engine' => 'Moteur de stockage :',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' est presque toujours la meilleure option, car il supporte bien l'[http://fr.wikipedia.org/wiki/Ordonnancement_dans_les_syst%C3%A8mes_d%27exploitation ordonnancement].
+
+'''MyISAM''' peut être plus rapide dans les installations monoposte ou en lecture seule. Les bases de données MyISAM ont tendance à se corrompre plus souvent que celles d'InnoDB.",
+ 'config-mysql-charset' => 'Jeu de caractères de la base de données :',
+ 'config-mysql-binary' => 'Binaire',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "En ''mode binaire'', MediaWiki stocke le texte au format UTF-8 dans la base de données. C'est plus efficace que le ''UTF-8 mode'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.
+
+En ''mode binaire'', MediaWiki stocke le texte UTF-8 dans des champs binaires de la base de données. C'est plus efficace que le ''mode UTF-8'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.
+En ''mode UTF-8'', MySQL connaîtra le jeu de caractères de vos données et pourra présenter et convertir les données de manière appropriée, mais il ne vous laissera pas stocker les caractères au-dessus du [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingue de base] (en anglais).",
+ 'config-site-name' => 'Nom du wiki :',
+ 'config-site-name-help' => 'Il apparaîtra dans la barre de titre du navigateur et en divers autres endroits.',
+ 'config-site-name-blank' => 'Entrez un nom de site.',
+ 'config-project-namespace' => 'Espace de noms du projet :',
+ 'config-ns-generic' => 'Projet',
+ 'config-ns-site-name' => 'Même nom que le wiki : $1',
+ 'config-ns-other' => 'Autre (préciser)',
+ 'config-ns-other-default' => 'MonWiki',
+ 'config-project-namespace-help' => "Suivant l'exemple de Wikipédia, plusieurs wikis gardent leurs pages de politique séparées de leurs pages de contenu, dans un ''espace de noms'' propre.
+Tous les titres de page de cet espace de noms commence par un préfixe défini, que vous pouvez spécifier ici.
+Traditionnellement, ce préfixe est dérivé du nom du wiki, mais il ne peut contenir des caractères de ponctuation tels que « # » ou « : ».",
+ 'config-ns-invalid' => "L'espace de noms spécifié « <nowiki>$1</nowiki> » n'est pas valide.
+Spécifiez un espace de noms pour le projet.",
+ 'config-ns-conflict' => "L'espace de noms spécifié « <nowiki>$1</nowiki> » est en conflit avec un espace de noms par défaut de MediaWiki.
+Choisir un autre espace de noms.",
+ 'config-admin-box' => 'Compte administrateur',
+ 'config-admin-name' => 'Votre nom :',
+ 'config-admin-password' => 'Mot de passe :',
+ 'config-admin-password-confirm' => 'Saisir à nouveau le mot de passe :',
+ 'config-admin-help' => "Entrez votre nom d'utilisateur préféré ici, par exemple « Jean Blogue ».
+C'est le nom que vous utiliserez pour vous connecter au wiki.",
+ 'config-admin-name-blank' => "Entrez un nom d'administrateur.",
+ 'config-admin-name-invalid' => "Le nom d'utilisateur spécifié « <nowiki>$1</nowiki> » n'est pas valide.
+Indiquez un nom d'utilisateur différent.",
+ 'config-admin-password-blank' => 'Entrez un mot de passe pour le compte administrateur.',
+ 'config-admin-password-same' => "Le mot de passe doit être différent du nom d'utilisateur.",
+ 'config-admin-password-mismatch' => 'Les deux mots de passe que vous avez saisis ne correspondent pas.',
+ 'config-admin-email' => 'Adresse de courriel :',
+ 'config-admin-email-help' => "Entrez une adresse de courriel ici pour vous permettre de recevoir des courriels d'autres utilisateurs du wiki, réinitialiser votre mot de passe, et être informé des modifications apportées aux pages de votre liste de suivi. Vous pouvez laisser ce champ vide.",
+ 'config-admin-error-user' => "Erreur interne lors de la création d'un administrateur avec le nom « <nowiki>$1</nowiki> ».",
+ 'config-admin-error-password' => "Erreur interne lors de l'inscription d'un mot de passe pour l'administrateur « <nowiki>$1</nowiki> » : <pre>$2</pre>",
+ 'config-admin-error-bademail' => 'Vous avez entré une adresse de courriel invalide',
+ 'config-subscribe' => "Abonnez-vous à la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce liste d'annonce des nouvelles versions] (la page peut afficher le texte en français).",
+ 'config-subscribe-help' => "Il s'agit d'une liste de diffusion à faible volume utilisée servant à annoncer les nouvelles versions, y compris les versions améliorant la sécurité du logiciel.
+Vous devriez y souscrire et mettre à jour votre version de MediaWiki lorsque de nouvelles versions sont publiées.",
+ 'config-almost-done' => 'Vous avez presque fini !
+Vous pouvez passer la configuration restante et installer immédiatement le wiki.',
+ 'config-optional-continue' => 'Me poser davantage de questions.',
+ 'config-optional-skip' => 'J’en ai assez, installer simplement le wiki.',
+ 'config-profile' => 'Profil des droits d’utilisateurs :',
+ 'config-profile-wiki' => 'Wiki traditionnel',
+ 'config-profile-no-anon' => 'Création de comte requise',
+ 'config-profile-fishbowl' => 'Éditeurs autorisés seulement',
+ 'config-profile-private' => 'Wiki privé',
+ 'config-profile-help' => "Les wikis fonctionnent mieux lorsque vous laissez le plus de personnes possible le modifier.
+Avec MediaWiki, il est facile de vérifier les modifications récentes et de révoquer tout dommage créé par des utilisateurs débutants ou mal intentionnés.
+
+Cependant, de nombreuses autres utilisations ont été trouvées au logiciel et il n’est pas toujours facile de convaincre tout le monde des bénéfices de l’esprit wiki.
+Vous avez donc le choix.
+
+'''{{int:config-profile-wiki}}''' autorise quiconque à modifier, y compris sans s’identifier.
+'''{{int:config-profile-no-anon}}''' fournit plus de contrôle, par l’identification, mais peut rebuter les contributeurs occasionnels.
+
+'''{{int:config-profile-fishbowl}}''' autorise la modification par les utilisateurs approuvés, mais le public peut toujours lire les pages et leur historique.
+'''{{int:config-profile-private}}''' n’autorise que les utilisateurs approuvés à voir et modifier les pages.
+
+Des configurations de droits d’utilisateurs plus complexes sont disponibles après l'installation, voir la [http://www.mediawiki.org/wiki/Manual:User_rights page correspondante du manuel].",
+ 'config-license' => "Droits d'auteur et licence :",
+ 'config-license-none' => 'Aucune licence en bas de page',
+ 'config-license-cc-by-sa' => "Creative Commons attribution partage à l'identique",
+ 'config-license-cc-by-nc-sa' => "Creative Commons attribution non commercial partage à l'identique",
+ 'config-license-cc-0' => 'Creative Commons Zero',
+ 'config-license-gfdl-old' => 'Licence de documentation libre GNU 1.2',
+ 'config-license-gfdl-current' => 'Licence de documentation libre GNU 1.3 ou plus récent',
+ 'config-license-pd' => 'Domaine public',
+ 'config-license-cc-choose' => 'Sélectionner une licence Creative Commons personnalisée',
+ 'config-license-help' => "Beaucoup de wikis publics mettent l'ensemble des contributions sous [http://freedomdefined.org/Definition/Fr licence libre].
+Cela contribue à créer un sentiment d'appartenance dans leur communauté et encourage les contributions sur le long terme.
+Ce n'est généralement pas nécessaire pour un wiki privé ou d'entreprise.
+
+Si vous souhaitez utiliser des textes de Wikipédia, et souhaitez que Wikipédia réutilise des textes de votre wiki, vous devriez choisir la [http://creativecommons.org/licenses/by-sa/3.0/deed.fr licence ''Creative Commons Attribution Share Alike''] (CC-by-sa).
+
+Wikipédia a déjà été publié selon les termes de la [http://fr.wikipedia.org/wiki/Licence_de_documentation_libre_GNU ''GNU Free Documentation License''] (GFDL).
+C'est encore une licence valide, mais elle possède des caractéristiques qui rendent difficiles la réutilisation et l'interprétation des textes.",
+ 'config-email-settings' => 'Paramètres de courriel',
+ 'config-enable-email' => 'Activer les courriels sortants',
+ 'config-enable-email-help' => 'Si vous souhaitez utiliser le courriel, vous devez [http://www.php.net/manual/en/mail.configuration.php configurer des paramètres PHP] (texte en anglais).
+Si vous ne voulez pas du service de courriel, vous pouvez le désactiver ici.',
+ 'config-email-user' => 'Activer les courriels de utilisateur à utilisateur',
+ 'config-email-user-help' => "Permet à tous les utilisateurs d'envoyer des courriels à d'autres utilisateurs si cela est activé dans leurs préférences.",
+ 'config-email-usertalk' => 'Activer la notification des pages de discussion des utilisateurs',
+ 'config-email-usertalk-help' => 'Permet aux utilisateurs de recevoir une notification en cas de modification de leurs pages de discussion, si cela est activé dans leurs préférences.',
+ 'config-email-watchlist' => 'Activer la notification de la liste de suivi',
+ 'config-email-watchlist-help' => "Permet aux utilisateurs de recevoir des notifications à propos des pages qu'ils ont en suivi (si cette préférence est activée).",
+ 'config-email-auth' => "Activer l'authentification par courriel",
+ 'config-email-auth-help' => "Si cette option est activée, les utilisateurs doivent confirmer leur adresse de courriel en utilisant l'hyperlien envoyé à chaque fois qu'ils la définissent ou la modifient.
+Seules les adresses authentifiées peuvent recevoir des courriels des autres utilisateurs ou lorsqu'il y a des notifications de modification.
+L'activation de cette option est '''recommandée''' pour les wikis publics en raison d'abus potentiels des fonctionnalités de courriels.",
+ 'config-email-sender' => 'Adresse de courriel de retour :',
+ 'config-email-sender-help' => "Entrez l'adresse de courriel à utiliser comme adresse de retour des courriels sortant.
+Les courriels rejetés y seront envoyés.
+De nombreux serveurs de courriels exigent au moins un [http://fr.wikipedia.org/wiki/Nom_de_domaine nom de domaine] valide.",
+ 'config-upload-settings' => 'Téléchargement des images et des fichiers',
+ 'config-upload-enable' => 'Activer le téléchargement des fichiers',
+ 'config-upload-help' => "Le téléchargement des fichiers expose votre serveur à des risques de sécurité.
+Pour plus d'informations, lire la section [http://www.mediawiki.org/wiki/Manual:Security ''Security''] du manuel d'installation (en anglais).
+
+Pour autoriser le téléchargement des fichiers, modifier le mode du sous-répertoire <code>images</code> qui se situe sous le répertoire racine de MediaWiki.
+Ensuite, activez cette option.",
+ 'config-upload-deleted' => 'Répertoire pour les fichiers supprimés :',
+ 'config-upload-deleted-help' => 'Choisissez un répertoire qui servira à archiver les fichiers supprimés.
+Idéalement, il ne devrait pas être accessible depuis le web.',
+ 'config-logo' => 'URL du logo :',
+ 'config-logo-help' => "L'habillage (''skin'') par défaut de MediaWiki comprend l'espace pour un logo de 135x160 pixels dans le coin supérieur gauche.
+Téléchargez une image de la taille appropriée, et entrez l'URL ici.
+
+Si vous ne voulez pas d'un logo, laissez cette case vide.",
+ 'config-instantcommons' => "Activer ''InstantCommons''",
+ 'config-instantcommons-help' => "[http://www.mediawiki.org/wiki/InstantCommons InstantCommons] est un service qui permet d'utiliser les images, les sons et les autres médias disponibles sur le site [http://commons.wikimedia.org/ Wikimedia Commons].
+Pour se faire, il faut que MediaWiki accède à Internet.
+
+Pour plus d'informations sur ce service, y compris les instructions sur la façon de le configurer pour d'autres wikis que Wikimedia Commons, consultez le [http://mediawiki.org/wiki/Manual:\$wgForeignFileRepos manuel] (en anglais).",
+ 'config-cc-error' => "Le sélection d'une licence ''Creative Commons'' n'a donné aucun résultat.
+Entrez le nom de la licence manuellement.",
+ 'config-cc-again' => 'Choisissez à nouveau...',
+ 'config-cc-not-chosen' => "Choisissez une licence ''Creative Commons'' et cliquez sur « Continuer ».",
+ 'config-advanced-settings' => 'Configuration avancée',
+ 'config-cache-options' => 'Paramètres pour la mise en cache des objets:',
+ 'config-cache-help' => "La mise en cache des objets améliore la vitesse de MediaWiki en mettant en cache les données fréquemment utilisées.
+Les sites de taille moyenne à grande sont fortement encouragés à l'activer. Les petits sites y verront également des avantages.",
+ 'config-cache-none' => 'Aucune mise en cache (aucune fonctionnalité supprimée, mais la vitesse peut changer sur les wikis importants)',
+ 'config-cache-accel' => 'Mise en cache des objets PHP (APC, eAccelerator, XCache ou WinCache)',
+ 'config-cache-memcached' => 'Utiliser Memcached (nécessite une installation et une configuration supplémentaires)',
+ 'config-memcached-servers' => 'serveurs pour Memcached :',
+ 'config-memcached-help' => 'Liste des adresses IP à utiliser pour Memcached.
+Elles doivent être séparés par des virgules et vous devez spécifier le port à utiliser. Par exemple :
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Vous avez sélectionné Memcached comme type de cache, mais ne précisez pas de serveur.',
+ 'config-memcache-badip' => 'Vous avez entré une adresse IP invalide pour Memcached: $1.',
+ 'config-memcache-noport' => "Vous n'avez pas entré un port pour le serveur Memcached : $1.
+Si vous ne le connaissez pas, la valeur par défaut est 11211.",
+ 'config-memcache-badport' => 'Les numéros de port de Memcached sont situés entre $1 et $2.',
+ 'config-extensions' => 'Extensions',
+ 'config-extensions-help' => 'Les extensions énumérées ci-dessus ont été détectées dans votre répertoire <code>./extensions</code>.
+
+Elles peuvent nécessiter une configuration supplémentaire, mais vous pouvez les activer maintenant',
+ 'config-install-alreadydone' => "'''Attention''': Vous semblez avoir déjà installé MediaWiki et tentez de l'installer à nouveau.
+S'il vous plaît, allez à la page suivante.",
+ 'config-install-begin' => "En appuyant sur {{int:config-continue}}, vous commencerez l'installation de MediaWiki.
+Si vous voulez apporter des modifications, appuyez sur Retour.",
+ 'config-install-step-done' => 'fait',
+ 'config-install-step-failed' => 'échec',
+ 'config-install-extensions' => 'Inclusion des extensions',
+ 'config-install-database' => 'Création de la base de données',
+ 'config-install-pg-schema-not-exist' => "Le schéma PostgreSQL n'existe pas",
+ 'config-install-pg-schema-failed' => "Échec lors de la création des tables.
+Assurez-vous que l'utilisateur « $1 » peut écrire selon le schéma « $2 ».",
+ 'config-install-pg-commit' => 'Validation des modifications',
+ 'config-install-pg-plpgsql' => 'Vérification du language PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Vous devez installer le langage PL/pgSQL dans la base de données $1',
+ 'config-pg-no-create-privs' => "Le compte que vous avez spécifié pour l'installation n'a pas suffisamment de privilèges pour créer un compte.",
+ 'config-install-user' => "Création d'un utilisateur de la base de données",
+ 'config-install-user-alreadyexists' => "L'utilisateur « $1 » existe déjà.",
+ 'config-install-user-create-failed' => "Échec lors de la création de l'utilisateur « $1 » : $2",
+ 'config-install-user-grant-failed' => "Échec lors de l'ajout de permissions à l'utilisateur « $1 » : $2",
+ 'config-install-tables' => 'Création des tables',
+ 'config-install-tables-exist' => "'''Avertissement:''' Les tables MediaWiki semblent déjà exister.
+Création omise.",
+ 'config-install-tables-failed' => "'''Erreur:''' échec lors de la création de la table avec l'erreur suivante: $1",
+ 'config-install-interwiki' => 'Remplissage par défaut de la table des interwikis',
+ 'config-install-interwiki-list' => 'Impossible de trouver le fichier <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Attention:''' La table des interwikis semble déjà contenir des entrées.
+La liste par défaut ne sera pas inscrite.",
+ 'config-install-stats' => 'Initialisation des statistiques',
+ 'config-install-keys' => 'Génération de la clé secrète',
+ 'config-install-sysop' => 'Création du compte administrateur',
+ 'config-install-subscribe-fail' => "Impossible de s'abonner à mediawiki-announce",
+ 'config-install-mainpage' => 'Création de la page principale avec un contenu par défaut',
+ 'config-install-extension-tables' => 'Création de tables pour les extensions activées',
+ 'config-install-mainpage-failed' => 'Impossible d’insérer la page principale: $1',
+ 'config-install-done' => "'''Félicitations!'''
+Vous avez réussi à installer MediaWiki.
+
+Le programme d'installation a généré <code>LocalSettings.php</code>, un fichier qui contient tous les paramètres de configuration.
+
+Si le téléchargement n'a pas été offert, ou que vous l'avez annulé, vous pouvez démarrer à nouveau le téléchargement en cliquant ce lien :
+
+$3
+
+'''Note''': Si vous ne le faites pas maintenant, ce fichier de configuration généré ne sera pas disponible plus tard si vous quittez l'installation sans le télécharger.
+
+Lorsque c'est fait, vous pouvez '''[$2 accéder à votre wiki]'''.",
+ 'config-download-localsettings' => 'Télécharger LocalSettings.php',
+ 'config-help' => 'aide',
+);
+
+/** Galician (Galego)
+ * @author Toliño
+ */
+$messages['gl'] = array(
+ 'config-desc' => 'O programa de instalación de MediaWiki',
+ 'config-title' => 'Instalación de MediaWiki $1',
+ 'config-information' => 'Información',
+ 'config-localsettings-upgrade' => 'Detectouse un ficheiro <code>LocalSettings.php</code>.
+Para actualizar esta instalación, introduza o valor de <code>$wgUpgradeKey</code> na caixa.
+Pode atopalo en LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Detectouse un ficheiro LocalSettings.php.
+Para actualizar esta instalación, execute update.php',
+ 'config-localsettings-key' => 'Clave de actualización:',
+ 'config-localsettings-badkey' => 'A clave dada é incorrecta',
+ 'config-upgrade-key-missing' => 'Detectouse unha instalación existente de MediaWiki.
+Para actualizar esta instalación, inclúa esta liña ao final do ficheiro LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'Semella que o ficheiro LocalSettings.php existente está incompleto.
+A variable $1 non está establecida.
+Modifique o ficheiro LocalSettings.php de xeito que a variable quede establecida e prema en "Continuar".',
+ 'config-localsettings-connection-error' => 'Atopouse un erro ao conectar coa base de datos empregando a configuración especificada no ficheiro LocalSettings.php ou no ficheiro AdminSettings.php. Corrixa esta configuración e inténteo de novo.
+
+$1',
+ 'config-session-error' => 'Erro ao iniciar a sesión: $1',
+ 'config-session-expired' => 'Semella que os seus datos da sesión caducaron.
+As sesións están configuradas para unha duración de $1.
+Pode incrementar isto fixando <code>session.gc_maxlifetime</code> en php.ini.
+Reinicie o proceso de instalación.',
+ 'config-no-session' => 'Perdéronse os datos da súa sesión!
+Comprobe o seu php.ini e asegúrese de que en <code>session.save_path</code> está definido un directorio correcto.',
+ 'config-your-language' => 'A súa lingua:',
+ 'config-your-language-help' => 'Seleccione a lingua que se empregará durante o proceso de instalación.',
+ 'config-wiki-language' => 'Lingua do wiki:',
+ 'config-wiki-language-help' => 'Seleccione a lingua que predominará no wiki.',
+ 'config-back' => '← Volver',
+ 'config-continue' => 'Continuar →',
+ 'config-page-language' => 'Lingua',
+ 'config-page-welcome' => 'Benvido a MediaWiki!',
+ 'config-page-dbconnect' => 'Conectarse á base de datos',
+ 'config-page-upgrade' => 'Actualizar a instalación actual',
+ 'config-page-dbsettings' => 'Configuración da base de datos',
+ 'config-page-name' => 'Nome',
+ 'config-page-options' => 'Opcións',
+ 'config-page-install' => 'Instalar',
+ 'config-page-complete' => 'Completo!',
+ 'config-page-restart' => 'Reiniciar a instalación',
+ 'config-page-readme' => 'Léame',
+ 'config-page-releasenotes' => 'Notas de lanzamento',
+ 'config-page-copying' => 'Copiar',
+ 'config-page-upgradedoc' => 'Actualizar',
+ 'config-page-existingwiki' => 'Wiki existente',
+ 'config-help-restart' => 'Quere eliminar todos os datos gardados e reiniciar o proceso de instalación?',
+ 'config-restart' => 'Si, reiniciala',
+ 'config-welcome' => '=== Comprobación do entorno ===
+Cómpre realizar unhas comprobacións básicas para ver se o entorno é axeitado para a instalación de MediaWiki.
+Deberá proporcionar os resultados destas comprobacións se necesita axuda durante a instalación.',
+ 'config-copyright' => "=== Dereitos de autor e termos de uso ===
+
+$1
+
+Este programa é software libre; pode redistribuílo e/ou modificalo segundo os termos da licenza pública xeral GNU publicada pola Free Software Foundation; versión 2 ou (na súa escolla) calquera outra posterior.
+
+Este programa distribúese coa esperanza de que poida ser útil, pero '''sen ningunha garantía'''; nin sequera a garantía implícita de '''comercialización''' ou '''adecuación a unha finalidade específica'''.
+Olle a licenza pública xeral GNU para obter máis detalles.
+
+Debería recibir <doclink href=Copying>unha copia da licenza pública xeral GNU</doclink> xunto ao programa; se non é así, escriba á Free Software Foundation, Inc., 51 da rúa Franklin, quinto andar, Boston, MA 02110-1301, Estados Unidos ou [http://www.gnu.org/copyleft/gpl.html lea a licenza en liña].",
+ 'config-sidebar' => '* [http://www.mediawiki.org/wiki/MediaWiki/gl Páxina principal de MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Guía de usuario]
+* [http://www.mediawiki.org/wiki/Manual:Contents Guía de administrador]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Preguntas máis frecuentes]
+----
+* <doclink href=Readme>Léame</doclink>
+* <doclink href=ReleaseNotes>Notas de lanzamento</doclink>
+* <doclink href=Copying>Copia</doclink>
+* <doclink href=UpgradeDoc>Actualización</doclink>',
+ 'config-env-good' => 'Rematou a comprobación do entorno.
+Pode instalar MediaWiki.',
+ 'config-env-bad' => 'Rematou a comprobación do entorno.
+Non pode instalar MediaWiki.',
+ 'config-env-php' => 'Está instalado o PHP $1.',
+ 'config-env-php-toolow' => 'Está instalado o PHP $1.
+Porén, MediaWiki necesita o PHP $2 ou superior.',
+ 'config-unicode-using-utf8' => 'Usando utf8_normalize.so de Brion Vibber para a normalización Unicode.',
+ 'config-unicode-using-intl' => 'Usando a [http://pecl.php.net/intl extensión intl PECL] para a normalización Unicode.',
+ 'config-unicode-pure-php-warning' => "'''Atención:''' A [http://pecl.php.net/intl extensión intl PECL] non está dispoñible para manexar a normalización Unicode; volvendo á implementación lenta de PHP puro.
+Se o seu sitio posúe un alto tráfico de visitantes, debería ler un chisco sobre a [http://www.mediawiki.org/wiki/Unicode_normalization_considerations normalización Unicode].",
+ 'config-unicode-update-warning' => "'''Atención:''' A versión instalada da envoltura de normalización Unicode emprega unha versión vella da biblioteca [http://site.icu-project.org/ do proxecto ICU].
+Debería [http://www.mediawiki.org/wiki/Unicode_normalization_considerations actualizar] se o uso de Unicode é importante para vostede.",
+ 'config-no-db' => 'Non se puido atopar un controlador axeitado para a base de datos!',
+ 'config-no-db-help' => 'Debe instalar un controlador de base de datos para PHP.
+Os tipos de base de datos soportados son os seguintes: $1.
+
+Se está nun aloxamento compartido, pregunte ao seu provedor de hospedaxe para instalar un controlador de base de datos axeitado.
+Se compilou o PHP vostede mesmo, reconfigúreo activando un cliente de base de datos, por exemplo, usando <code>./configure --with-mysql</code>.
+Se instalou o PHP desde un paquete Debian ou Ubuntu, entón tamén necesita instalar o módulo php5-mysql.',
+ 'config-no-fts3' => "'''Atención:''' O SQLite está compilado sen o [http://sqlite.org/fts3.html módulo FTS3]; as características de procura non estarán dispoñibles nesta instalación.",
+ 'config-register-globals' => "'''Atención: A opción PHP <code>[http://php.net/register_globals register_globals]</code> está activada.'''
+'''Desactívea se pode.'''
+MediaWiki funcionará, pero o seu servidor está exposto a potenciais vulnerabilidades de seguridade.",
+ 'config-magic-quotes-runtime' => "'''Erro fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está activado!'''
+Esta opción corrompe os datos de entrada de xeito imprevisible.
+Non pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
+ 'config-magic-quotes-sybase' => "'''Erro fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está activado!'''
+Esta opción corrompe os datos de entrada de xeito imprevisible.
+Non pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
+ 'config-mbstring' => "'''Erro fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está activado!'''
+Esta opción causa erros e pode corromper os datos de xeito imprevisible.
+Non pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
+ 'config-ze1' => "'''Erro fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] está activado!'''
+Esta opción causa erros horribles en MediaWiki.
+Non pode instalar ou empregar MediaWiki a menos que esta opción estea desactivada.",
+ 'config-safe-mode' => "'''Atención:''' O [http://www.php.net/features.safe-mode safe mode] do PHP está activado.
+Isto pode causar problemas, particularmente se emprega cargas de ficheiros e soporte de <code>math</code>.",
+ 'config-xml-bad' => 'Falta o módulo XML do PHP.
+MediaWiki necesita funcións neste módulo e non funcionará con esta configuración.
+Se está executando o Mandrake, instale o paquete php-xml.',
+ 'config-pcre' => 'Semella que falta o módulo de soporte PCRE.
+MediaWiki necesita que funcionen as expresións regulares compatibles co Perl.',
+ 'config-pcre-no-utf8' => "'''Erro fatal:''' Semella que o módulo PCRE do PHP foi compilado sen o soporte PCRE_UTF8.
+MediaWiki necesita soporte UTF-8 para funcionar correctamente.",
+ 'config-memory-raised' => 'O parámetro <code>memory_limit</code> do PHP é $1. Aumentado a $2.',
+ 'config-memory-bad' => "'''Atención:''' O parámetro <code>memory_limit</code> do PHP é $1.
+Probablemente é un valor baixo de máis.
+A instalación pode fallar!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] está instalado',
+ 'config-apc' => '[http://www.php.net/apc APC] está instalado',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] está instalado',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] está instalado',
+ 'config-no-cache' => "'''Atención:''' Non se puido atopar [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
+A caché de obxectos está desactivada.",
+ 'config-diff3-bad' => 'GNU diff3 non se atopou.',
+ 'config-imagemagick' => 'ImageMagick atopado: <code>$1</code>.
+As miniaturas de imaxes estarán dispoñibles se activa as cargas.',
+ 'config-gd' => 'Atopouse a biblioteca gráfica GD integrada.
+As miniaturas de imaxes estarán dispoñibles se activa as cargas.',
+ 'config-no-scaling' => 'Non se puido atopar a biblioteca GD ou ImageMagick.
+As miniaturas de imaxes estarán desactivadas.',
+ 'config-no-uri' => "'''Erro:''' Non se puido determinar o URI actual.
+Instalación abortada.",
+ 'config-uploads-not-safe' => "'''Atención:''' O seu directorio por defecto para as cargas, <code>$1</code>, é vulnerable a execucións arbitrarias de escrituras.
+Aínda que MediaWiki comproba todos os ficheiros cargados por se houbese ameazas de seguridade, é amplamente recomendable [http://www.mediawiki.org/wiki/Manual:Security#Upload_security pechar esta vulnerabilidade de seguridade] antes de activar as cargas.",
+ 'config-brokenlibxml' => 'O seu sistema ten unha combinación de versións de PHP e libxml2 que pode ser problemático e causar corrupción de datos en MediaWiki e outras aplicacións web.
+Actualice o sistema á versión 5.2.9 ou posterior do PHP e á 2.7.3 ou posterior de libxml2 ([http://bugs.php.net/bug.php?id=45996 erro presentado co PHP]).
+Instalación abortada.',
+ 'config-using531' => 'O PHP $1 non é compatible con MediaWiki debido a un erro que afecta aos parámetros de referencia de <code>__call()</code>.
+Actualice o sistema á versión 5.3.2 ou posterior do PHP ou volva á versión 5.3.0 do PHP para arranxar o problema.
+Instalación abortada.',
+ 'config-db-type' => 'Tipo de base de datos:',
+ 'config-db-host' => 'Servidor da base de datos:',
+ 'config-db-host-help' => 'Se o servidor da súa base de datos está nun servidor diferente, escriba o nome do servidor ou o enderezo IP aquí.
+
+Se está usando un aloxamento web compartido, o seu provedor de hospedaxe debe darlle o nome de servidor correcto na súa documentación.
+
+Se está a realizar a instalación nun servidor de Windows con MySQL, o nome "localhost" pode non valer como servidor. Se non funcionase, inténteo con "127.0.0.1" como enderezo IP local.',
+ 'config-db-host-oracle' => 'TNS da base de datos:',
+ 'config-db-host-oracle-help' => 'Insira un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nome de conexión local] válido; cómpre que haxa visible un ficheiro tnsnames.ora para esta instalación.<br />Se está a empregar bibliotecas cliente versión 10g ou máis recentes, tamén pode usar o método de atribución de nomes [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Identificar o wiki',
+ 'config-db-name' => 'Nome da base de datos:',
+ 'config-db-name-help' => 'Escolla un nome que identifique o seu wiki.
+Non debe conter espazos.
+
+Se está usando un aloxamento web compartido, o seu provedor de hospedaxe daralle un nome específico para a base de datos ou deixaralle crear unha a través do panel de control.',
+ 'config-db-name-oracle' => 'Esquema da base de datos:',
+ 'config-db-account-oracle-warn' => 'Existen tres escenarios soportados para a instalación de Oracle como fin da base de datos:
+
+Se quere crear unha conta para a base de datos como parte do proceso de instalación, proporcione unha conta co papel SYSDBA e especifique as credenciais desexadas para a conta; senón pode crear a conta manualmente e dar só esa conta (se ten os permisos necesarios para crear os obxectos do esquema) ou fornecer dous contas diferentes, unha con privilexios de creación e outra restrinxida para o acceso á web.
+
+A escritura para crear unha conta cos privilexios necesarios atópase no directorio "maintenance/oracle/" desta instalación. Teña en conta que o emprego de contas restrinxidas desactivará todas as operacións de mantemento da conta predeterminada.',
+ 'config-db-install-account' => 'Conta de usuario para a instalación',
+ 'config-db-username' => 'Nome de usuario da base de datos:',
+ 'config-db-password' => 'Contrasinal da base de datos:',
+ 'config-db-password-empty' => 'Introduza un contrasinal para o novo usuario da base de datos: $1.
+Malia que é posible crear usuarios sen contrasinal, esta práctica non é segura.',
+ 'config-db-install-username' => 'Escriba o nome de usuario que empregará para conectarse á base de datos durante o proceso de instalación. Este non é o nome de usuario da conta de MediaWiki, trátase do nome de usuario para a súa base de datos.',
+ 'config-db-install-password' => 'Escriba o contrasinal que empregará para conectarse á base de datos durante o proceso de instalación. Este non é o contrasinal da conta de MediaWiki, trátase do contrasinal para a súa base de datos.',
+ 'config-db-install-help' => 'Introduza o nome de usuario e contrasinal que se usará para conectar á base de datos durante o proceso de instalación.',
+ 'config-db-account-lock' => 'Use o mesmo nome de usuario e contrasinal despois do proceso de instalación',
+ 'config-db-wiki-account' => 'Conta de usuario para despois do proceso de instalación',
+ 'config-db-wiki-help' => 'Introduza o nome de usuario e mais o contrasinal que se usarán para conectar á base de datos durante o funcionamento habitual do wiki.
+Se a conta non existe e a conta de instalación ten privilexios suficientes, esa conta de usuario será creada cos privilexios mínimos necesarios para o funcionamento do wiki.',
+ 'config-db-prefix' => 'Prefixo das táboas da base de datos:',
+ 'config-db-prefix-help' => 'Se necesita compartir unha base de datos entre varios wikis ou entre MediaWiki e outra aplicación web, pode optar por engadir un prefixo a todos os nomes da táboa para evitar conflitos.
+Non utilice espazos.
+
+O normal é que este campo quede baleiro.',
+ 'config-db-charset' => 'Conxunto de caracteres da base de datos',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binario',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 retrocompatible UTF-8',
+ 'config-charset-help' => "'''Atención:''' Se emprega '''backwards-compatible UTF-8''' no MySQL 4.1+ e posteriormente realiza unha copia de seguridade da base de datos con <code>mysqldump</code>, pode destruír todos os caracteres que non sexan ASCII, corrompendo de xeito irreversible as súas copias!
+
+No '''modo binario''', MediaWiki almacena texto UTF-8 na base de datos en campos binarios.
+Isto é máis eficaz ca o modo UTF-8 de MySQL e permítelle usar o rango completo de caracteres Unicode.
+No '''modo UTF-8''', MySQL saberá o xogo de caracteres dos seus datos e pode presentar e converter os datos de maneira axeitada,
+pero non lle deixará gardar caracteres por riba do [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingüe básico].",
+ 'config-mysql-old' => 'Necesítase MySQL $1 ou posterior; ten a versión $2.',
+ 'config-db-port' => 'Porto da base de datos:',
+ 'config-db-schema' => 'Esquema para MediaWiki',
+ 'config-db-schema-help' => 'O normal é que este esquema sexa correcto.
+Cámbieo soamente se sabe que é necesario.',
+ 'config-sqlite-dir' => 'Directorio de datos SQLite:',
+ 'config-sqlite-dir-help' => "SQLite recolle todos os datos nun ficheiro único.
+
+O servidor web debe ter permisos sobre o directorio para que poida escribir nel durante a instalación.
+
+Ademais, o servidor '''non''' debe ser accesible a través da web, motivo polo que non está no mesmo lugar ca os ficheiros PHP.
+
+Asemade, o programa de instalación escribirá un ficheiro <code>.htaccess</code>, pero se erra alguén pode obter acceso á súa base de datos.
+Isto inclúe datos de usuario (enderezos de correo electrónico, contrasinais codificados), así como revisións borradas e outros datos restrinxidos no wiki.
+
+Considere poñer a base de datos nun só lugar, por exemplo en <code>/var/lib/mediawiki/oseuwiki</code>.",
+ 'config-oracle-def-ts' => 'Espazo de táboas por defecto:',
+ 'config-oracle-temp-ts' => 'Espazo de táboas temporal:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki soporta os seguintes sistemas de bases de datos:
+
+$1
+
+Se non ve listado a continuación o sistema de base de datos que intenta usar, siga as instrucións ligadas enriba para activar o soporte.',
+ 'config-support-mysql' => '* $1 é o obxectivo principal para MediaWiki e está mellor soportado ([http://www.php.net/manual/en/mysql.installation.php como compilar o PHP con soporte MySQL])',
+ 'config-support-postgres' => '* $1 é un sistema de base de datos popular e de código aberto como alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar o PHP con soporte PostgreSQL]). É posible que haxa algúns pequenos erros e non se recomenda o seu uso nun entorno de produción.',
+ 'config-support-sqlite' => '* $1 é un sistema de base de datos lixeiro moi ben soportado. ([http://www.php.net/manual/en/pdo.installation.php Como compilar o PHP con soporte SQLite], emprega PDO)',
+ 'config-support-oracle' => '* $1 é un sistema comercial de xestión de base de datos de empresa. ([http://www.php.net/manual/en/oci8.installation.php Como compilar PHP con soporte OCI8])',
+ 'config-header-mysql' => 'Configuración do MySQL',
+ 'config-header-postgres' => 'Configuración do PostgreSQL',
+ 'config-header-sqlite' => 'Configuración do SQLite',
+ 'config-header-oracle' => 'Configuración do Oracle',
+ 'config-invalid-db-type' => 'Tipo de base de datos incorrecto',
+ 'config-missing-db-name' => 'Debe escribir un valor "Nome da base de datos"',
+ 'config-missing-db-host' => 'Debe escribir un valor "Servidor da base de datos"',
+ 'config-missing-db-server-oracle' => 'Debe escribir un valor "TNS da base de datos"',
+ 'config-invalid-db-server-oracle' => 'O TNS da base de datos, "$1", é incorrecto.
+Só pode conter letras ASCII (a-z, A-Z), números (0-9), guións baixos (_) e puntos (.).',
+ 'config-invalid-db-name' => 'O nome da base de datos, "$1", é incorrecto.
+Só pode conter letras ASCII (a-z, A-Z), números (0-9), guións baixos (_) e guións (-).',
+ 'config-invalid-db-prefix' => 'O prefixo da base de datos, "$1", é incorrecto.
+Só pode conter letras ASCII (a-z, A-Z), números (0-9), guións baixos (_) e guións (-).',
+ 'config-connection-error' => '$1.
+
+Comprobe o servidor, nome de usuario e contrasinal que hai a continuación e inténteo de novo.',
+ 'config-invalid-schema' => 'O esquema de MediaWiki, "$1", é incorrecto.
+Só pode conter letras ASCII (a-z, A-Z), números (0-9) e guións baixos (_).',
+ 'config-db-sys-create-oracle' => 'O programa de instalación soamente soporta o emprego de contas SYSDBA como método para crear unha nova conta.',
+ 'config-db-sys-user-exists-oracle' => 'A conta de usuario "$1" xa existe. SYSDBA soamente se pode empregar para a creación dunha nova conta!',
+ 'config-postgres-old' => 'Necesítase PostgreSQL $1 ou posterior; ten a versión $2.',
+ 'config-sqlite-name-help' => 'Escolla un nome que identifique o seu wiki.
+Non utilice espazos ou guións.
+Este nome será utilizado para o ficheiro de datos SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Non se puido crear o directorio de datos <code><nowiki>$1</nowiki></code>, porque o servidor web non pode escribir no directorio pai <code><nowiki>$2</nowiki></code>.
+
+O programa de instalación determinou o usuario que executa o seu servidor web.
+Para continuar, faga que se poida escribir no directorio <code><nowiki>$3</nowiki></code>.
+Nun sistema Unix/Linux cómpre realizar:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Non se puido crear o directorio de datos <code><nowiki>$1</nowiki></code>, porque o servidor web non pode escribir no directorio pai <code><nowiki>$2</nowiki></code>.
+
+O programa de instalación non puido determinar o usuario que executa o seu servidor web.
+Para continuar, faga que se poida escribir globalmente no directorio <code><nowiki>$3</nowiki></code>.
+Nun sistema Unix/Linux cómpre realizar:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Erro ao crear o directorio de datos "$1".
+Comprobe a localización e inténteo de novo.',
+ 'config-sqlite-dir-unwritable' => 'Non se puido escribir o directorio "$1".
+Cambie os permisos para que o servidor poida escribir nel e inténteo de novo.',
+ 'config-sqlite-connection-error' => '$1.
+
+Comprobe o directorio de datos e o nome da base de datos que hai a continuación e inténteo de novo.',
+ 'config-sqlite-readonly' => 'Non se pode escribir no ficheiro <code>$1</code>.',
+ 'config-sqlite-cant-create-db' => 'Non se puido crear o ficheiro da base de datos <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'Falta o soporte FTS3 para o PHP; diminuíndo as táboas',
+ 'config-can-upgrade' => "Existen táboas MediaWiki nesta base de datos.
+Para actualizalas a MediaWiki \$1, prema sobre \"'''Continuar'''\".",
+ 'config-upgrade-done' => "Actualización completada.
+
+Agora pode [$1 comezar a utilizar o seu wiki].
+
+Se quere rexenerar o seu ficheiro <code>LocalSettings.php</code>, prema no botón que aparece a continuación.
+Isto '''non é recomendable''' a menos que estea a ter problemas co seu wiki.",
+ 'config-upgrade-done-no-regenerate' => 'Actualización completada.
+
+Xa pode [$1 comezar a usar o seu wiki].',
+ 'config-regenerate' => 'Rexenerar LocalSettings.php →',
+ 'config-show-table-status' => 'A pescuda SHOW TABLE STATUS fallou!',
+ 'config-unknown-collation' => "'''Atención:''' A base de datos está a empregar unha clasificación alfabética irrecoñecible.",
+ 'config-db-web-account' => 'Conta na base de datos para o acceso á internet',
+ 'config-db-web-help' => 'Seleccione o nome de usuario e contrasinal que o servidor web empregará para se conectar ao servidor da base de datos durante o funcionamento normal do wiki.',
+ 'config-db-web-account-same' => 'Empregar a mesma conta que para a instalación',
+ 'config-db-web-create' => 'Crear a conta se aínda non existe',
+ 'config-db-web-no-create-privs' => 'A conta que especificou para a instalación non ten os privilexios suficientes para crear unha conta.
+A conta que se especifique aquí xa debe existir.',
+ 'config-mysql-engine' => 'Motor de almacenamento:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' é case sempre a mellor opción, dado que soporta ben os accesos simultáneos.
+
+'''MyISAM''' é máis rápido en instalacións de usuario único e de só lectura.
+As bases de datos MyISAM tenden a se corromper máis a miúdo ca as bases de datos InnoDB.",
+ 'config-mysql-charset' => 'Conxunto de caracteres da base de datos:',
+ 'config-mysql-binary' => 'Binario',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "No '''modo binario''', MediaWiki almacena texto UTF-8 na base de datos en campos binarios.
+Isto é máis eficaz ca o modo UTF-8 de MySQL e permítelle usar o rango completo de caracteres Unicode.
+
+No '''modo UTF-8''', MySQL saberá o xogo de caracteres dos seus datos e pode presentar e converter os datos de maneira axeitada,
+pero non lle deixará gardar caracteres por riba do [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingüe básico].",
+ 'config-site-name' => 'Nome do wiki:',
+ 'config-site-name-help' => 'Isto aparecerá na barra de títulos do navegador e noutros lugares.',
+ 'config-site-name-blank' => 'Escriba o nome do sitio.',
+ 'config-project-namespace' => 'Espazo de nomes do proxecto:',
+ 'config-ns-generic' => 'Proxecto',
+ 'config-ns-site-name' => 'O mesmo nome que o wiki: $1',
+ 'config-ns-other' => 'Outro (especificar)',
+ 'config-ns-other-default' => 'OMeuWiki',
+ 'config-project-namespace-help' => 'Seguindo o exemplo da Wikipedia, moitos wikis manteñen as súas páxinas de políticas separadas das súas páxinas de contido, nun "\'\'\'espazo de nomes do proxecto\'\'\'".
+Todos os títulos presentes neste espazo de nomes comezan cun prefixo determinado, que pode especificar aquí.
+Tradicionalmente, este prefixo deriva do nome do wiki, pero non pode conter caracteres de puntuación como "#" ou ":".',
+ 'config-ns-invalid' => 'O espazo de nomes especificado, "<nowiki>$1</nowiki>", é incorrecto.
+Especifique un espazo de nomes do proxecto diferente.',
+ 'config-ns-conflict' => 'O espazo de nomes especificado, "<nowiki>$1</nowiki>", entra en conflito co espazo de nomes MediaWiki por defecto.
+Especifique un espazo de nomes do proxecto diferente.',
+ 'config-admin-box' => 'Conta de administrador',
+ 'config-admin-name' => 'O seu nome:',
+ 'config-admin-password' => 'Contrasinal:',
+ 'config-admin-password-confirm' => 'Repita o contrasinal:',
+ 'config-admin-help' => 'Escriba o nome de usuario que queira aquí, por exemplo, "Joe Bloggs".
+Este é o nome que usará para acceder ao sistema do wiki.',
+ 'config-admin-name-blank' => 'Escriba un nome de usuario para o administrador.',
+ 'config-admin-name-invalid' => 'O nome de usuario especificado, "<nowiki>$1</nowiki>", é incorrecto.
+Especifique un nome de usuario diferente.',
+ 'config-admin-password-blank' => 'Escriba un contrasinal para a conta de administrador.',
+ 'config-admin-password-same' => 'O contrasinal debe diferir do nome de usuario.',
+ 'config-admin-password-mismatch' => 'Os contrasinais non coinciden.',
+ 'config-admin-email' => 'Enderezo de correo electrónico:',
+ 'config-admin-email-help' => 'Escriba aquí un enderezo de correo electrónico para que poida recibir mensaxes doutros usuarios a través do wiki, restablecer o contrasinal e ser notificado das modificacións feitas nas páxinas presentes na súa lista de vixilancia. Pode deixar este campo en branco.',
+ 'config-admin-error-user' => 'Erro interno ao crear un administrador co nome "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Erro interno ao establecer un contrasinal para o administrador "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Escribiu un enderezo de correo electrónico non válido.',
+ 'config-subscribe' => 'Subscríbase á [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de correo de anuncios sobre lanzamentos].',
+ 'config-subscribe-help' => 'Esta é unha lista de correos de baixo volume usada para anuncios sobre lanzamentos de novas versións, incluíndo avisos de seguridade importantes.
+Debería subscribirse a ela e actualizar a súa instalación MediaWiki cando saian as novas versións.',
+ 'config-almost-done' => 'Xa case rematou!
+Neste paso pode saltar o resto da configuración e instalar o wiki agora mesmo.',
+ 'config-optional-continue' => 'Facédeme máis preguntas.',
+ 'config-optional-skip' => 'Xa estou canso. Instalade o wiki.',
+ 'config-profile' => 'Perfil dos dereitos de usuario:',
+ 'config-profile-wiki' => 'Wiki tradicional',
+ 'config-profile-no-anon' => 'Necesítase a creación dunha conta',
+ 'config-profile-fishbowl' => 'Só os editores autorizados',
+ 'config-profile-private' => 'Wiki privado',
+ 'config-profile-help' => "Os wikis funcionan mellor canta máis xente os edite.
+En MediaWiki, é doado revisar os cambios recentes e reverter calquera dano feito por usuarios novatos ou con malas intencións.
+Porén, moita xente atopa MediaWiki útil nunha ampla variedade de papeis, e ás veces non é fácil convencer a todos dos beneficios que leva consigo o estilo wiki.
+Vostede decide.
+
+O tipo '''{{int:config-profile-wiki}}''' permite a edición por parte de calquera, mesmo sen rexistro.
+A opción '''{{int:config-profile-no-anon}}''' proporciona un control maior, pero pode desalentar os colaboradores casuais.
+
+O escenario '''{{int:config-profile-fishbowl}}''' restrinxe a edición aos usuarios aprobados, pero o público pode ollar as páxinas, incluíndo os historiais.
+O tipo '''{{int:config-profile-private}}''' só deixa que os usuarios aprobados vexan e editen as páxinas.
+
+Hai dispoñibles configuracións de dereitos de usuario máis complexas despois da instalación; bótelle un ollo a [http://www.mediawiki.org/wiki/Manual:User_rights esta entrada no manual].",
+ 'config-license' => 'Dereitos de autor e licenza:',
+ 'config-license-none' => 'Sen licenza ao pé',
+ 'config-license-cc-by-sa' => 'Creative Commons recoñecemento compartir igual',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons recoñecemento non comercial compartir igual',
+ 'config-license-cc-0' => 'Creative Commons Zero',
+ 'config-license-gfdl-old' => 'Licenza de documentación libre de GNU 1.2',
+ 'config-license-gfdl-current' => 'Licenza de documentación libre de GNU 1.3 ou posterior',
+ 'config-license-pd' => 'Dominio público',
+ 'config-license-cc-choose' => 'Seleccione unha licenza Creative Commons personalizada',
+ 'config-license-help' => "Moitos wikis públicos liberan todas as súas contribucións baixo unha [http://freedomdefined.org/Definition/Gl licenza libre].
+Isto axuda a crear un sentido de propiedade na comunidade e anima a seguir contribuíndo durante moito tempo.
+Xeralmente, non é necesario nos wikis privados ou de empresas.
+
+Se quere poder empregar textos da Wikipedia, así como que a Wikipedia poida aceptar textos copiados do seu wiki, escolla a licenza '''Creative Commons recoñecemento compartir igual'''.
+
+A licenza de documentación libre de GNU era a licenza anterior da Wikipedia.
+Malia aínda ser unha licenza válida, esta ten algunhas características que poden facer o reuso e a interpretación difíciles.",
+ 'config-email-settings' => 'Configuración do correo electrónico',
+ 'config-enable-email' => 'Activar os correos electrónicos de saída',
+ 'config-enable-email-help' => 'Se quere que o correo electrónico funcione, cómpre configurar os [http://www.php.net/manual/en/mail.configuration.php parámetros PHP] correctamente.
+Se non quere ningunha característica no correo, pode desactivalas aquí.',
+ 'config-email-user' => 'Activar o intercambio de correos electrónicos entre usuarios',
+ 'config-email-user-help' => 'Permitir que todos os usuarios intercambien correos electrónicos, se o teñen activado nas súas preferencias.',
+ 'config-email-usertalk' => 'Activar a notificación da páxina de conversa de usuario',
+ 'config-email-usertalk-help' => 'Permitir que os usuarios reciban notificacións cando a súa páxina de conversa de usuario sufra modificacións, se o teñen activado nas súas preferencias.',
+ 'config-email-watchlist' => 'Activar a notificación da lista de vixilancia',
+ 'config-email-watchlist-help' => 'Permitir que os usuarios reciban notificacións sobre modificacións nas páxinas que vixían, se o teñen activado nas súas preferencias.',
+ 'config-email-auth' => 'Activar a autenticación do correo electrónico',
+ 'config-email-auth-help' => "Se esta opción está activada, os usuarios teñen que confirmar o seu correo electrónico mediante unha ligazón enviada ao enderezo cando o definan ou o cambien.
+Só os enderezos autenticados poden recibir correos doutros usuarios ou de notificación.
+É '''recomendable''' establecer esta opción nos wikis públicos para evitar abusos potenciais das características do correo.",
+ 'config-email-sender' => 'Enderezo de correo electrónico de retorno:',
+ 'config-email-sender-help' => 'Introduza o enderezo de correo electrónico a usar como enderezo de retorno dos correos de saída.
+Aquí é onde irán parar os correos rexeitados.
+Moitos servidores de correo electrónico esixen que polo menos a parte do nome de dominio sexa válido.',
+ 'config-upload-settings' => 'Imaxes e carga de ficheiros',
+ 'config-upload-enable' => 'Activar a carga de ficheiros',
+ 'config-upload-help' => 'A subida de ficheiros expón potencialmente o servidor a riscos de seguridade.
+Para obter máis información, lea a [http://www.mediawiki.org/wiki/Manual:Security sección de seguridade] no manual.
+
+Para activar a carga de ficheiros, cambie o modo no subdirectorio <code>images</code> que está baixo o directorio raíz de MediaWiki, de xeito que o servidor web poida escribir nel.
+A continuación, active esta opción.',
+ 'config-upload-deleted' => 'Directorio para os ficheiros borrados:',
+ 'config-upload-deleted-help' => 'Escolla un directorio no que arquivar os ficheiros borrados.
+O ideal é que non sexa accesible desde a web.',
+ 'config-logo' => 'URL do logo:',
+ 'config-logo-help' => 'A aparencia de MediaWiki por defecto inclúe espazo para un logo de 135x160 píxeles no recuncho superior esquerdo.
+Cargue unha imaxe do tamaño axeitado e introduza o URL aquí.
+
+Se non quere un logo, deixe esta caixa en branco.',
+ 'config-instantcommons' => 'Activar Instant Commons',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons InstantCommons] é unha característica que permite aos wikis usar imaxes, sons e outros ficheiros multimedia atopados no sitio da [http://commons.wikimedia.org/wiki/Portada_galega Wikimedia Commons].
+Para facer isto, MediaWiki necesita acceso á internet.
+
+Para obter máis información sobre esta característica, incluíndo as instrucións sobre como configuralo para outros wikis que non sexan a Wikimedia Commons, consulte [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos o manual].',
+ 'config-cc-error' => 'A escolla da licenza Creative Commons non deu resultados.
+Escriba o nome da licenza manualmente.',
+ 'config-cc-again' => 'Escolla outra vez...',
+ 'config-cc-not-chosen' => 'Escolla a licenza Creative Commons que desexe e prema en "continuar".',
+ 'config-advanced-settings' => 'Configuración avanzada',
+ 'config-cache-options' => 'Configuración da caché de obxectos:',
+ 'config-cache-help' => 'A caché de obxectos emprégase para mellorar a velocidade de MediaWiki mediante a memorización de datos usados con frecuencia.
+É amplamente recomendable a súa activación nos sitios de tamaño medio e grande; os sitios pequenos obterán tamén beneficios.',
+ 'config-cache-none' => 'Sen caché (non se elimina ningunha funcionalidade, pero pode afectar á velocidade en wikis grandes)',
+ 'config-cache-accel' => 'Caché de obxectos do PHP (APC, eAccelerator, XCache ou WinCache)',
+ 'config-cache-memcached' => 'Empregar o Memcached (necesita unha instalación e configuración adicional)',
+ 'config-memcached-servers' => 'Servidores da memoria caché:',
+ 'config-memcached-help' => 'Lista de enderezos IP para Memcached.
+Debe especificarse un por liña, así como o porto a usar. Por exemplo:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Seleccionou Memcached como o seu tipo de caché, pero non especificou ningún servidor.',
+ 'config-memcache-badip' => 'Escribiu un enderezo IP inválido para Memcached: $1.',
+ 'config-memcache-noport' => 'Non especificou o porto a usar no servidor Memcached: $1.
+Se non sabe o porto, o predeterminado é 11211.',
+ 'config-memcache-badport' => 'Os números de porto Memcached deben estar entre $1 e $2.',
+ 'config-extensions' => 'Extensións',
+ 'config-extensions-help' => 'As extensións anteriores detectáronse no seu directorio <code>./extensions</code>.
+
+Quizais necesite algunha configuración adicional, pero pode activalas agora',
+ 'config-install-alreadydone' => "'''Atención:''' Semella que xa instalou MediaWiki e que o está a instalar de novo.
+Vaia ata a seguinte páxina.",
+ 'config-install-begin' => 'Ao premer en "{{int:config-continue}}", comezará a instalación de MediaWiki.
+Se aínda quere facer algún cambio, volva atrás.',
+ 'config-install-step-done' => 'feito',
+ 'config-install-step-failed' => 'erro',
+ 'config-install-extensions' => 'Incluíndo as extensións',
+ 'config-install-database' => 'Configurando a base de datos',
+ 'config-install-pg-schema-not-exist' => 'O esquema PostgreSQL non existe.',
+ 'config-install-pg-schema-failed' => 'Fallou a creación de táboas.
+Asegúrese de que o usuario "$1" pode escribir no esquema "$2".',
+ 'config-install-pg-commit' => 'Validando os cambios',
+ 'config-install-pg-plpgsql' => 'Comprobación da lingua PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Cómpre instalar a lingua PL/pgSQL na base de datos $1',
+ 'config-pg-no-create-privs' => 'A conta especificada para a instalación non ten os privilexios necesarios para crear unha conta.',
+ 'config-install-user' => 'Creando o usuario da base de datos',
+ 'config-install-user-alreadyexists' => 'O usuario "$1" xa existe',
+ 'config-install-user-create-failed' => 'A creación do usuario "$1" fallou: $2',
+ 'config-install-user-grant-failed' => 'Fallou a concesión de permisos ao usuario "$1": $2',
+ 'config-install-tables' => 'Creando as táboas',
+ 'config-install-tables-exist' => "'''Atención:''' Semella que as táboas de MediaWiki xa existen.
+Saltando a creación.",
+ 'config-install-tables-failed' => "'''Erro:''' Fallou a creación da táboa. Descrición do erro: $1",
+ 'config-install-interwiki' => 'Enchendo a táboa de interwiki por defecto',
+ 'config-install-interwiki-list' => 'Non se puido atopar o ficheiro <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Atención:''' Semella que a táboa de interwiki xa contén entradas.
+Saltando a lista por defecto.",
+ 'config-install-stats' => 'Iniciando as estatísticas',
+ 'config-install-keys' => 'Xerando a clave secreta',
+ 'config-install-sysop' => 'Creando a conta de usuario de administrador',
+ 'config-install-subscribe-fail' => 'Non se puido subscribir á lista mediawiki-announce',
+ 'config-install-mainpage' => 'Creando a páxina principal co contido por defecto',
+ 'config-install-extension-tables' => 'Creando as táboas para as extensións activadas',
+ 'config-install-mainpage-failed' => 'Non se puido inserir a páxina principal: $1',
+ 'config-install-done' => "'''Parabéns!'''
+Instalou correctamente MediaWiki.
+
+O programa de instalación xerou un ficheiro <code>LocalSettings.php</code>.
+Este ficheiro contén toda a súa configuración.
+
+Terá que descargalo e poñelo na base da instalación do seu wiki (no mesmo directorio ca index.php). A descarga debería comezar automaticamente.
+
+Se non comezou a descarga ou se a cancelou, pode facer que comece de novo premendo na ligazón que aparece a continuación:
+
+$3
+
+'''Nota:''' Se non fai iso agora, este ficheiro de configuración xerado non estará dispoñible máis adiante se sae da instalación sen descargalo.
+
+Cando faga todo isto, xa poderá '''[$2 entrar no seu wiki]'''.",
+ 'config-download-localsettings' => 'Descargar o LocalSettings.php',
+ 'config-help' => 'axuda',
+);
+
+/** Swiss German (Alemannisch)
+ * @author Als-Holder
+ */
+$messages['gsw'] = array(
+ 'config-desc' => 'S MediaWiki-Inschtallationsprogramm',
+ 'config-title' => 'MediaWiki $1 inschtalliere',
+ 'config-information' => 'Information',
+ 'config-localsettings-upgrade' => "'''Warnig:''' E Datei <code>LocalSettings.php</code> isch gfunde wore.
+Fir d Aktualisierig vu dr däre Inschtallation, gib bitte dr Wärt vum Parameter <code>\$wgUpgradeKey</code> im Fäld unten yy.
+Du findsch dr Wärt in dr Datei LocalSettings.php.",
+ 'config-localsettings-key' => 'Aktualisierigsschlissel:',
+ 'config-localsettings-badkey' => 'Dr Aktualisierigsschlissel, wu du aagee hesch, isch falsch.',
+ 'config-session-error' => 'Fähler bim Starte vu dr Sitzig: $1',
+ 'config-session-expired' => 'D Sitzigsdate sin schyns abgloffe.
+Sitzige sin fir e Zytruum vu $1 konfiguriert.
+Dää cha dur Aalupfe vum Parameter <code>session.gc_maxlifetime</code> in dr Datei <code>php.ini</code> greßer gmacht wäre.
+Dr Inschtallationsvorgang nomol starte.',
+ 'config-no-session' => 'Dyyni Sitzigsdate sin verlore gange!
+D Datei <code>php.ini</code> mueß prieft wäre un s mueß derby sichergstellt wäre, ass dr Parameter <code>session.save_path</code> uf s richtig Verzeichnis verwyyst.',
+ 'config-your-language' => 'Dyy Sproch:',
+ 'config-your-language-help' => 'Bitte d Sproch uuswehle, wu bim Inschtallationsvorgang soll brucht wäre.',
+ 'config-wiki-language' => 'Wikisproch:',
+ 'config-wiki-language-help' => 'Bitte d Sproch uuswehle, wu s Wiki in dr Hauptsach din gschribe wird.',
+ 'config-back' => '← Zruck',
+ 'config-continue' => 'Wyter →',
+ 'config-page-language' => 'Sproch',
+ 'config-page-welcome' => 'Willchuu bi MediaWiki!',
+ 'config-page-dbconnect' => 'Mit dr Datebank verbinde',
+ 'config-page-upgrade' => 'E Inschtallition, wu s scho het, aktualisiere',
+ 'config-page-dbsettings' => 'Datebankyystellige',
+ 'config-page-name' => 'Name',
+ 'config-page-options' => 'Optione',
+ 'config-page-install' => 'Inschtalliere',
+ 'config-page-complete' => 'Fertig!',
+ 'config-page-restart' => 'Inschtallation nomol aafange',
+ 'config-page-readme' => 'Liis mi',
+ 'config-page-releasenotes' => 'Hiiwys fir d Vereffentlichung',
+ 'config-page-copying' => 'Am Kopiere',
+ 'config-page-upgradedoc' => 'Am Aktualisiere',
+ 'config-help-restart' => 'Witt alli Date, wu Du yygee hesch, lesche un d Inschtallation nomol aafange?',
+ 'config-restart' => 'Jo, nomol aafange',
+ 'config-welcome' => '=== Priefig vu dr Inschtallationsumgäbig ===
+Basispriefige wäre durgfiert zum Feschtstelle, eb d Inschtallationsumgäbig fir d Inschtallation vu MediaWiki geignet isch.
+Du sottsch d Ergebnis vu däre Priefig aagee, wänn Du bi dr Inschtallation Hilf bruchsch.',
+ 'config-copyright' => "=== Copyright un Nutzigsbedingige ===
+
+$1
+
+Des Programm isch e freji Software, d. h. s cha, no dr Bedingige vu dr GNU General Public-Lizänz, wu vu dr Free Software Foundation vereffentligt woren isch, wyterverteilt un/oder modifiziert wäre. Doderbyy cha d Version 2, oder no eigenem Ermässe, jedi nejeri Version vu dr Lizänz brucht wäre.
+
+Des Programm wird in dr Hoffnig verteilt, ass es nitzli isch, aber '''ohni jedi Garanti''' un sogar ohni di impliziert Garanti vun ere '''Märtgängigkeit''' oder '''Eignig fir e bstimmte Zwäck'''. Doderzue git meh Hiiwys in dr GNU General Public-Lizänz.
+
+E <doclink href=Copying>Kopi vu dr GNU General Public-Lizänz</doclink> sott zämme mit däm Programm verteilt wore syy. Wänn des nit eso isch, cha ne Kopi bi dr Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftli aagforderet oder [http://www.gnu.org/copyleft/gpl.html online gläse] wäre.",
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki Websyte vu MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Nutzeraaleitig zue MediaWiki]
+* [http://www.mediawiki.org/wiki/Manual:Contents Adminischtratoreaaleitig zue MediaWiki]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Vilmol gstellti Froge zue MediaWiki]',
+ 'config-env-good' => 'D Inschtallationsumgäbig isch prieft wore.
+Du chasch MediaWiki inschtalliere.',
+ 'config-env-bad' => 'D Inschtallationsumgäbigisch prieft wore.
+Du chasch MediaWiki nit inschtalliere.',
+ 'config-env-php' => 'PHP $1 isch inschtalliert.',
+ 'config-unicode-using-utf8' => 'Fir d Unicode-Normalisierig wird em Brion Vibber syy utf8_normalize.so yygsetzt.',
+ 'config-unicode-using-intl' => 'For d Unicode-Normalisierig wird d [http://pecl.php.net/intl PECL-Erwyterig intl] yygsetzt.',
+ 'config-unicode-pure-php-warning' => "'''Warnig:''' D [http://pecl.php.net/intl PECL-Erwyterig intl] isch fir d Unicode-Normalisierig nit verfiegbar. Wäge däm wird di langsam pure-PHP-Implementierig brucht.
+Wänn Du ne Websyte mit ere große Bsuechrzahl bedrybsch, sottsch e weng ebis läse iber [http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-Normalisierig (en)].",
+ 'config-unicode-update-warning' => "'''Warnig:''' Di inschtalliert Version vum Unicode-Normalisierigswrapper verwändet e elteri Version vu dr Bibliothek vum [http://site.icu-project.org/ ICU-Projäkt].
+Du sottsch si [http://www.mediawiki.org/wiki/Unicode_normalization_considerations aktualisiere], wänn Dor d Verwändig vu Unicode wichtig isch.",
+ 'config-no-db' => 'S isch kei adäquate Datebanktryyber gfunde wore!',
+ 'config-no-db-help' => 'S mueß e Datebanktryyber fir PHP inschtalliert wäre.
+Die Datebanksyschtem wäre unterstitzt: $1
+
+Wänn Du ne gmeinschaftli gnutzte Server fir s Hosting bruchsch, muesch dr Hoster froge go ne adäquate Datebanktryyber inschtalliere.
+Wänn Du PHP sälber kumpiliert hesch, muesch s nej konfiguriere, dr Datebankclient mueß aktiviert wäre. Doderzue chasch zem Byschpel <code>./configure --with-mysql</code> uusfiere.
+Wänn Du PHP iber d Paketverwaltig vun ere Debian- oder Ubuntu-Inschtallation inschtalliert hesch, muesch s „php5-mysql“-Paket nooinschtalliere.',
+ 'config-no-fts3' => "'''Warnig:''' SQLite isch ohni s [http://sqlite.org/fts3.html FTS3-Modul] kumpiliert wore, s stehn kei Suechfunktione z Verfiegig.",
+ 'config-register-globals' => "'''Warnig: Dr Parameter <code>[http://php.net/register_globals register_globals]</code> vu PHP isch aktiviert.'''
+'''Är sott deaktiviert wäre, wänn des megli isch.'''
+D MediaWiki-Inschtallation lauft einwäg, aber dr Server isch aafällig fi megligi Sicherheitsprobläm.",
+ 'config-magic-quotes-runtime' => "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]</code> vu PHP isch aktiviert!'''
+Die Yystellig fiert zue nit vorhärsähbare Probläm bi dr Datenyygab.
+MediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
+ 'config-magic-quotes-sybase' => "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> vu PHP isch aktiviert!'''
+Die Yystellig fiert zue nit vorhärsähbare Probläm bi dr Datenyygab.
+MediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
+ 'config-mbstring' => "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> vu PHP isch aktiviert!'''
+Die Yystellig verursacht Fähler un fiert zue nit vorhärsähbare Probläm bi dr Datenyygab.
+MediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
+ 'config-ze1' => "'''Fatal: Dr Parameter <code>[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]</code> vu PHP isch aktiviert!'''
+Die Yystellig fiert zue große Fähler bi MediaWiki.
+MediaWiki cha nit inschtalliert wäre, solang dää Parameter nit deaktiviert woren isch.",
+ 'config-safe-mode' => "'''Warnig:''' D Funktion <code>[http://www.php.net/features.safe-mode Safe Mode]</code> vu PHP isch aktiviert.
+Des cha zue Probläm fiere, vor allem wänn s Uffelade vu Dateie soll megli syy bzw. dr Uuszeichner <code>math</code> soll brucht wäre.",
+ 'config-xml-bad' => 'S XML-Modul vu PHP fählt.
+MediaWiki brucht Funktione, wu au des Modul z Verfiegig stellt, un funktioniert in däre Konfiguration nit.
+Wänn Mandriva brucht wird, mueß no s „php-xml“-Paket inschtalliert wäre.',
+ 'config-pcre' => 'S PHP-Modul fir d PCRE-Unterstitzig isch nit gfunde wore.
+MediaWiki brucht aber perl-kompatibli reguläri Uusdruck zum lauffähig syy.',
+ 'config-pcre-no-utf8' => "'''Fatale Fähler: S PHP-Modul PCRE isch schyns ohni PCRE_UTF8-Unterstitzig kompiliert wore.'''
+MediaWiki brucht d UTF-8-Unterstitzi zum fählerfrej lauffähig syy.",
+ 'config-memory-raised' => 'Dr PHP-Parameter <code>memory_limit</code> lyt bi $1 un isch uf $2 uffegsetzt wore.',
+ 'config-memory-bad' => "'''Warnig:''' Dr PHP-Parameter <code>memory_limit</code> lyt bi $1.
+Dää Wärt isch wahrschyns z nider.
+Dr Inschtallationsvorgang chennt wäge däm fählschlaa!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] isch inschtalliert',
+ 'config-apc' => '[http://www.php.net/apc APC] isch inschtalliert',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] isch inschtalliert',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] isch inschtalliert',
+ 'config-no-cache' => "'''Warnig:''' [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] hän nit chenne gfunde wäre.
+S Objäktcaching isch wäge däm nit aktiviert.",
+ 'config-diff3-bad' => 'GNU diff3 isch nit gfunde wore.',
+ 'config-imagemagick' => 'ImageMagick isch gfunde wore: <code>$1</code>.
+Miniaturaasichte vu Bilder sin megli, sobald s Uffelade vu Dateie aktiviert isch.',
+ 'config-help' => 'Hilf',
+);
+
+/** Hebrew (עברית)
+ * @author Amire80
+ * @author YaronSh
+ */
+$messages['he'] = array(
+ 'config-desc' => 'תכנית ההתקנה של מדיה־ויקי',
+ 'config-title' => 'התקנת מדיה־ויקי $1',
+ 'config-information' => 'פרטים',
+ 'config-localsettings-upgrade' => 'זוהה קובץ <code>LocalSettings.php</code>.
+כדי לשדרג את ההתקנה הזאת, נא להקליד את הערך של <code>$wgUpgradeKey</code> בתיבה להלן.
+אפשר למצוא אותו בקובץ LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'זוהה קובץ LocalSettings.php.
+כדי לשדרג את ההתקנה הזאת, הריצו את update.php ולא את הקובץ הזה.',
+ 'config-localsettings-key' => 'מפתח השדרוג:',
+ 'config-localsettings-badkey' => 'המפתח שהקלדתם שגוי',
+ 'config-upgrade-key-missing' => 'זוהתה התקנה קיימת של מדיה־ויקי.
+כדי לשדרג את ההתקנה הזאת, אנא כתבו את השורה הבא בתחתית קובץ LocalSettings.php שלכם:
+
+$1',
+ 'config-localsettings-incomplete' => 'נראה שקובץ LocalSettings.php הקיים אינו שלם.
+המשתנה $1 אינו מוגדר.
+נו לשנות את קובץ LocalSettings.php שלכם כך שהמשתנה הזה יהיה מוגדר וללחוץ "המשך".',
+ 'config-localsettings-connection-error' => 'אירעה שגיאה בעת חיבור למסד נתונים עם הגדרות ב־LocalSettings.php או ב־AdminSettings.php. נא לתקן את ההגדרות האלו ולנסות שוב.
+
+$1',
+ 'config-session-error' => 'שגיאה באתחול שיחה: $1',
+ 'config-session-expired' => 'נראה שנתוני השיחה שלכם פגו.
+השיחות מוגדרות להיות תקפות לזמן של $1.
+אפשר להגדיל את זה ב־<code>session.gc_maxlifetime</code> בקובץ php.ini.
+יש להתחיל מחדש את תהליך ההתקנה.',
+ 'config-no-session' => 'נתוני השיחה שלכם אבדו!
+יש לבדוק את קובץ php.ini שלכם ולוודא שתיקייה נכונה מוגדרת ב־<code>session.save_path</code>.',
+ 'config-your-language' => 'השפה שלכם:',
+ 'config-your-language-help' => 'נא לבחור את השפה שתשמש במהלך ההתקנה.',
+ 'config-wiki-language' => 'שפת הוויקי:',
+ 'config-wiki-language-help' => 'נא לבחור את השפה העיקרית שבה ייכתב ויקי זה.',
+ 'config-back' => '→ חזרה',
+ 'config-continue' => 'המשך ←',
+ 'config-page-language' => 'שפה',
+ 'config-page-welcome' => 'ברוכים הבאים למדיה־ויקי!',
+ 'config-page-dbconnect' => 'התחברות למסד הנתונים',
+ 'config-page-upgrade' => 'שדרוג התקנה קיימת',
+ 'config-page-dbsettings' => 'הגדרות מסד הנתונים',
+ 'config-page-name' => 'שם',
+ 'config-page-options' => 'אפשרויות',
+ 'config-page-install' => 'התקנה',
+ 'config-page-complete' => 'הושלמה!',
+ 'config-page-restart' => 'הפעלת ההתקנה מחדש',
+ 'config-page-readme' => 'קרא־אותי',
+ 'config-page-releasenotes' => 'הערות גרסה',
+ 'config-page-copying' => 'העתקה',
+ 'config-page-upgradedoc' => 'שדרוג',
+ 'config-page-existingwiki' => 'ויקי קיים',
+ 'config-help-restart' => 'האם ברצונך לפנות את כל הנתונים שנשמרו שהוזנו על ידיך ולהתחיל מחדש את תהליך ההתקנה?',
+ 'config-restart' => 'כן, להפעיל מחדש',
+ 'config-welcome' => '=== בדיקות סביבה ===
+בדיקות בסיסיות מתבצעות כדי לבדוק שהסביבה מתאימה להתקנת מדיה־ויקי.
+יש לתת את תוצאות הבדיקות האלו אם תזדקקו לעזרה בזמן ההתקנה.',
+ 'config-copyright' => "=== זכויות יוצרים ותנאים ===
+
+$1
+
+תכנית זו היא תכנה חופשית; באפשרותך להפיצה מחדש ו/או לשנות אותה על פי תנאי הרישיון הציבורי הכללי של GNU כפי שפורסם על ידי קרן התכנה החופשית; בין אם גרסה 2 של הרישיון, ובין אם (לפי בחירתך) כל גרסה מאוחרת שלו.
+
+תכנית זו מופצת בתקווה שתהיה מועילה, אבל '''בלא אחריות כלשהי'''; ואפילו ללא האחריות המשתמעת בדבר '''מסחריותה''' או '''התאמתה למטרה '''מסוימת'''. לפרטים נוספים, ניתן לעיין ברישיון הציבורי הכללי של GNU.
+
+לתכנית זו אמור היה להיות מצורף <doclink href=Copying>עותק של הרישיון הציבורי הכללי של GNU</doclink>; אם לא, עליך לכתוב ל־Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, , MA 02111-1307, USA. או [http://www.gnu.org/copyleft/gpl.html לקרוא אותו דרך האינטרנט].",
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki אתר הבית של מדיה־ויקי]
+* [http://www.mediawiki.org/wiki/Help:Contents המדריך למשתמשים]
+* [http://www.mediawiki.org/wiki/Manual:Contents המדריך למנהלים]
+* [http://www.mediawiki.org/wiki/Manual:FAQ שו״ת]
+----
+* <doclink href=Readme>קרא אותי</doclink>
+* <doclink href=ReleaseNotes>הערות גרסה</doclink>
+* <doclink href=Copying>העתקה</doclink>
+* <doclink href=UpgradeDoc>שדרוג</doclink>',
+ 'config-env-good' => 'הסביבה שלכם נבדקה.
+אפשר להתקין מדיה־ויקי.',
+ 'config-env-bad' => 'הסביבה שלכם נבדקה.
+אי־אפשר להתקין מדיה־ויקי.',
+ 'config-env-php' => 'מותקנת PHP $1.',
+ 'config-env-php-toolow' => 'מותקנת PHP $1.
+למדיה־ויקי נדרשת PHP $2 או גרסה גבוהה יותר.',
+ 'config-unicode-using-utf8' => 'משתמש ב־normalize.so של בריון ויבר לנרמול יוניקוד.',
+ 'config-unicode-using-intl' => 'משתמש בהרחבת [http://pecl.php.net/intl הרחבת intl PECL] לנרמול יוניקוד',
+ 'config-unicode-pure-php-warning' => "'''אזהרה''': [http://pecl.php.net/intl הרחבת intl PECL] אינה זמינה לטיפול בנרמול יוניקוד. משתמש ביישום PHP טהור ואטי יותר.
+אם זה אתר בעל תעבורה גבוהה, כדאי לקרוא את המסמך הבא: [http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalization].",
+ 'config-unicode-update-warning' => "'''אזהרה''': הגרסה המותקנת של מעטפת נרמול יוניקוד משתמשת בגרסה ישנה של הספרייה של [http://site.icu-project.org/ פרויקט ICU].
+כדאי [http://www.mediawiki.org/wiki/Unicode_normalization_considerations לעדכן] אם יש חשוב לכם הטיפול ביוניקוד.",
+ 'config-no-db' => 'לא נמצא דרייבר מסד נתונים מתאים.',
+ 'config-no-db-help' => 'יש להתקין דרייבר מסד נתונים ל־PHP.
+נתמכים הסוגים הבאים של מסדי נתונים: $1.
+
+אם אתם משתמשים באירוח משותף, בקשו מספק האירוח שלכם להתקין דרייבר מסד נתונים מתאים.
+אם קמפלתם את PHP בעצמכם, הגדירו אותו מחדש והפעילו את לקוח מסד נתונים (database client), למשל בעזרת <code>./configure --with-mysql</code>.
+אם התקנתם את PHP מחבילה של דביאן או אובונטו, יש להתקין את המודול php5-mysql.',
+ 'config-no-fts3' => "'''אזהרה''': SQLite מקומפל ללא [http://sqlite.org/fts3.html מודול FTS]. יכולות חיפוש לא יהיו זמינות בהתקנה הזאת.",
+ 'config-register-globals' => "'''אזהרה: האפשרות <code>[http://php.net/register_globals register_globals]</code> של PHP מופעלת.'''
+'''כבו אותה אם אתם יכולים.'''
+מדיה־ויקי תעבוד, אבל השרת שלכם חשוף לפגיעות אבטחה.",
+ 'config-magic-quotes-runtime' => "'''שגיאה סופנית: האפשרות [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] פעילה!'''
+האפשרות הזאת מעוותת את נתוני הקלט באופן בלתי־צפוי.
+לא ניתן להתקין את מדיה־ויקי אלא אם האפשרות הזאת תכובה.",
+ 'config-magic-quotes-sybase' => "'''שגיאה סופנית''': האפשרות [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] פעילה!'''
+האפשרות הזאת מעוותת את נתוני הקלט באופן בלתי־צפוי.
+לא ניתן להתקין את מדיה־ויקי או להשתמש בה אלא אם האפשרות הזאת תכובה.",
+ 'config-mbstring' => "'''שגיאה סופנית''': האפשרות [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] פעילה!'''
+האפשרות הזאת גורמת לשגיאות ומעוותת את נתוני הקלט באופן בלתי־צפוי.
+לא ניתן להתקין את מדיה־ויקי או להשתמש בה אלא אם האפשרות הזאת תכובה.",
+ 'config-ze1' => "'''שגיאה סופנית''': האפשרות [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] פעילה!'''
+האפשרות הזאת גורמת לתקלות מזעזעות במדיה־ויקי.
+לא ניתן להתקין את מדיה־ויקי או להשתמש בה אלא אם האפשרות הזאת תכובה.",
+ 'config-safe-mode' => "'''אזהרה:''' האפשרות [http://www.php.net/features.safe-mode safe mode] של PHP פעילה.
+היא יכולה לגרום לבעיות, במיוחד אם אתם משתמשים בהעלאת קבצים או ב־<code>math</code>.",
+ 'config-xml-bad' => 'מודול XML של PHP חסר.
+מדיה־ויקי דורשת פונקציות של המודול ולא תעבוד עם הגדרות כאלו.
+אם מערכת ההפעלה שלהם היא Mandrake, התקינו את החבילה php-xml.',
+ 'config-pcre' => 'נראה שחסרה תמיכה במודול PCRE.
+כדי שמדיה־ויקי תעבוד, נדרשת תמיכה בביטויים רגולריים תואמי Perl.',
+ 'config-pcre-no-utf8' => "'''שגיאה סופנית:''': נראה שמודול PCRE של PHP מקומפל ללא תמיכה ב־PCRE_UTF8.
+מדיה־ויקי דורשת תמיכה ב־UTF-8 לפעילות נכונה.",
+ 'config-memory-raised' => 'ערך האפשרות <code>memory_limit</code> של PHP הוא $1, הועלה ל־$2.',
+ 'config-memory-bad' => "'''אזהרה:''' ערך האפשרות <code>memory_limit</code> של PHP הוא $1.
+זה כנראה נמוך מדי.
+ההתקנה עשויה להיכשל!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] מותקן',
+ 'config-apc' => '[http://www.php.net/apc APC] מותקן',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] מותקן',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] מותקן',
+ 'config-no-cache' => "'''אזהרה:''' אחת מהתוכנות הבאות לא נמצאה: [http://eaccelerator.sourceforge.net eAccelerator]&rlm;, [http://www.php.net/apc APC]&rlm;, [http://trac.lighttpd.net/xcache/ XCache] או [http://www.iis.net/download/WinCacheForPhp WinCache].
+מטמון עצמים לא מופעל.",
+ 'config-diff3-bad' => 'GNU diff3 לא נמצא.',
+ 'config-imagemagick' => 'נמצא ImageMagick&rlm;: <code>$1</code>.
+מזעור תמונות יופעל, אם תפעילו את האפשרות להעלות קבצים.',
+ 'config-gd' => 'נמצאה ספריית הגרפיקה GD המובנית.
+מזעור תמונות יופעל, אם תפעילו את האפשרות להעלות קבצים.',
+ 'config-no-scaling' => 'ספריית GD או ImageMagick לא נמצאו.
+מזעור תמונות לא יופעל.',
+ 'config-no-uri' => "'''שגיאה:''' אי־אפשר לזהות את הכתובת הנוכחית.
+ההתקנה בוטלה.",
+ 'config-uploads-not-safe' => "'''אזהרה:''' התיקייה ההתחלתית להעלות <code>$1</code> חשופה להרצת סקריפטים.
+מדיה־ויקי בודקת את כל הקבצים המוּעלים לאיומי אבטחה, מומלץ מאוד למנוע את [http://www.mediawiki.org/wiki/Manual:Security#Upload_security פרצת האבטחה] הזאת לפני שאתם מפעילים את ההעלאות.",
+ 'config-brokenlibxml' => 'במערכת שלכם יש שילוב של גרסאות של PHP ושל libxml2 שחשוף לבאגים ויכול לגרום לעיוות נתונים נסתר במדיה־ויקי וביישומי רשת אחרים.
+שדרגו ל־PHP 5.2.9 או לגרסה חדשה יותר ול־libxml2 2.7.3 או גרסה חדשה יותר ([http://bugs.php.net/bug.php?id=45996 באג מתויק ב־PHP]).
+ההתקנה בוטלה.',
+ 'config-using531' => 'אי־אפשר להשתמש במדיה־ויקי עם PHP $1 בגלל באג בפרמטרים של הפניות (reference parameters) ל־<span dir="ltr"><code>__call()</code></span>.
+שדרגו ל־PHP 5.3.2 או לגרסה גבוהה יותר כדי לתקן את זה ([http://bugs.php.net/bug.php?id=50394 bug filed with PHP]) או שנמכו ל־PHP 5.3.0 כדי לפתור את הבעיה הזאת.
+ההתקנה בוטלה.',
+ 'config-db-type' => 'סוג מסד הנתונים:',
+ 'config-db-host' => 'שרת מסד הנתונים:',
+ 'config-db-host-help' => 'אם שרת מסד הנתונים שלכם נמצא על שרת מחשב אחר, הקלידו את שם המחשב או כתובת ה־IP כאן.
+
+אם אתם משתמשים באירוח משותף, ספק האירוח שלכם אמור לתת לכם את שם השרת הנכון במסמכים.
+
+אם אתם מתקינים בשרת חלונות ומשתמשים ב־MySQL, השימוש ב־localhost עשוי לא לעבוד. אם הוא לא עובד, נסו את "127.0.0.1" בתור כתובת ה־IP המקומית.',
+ 'config-db-host-oracle' => 'TNS של מסד הנתונים:',
+ 'config-db-host-oracle-help' => 'הקלידו [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm שם חיבור מקומי (Local Connect Name)] תקין; הקובץ tnsnames.ora צריך להיות זמין להתקנה הזאת.<br />
+אם אתם משתמשים ב־client libraries 10g או בגרסה חדשה יותר, אתם יכולים גם להשתמש בשיטת מתן השמות [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'זיהוי ויקי זה',
+ 'config-db-name' => 'שם מסד הנתונים:',
+ 'config-db-name-help' => 'בחרו שם שמזהה את הוויקי שלכם.
+לא צריכים להיות בו רווחים.
+
+אם אתם משתמשים באירוח משותף, ספק האירוח שלכם ייתן לכם שם מסד נתונים מסוים שתוכלו להשתמש בו או יאפשר לכם ליצור מסד נתונים דרך לוח בקרה.',
+ 'config-db-name-oracle' => 'סכמה של מסד נתונים:',
+ 'config-db-account-oracle-warn' => 'קיימים שלושה תרחישים נתמכים עבור התקנת אורקל בתור מסד הנתונים:
+
+אם אתם רוצים ליצור חשבון מסד נתונים כחלק מתהליך ההתקנה, נא לספק חשבון בעל תפקיד SYSDBA בתור חשבון מסד הנתונים עבור ההתקנה ולציין את האישורים המבוקשים עבור חשבון הגישה לאינטרנט, אחרת ניתן ליצור באופן ידני את חשבון הגישה לאינטרנט, ולספק חשבון זה בלבד (אם יש לו ההרשאות הדרושות ליצירת עצמי סכמה) או לספק שני חשבונות שונים, אחד עם הרשאות יצירה ואחד מוגבלת עבור גישה לאינטרנט.
+
+סקריפט ליצירת חשבון עם ההרשאות הנדרשות ניתן למצוא בתיקייה "<span dir="ltr">maintenance/oracle/</span>" של ההתקנה זו. זכרו כי שימוש בחשבון מוגבל יגרום להשבתת כל יכולות תחזוקה עם חשבון בררת המחדל.',
+ 'config-db-install-account' => 'חשבון משתמש להתקנה',
+ 'config-db-username' => 'שם המשתמש במסד הנתונים:',
+ 'config-db-password' => 'הססמה במסד הנתונים:',
+ 'config-db-password-empty' => 'נא להזין ססמה למשתמש מסד הנתונים החדש: $1.
+אף־על־פי שאפשר ליצור חשבונות ללא ססמה, זה לא מאובטח.',
+ 'config-db-install-username' => 'הכניסו שם משתמש שישמש אתכם לחיבור למסד נתונים במהלך ההתקנה.
+זהו לא שם משתמש לחשבון במדיה־ויקי; זהו שם משתמש בשרת מסד נתונים.',
+ 'config-db-install-password' => 'הקלידו ססמה שתשמש אתכם לצורך חיבור למסד נתונים במהלך ההתקנה.
+זוהי לא ססמה של חשבון במדיה־ויקי; זוהי ססמה לשרת מסד נתונים.',
+ 'config-db-install-help' => 'הקלידו את שם המשתמש ואת הססמה להתחברות למסד הנתונים במהלך ההתקנה.',
+ 'config-db-account-lock' => 'להשתמש באותו שם המשתמש ובאותה ססמה בזמן הפעלה רגילה',
+ 'config-db-wiki-account' => 'חשבון משתמש להפעלה רגילה',
+ 'config-db-wiki-help' => 'הקלידו את שם המשתמש והססמה לחיבור למסד הנתונים במהלך פעילות רגילה של הוויקי.
+אם החשבון אינו קיים ולחשבון שבו מתבצעת ההתקנה יש הרשאות מספיקות, החשבון הזה ייווצר עם ההרשאות המזעריות הנחוצות להפעלת הוויקי.',
+ 'config-db-prefix' => 'תחילית לטבלאות של מסד נתונים (database table prefix):',
+ 'config-db-prefix-help' => 'אם אתם צריכים לשתף מסד נתונים אחד בין אתרי ויקי שונים או בין מדיה־ויקי ויישום וב אחר, תוכלו לבחור להוסיף תחילית וכל שמות הטבלאות כדי להימנע מהתנגשויות.
+אל תשתמשו ברווחים.
+
+השדה הזה בדרך כלל אמור להיות ריק.',
+ 'config-db-charset' => 'קבוצת התווים (character set) של מסד הנתונים',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 backwards-compatible UTF-8',
+ 'config-charset-help' => "'''אזהרה:''' אם אתם משתמשים ב־'''backwards-compatible UTF-8''' ב־<span dir=\"ltr\">MySQL 4.1+</span>, ומגבים את מסד הנתונים באמצעות <code>mysqldump</code>, זה יכול להרוס את כל תווי ה־ASCII ויהרוס באופן בלתי־הפיך את הגיבויים שלכם!
+
+ב'''מצב בינרי''' (binary mode) מדיה־ויקי שומרת טקסט UTF-8 במסד הנתונים בשדות בינריים.
+זה יעיל יותר ממצב UTF-8 של MySQL ומאפשר לכם להשתמש בכל הטווח של תווי יוניקוד.
+ב'''מצב UTF-8'''&rlm; (UTF-8 mode)&rlm; MySQL יֵדַע מה קבוצת התווים (character set) של הטקסט שלכם ויציג וימיר אותו בהתאם, אבל לא יאפשר לכם לשמור תווים שאינם נמצאים בטווח הרב־לשוני הבסיסי ([http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane]).",
+ 'config-mysql-old' => 'נדרשת גרסה $1 של MySQL או גרסה חדשה יותר. הגרסה הנוכחית שלכם היא $2.',
+ 'config-db-port' => 'פִּתְחַת מסד הנתונים (database port):',
+ 'config-db-schema' => 'סכמה למדיה־ויקי',
+ 'config-db-schema-help' => 'הסְכֵמָה הבאה בדרך כלל מתאימה.
+שנו אותה רק אם אתם יודעים שאתם חייבים.',
+ 'config-sqlite-dir' => 'תיקיית נתונים (data directory) של SQLite:',
+ 'config-sqlite-dir-help' => 'SQLite שומר את כל הנתונים בקובץ אחד.
+
+לשרת הווב צריכה להיות הרשאה לכתוב לתיקייה שאתם מגדירים.
+
+היא לא צריכה נגישה לכולם דרך האינטרנט – בגלל זה איננו שמים אותה באותו מקום עם קובצי ה־PHP.
+
+תוכנת ההתקנה תכתוב קובץ <span dir="ltr"><code>.htaccess</code></span> יחד אִתו, אבל אם זה ייכשל, מישהו יוכל להשיג גישה למסד הנתונים שלכם. שם נמצא מידע מפורש של משתמשים (כתובות דוא״ל, ססמאות מגובבות) וגם גרסאות מחוקות של דפים ומידע מוגבל אחר.
+
+כדאי לשקול לשים את מסד הנתונים במקום אחר לגמרי, למשל ב־<span dir="ltr"><code>/var/lib/mediawiki/yourwik</code></span>.',
+ 'config-oracle-def-ts' => 'מרחב טבלאות לפי בררת מחדל (default tablespace):',
+ 'config-oracle-temp-ts' => 'מרחב טבלאות זמני (temporary tablespace):',
+ 'config-support-info' => 'מדיה־ויקי תומכת במערכות מסדי הנתונים הבאות:
+
+$1
+
+אם אינכם רואים את מסד הנתונים שלכם ברשימה, עקבו אחר ההוראות המקושרות לעיל כדי להפעיל את התמיכה.',
+ 'config-support-mysql' => '* $1 הוא היעד העיקרי עבור מדיה־ויקי ולו התמיכה הטובה ביותר (ר׳ [http://www.php.net/manual/en/mysql.installation.php how to compile PHP with MySQL support])',
+ 'config-support-postgres' => '$1 הוא מסד נתונים נפוץ בקוד פתוח והוא נפוץ בתור חלופה ל־MySQL (ר׳ [http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]). ייתכן שיש בתצורה הזאת באגים מסוימים והיא לא מומלצת לסביבות מבצעיות.',
+ 'config-support-sqlite' => '* $1 הוא מסד נתונים קליל עם תמיכה טובה מאוד. (ר׳ [http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], משתמש ב־PDO)',
+ 'config-support-oracle' => '* $1 הוא מסד נתונים עסקי מסחרי. (ר׳ [http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
+ 'config-header-mysql' => 'הגדרות MySQL',
+ 'config-header-postgres' => 'הגדרות PostgreSQL',
+ 'config-header-sqlite' => 'הגדרות SQLite',
+ 'config-header-oracle' => 'הגדרות Oracle',
+ 'config-invalid-db-type' => 'סוג מסד הנתונים שגוי',
+ 'config-missing-db-name' => 'עליך להזין ערך עבור "שם מסד הנתונים"',
+ 'config-missing-db-host' => 'יש להכניס ערך לשדה "שרת מסד הנתונים"',
+ 'config-missing-db-server-oracle' => 'יש להכניס ערך לשדה "TNS של מסד הנתונים"',
+ 'config-invalid-db-server-oracle' => '"$1" הוא TNS בלתי תקין.
+יש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9), קווים תחתיים (_) ונקודות (.).',
+ 'config-invalid-db-name' => '"$1" הוא שם מסד נתונים בלתי תקין.
+יש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9), קווים תחתיים (_) ומינוסים (-).',
+ 'config-invalid-db-prefix' => '"$1" היא תחילית מסד נתונים בלתי תקינה.
+יש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9), קווים תחתיים (_) ומינוסים (-).',
+ 'config-connection-error' => '$1.
+
+בדקו את שם השרת, את שם המשתמש ואת הססמה ונסו שוב.',
+ 'config-invalid-schema' => '"$1" היא סכמה לא תקינה עבור מדיה־ויקי.
+יש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9) וקווים תחתיים (_).',
+ 'config-db-sys-create-oracle' => 'תוכנית ההתקנה תומכת רק בשימוש בחשבון SYSDBA ליצירת חשבון חדש.',
+ 'config-db-sys-user-exists-oracle' => 'חשבון המשתמש "$1" כבר קיים. SYSDBA יכול לשמש רק ליצירת חשבון חדש!',
+ 'config-postgres-old' => 'נדרש PostgreSQL $1 או גרסה חדשה יותר, הגרסה הנוכחית שלכם היא $2.',
+ 'config-sqlite-name-help' => 'בחרו בשם שמזהה את הוויקי שלכם.
+אל תשתמשו ברווחים או במינוסים.
+זה יהיה שם קובץ הנתונים ל־SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'לא ניתן ליצור את תיקיית הנתונים <code><nowiki>$1</nowiki></code>, כי לשָׁרַת הווב אין הרשאות לכתוב לתיקיית האם <code><nowiki>$2</nowiki></code> .
+
+תוכנת ההתקנה זיהתה את החשבון שתחתיו רץ שרת הווב שלכם.
+אפשרו לשָׁרַת הווב לכתוב לתיקייה <code><nowiki>$3</nowiki></code>.
+במערכת Unix/Linux כִתבו:
+
+<div dir="ltr"><pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre></div>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'לא ניתן ליצור את תיקיית הנתונים <code><nowiki>$1</nowiki></code>, כי לשָׁרַת הווב אין הרשאות לכתוב לתיקיית האם <code><nowiki>$2</nowiki></code> .
+
+תוכנת ההתקנה לא זיהתה את החשבון שתחתיו רץ שרת הווב שלכם.
+אפשרו לכל החשבונות לכתוב לתיקייה <code><nowiki>$3</nowiki></code> כדי להמשיך.
+במערכת Unix/Linux כִתבו:
+
+<div dir="ltr"><pre>cd $2
+mkdir $3
+chmod a+w $3</pre></div>',
+ 'config-sqlite-mkdir-error' => 'אירעה שגיאה בעת יצירת תיקיית הנתונים "$1".
+נא לבדוק את המיקום ולנסות שוב.',
+ 'config-sqlite-dir-unwritable' => 'אי־אפשר לכתוב לתיקייה "$1".
+שנו את ההרשאות שלה כך ששרת הווב יוכל לכתוב אליה ונסו שוב.',
+ 'config-sqlite-connection-error' => '$1.
+
+בִדקו את תיקיית הנתונים את שם מסת הנתונים להלן ונסו שוב.',
+ 'config-sqlite-readonly' => 'לא ניתן לכתוב אל הקובץ <code>$1</code>.',
+ 'config-sqlite-cant-create-db' => 'לא ניתן ליצור את קובץ מסד הנתונים <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'ב־PHP חסרה תמיכה ב־FTS3, יבתצע שנמוך טבלאות',
+ 'config-can-upgrade' => "יש טבלאות מדיה־ויקי במסד הנתונים.
+כדי לשדרג אותן למדיה־ויקי $1, לחצו '''המשך'''.",
+ 'config-upgrade-done' => "השדרוג הושלם.
+
+עכשיו אפשר [$1 להשתמש בוויקי שלכם].
+
+אם תרצו ליצור מחדש את קובץ ה־<code>LocalSettings.php</code> שלכם, לחצו על הכפתור להלן.
+זה '''לא מומלץ''', אלא אם כן יש לכם בעיות עם הוויקי שלכם.",
+ 'config-upgrade-done-no-regenerate' => 'השדרוג הושלם.
+
+עכשיו אפשר [$1 להתחיל להשתמש בוויקי שלכם].',
+ 'config-regenerate' => 'לחולל מחדש את LocalSettings.php ←',
+ 'config-show-table-status' => 'שאילתת SHOW TABLE STATUS נכשלה!',
+ 'config-unknown-collation' => "'''אזהרה:''' מסד הנתונים משתמש בשיטת מיון שאינה מוּכּרת.",
+ 'config-db-web-account' => 'חשבון במסד הנתונים לגישה מהרשת',
+ 'config-db-web-help' => 'לבחור את שם המשתמש ואת הססמה ששרת הווב ישתמש בו להתחברות לשרת מסד הנתונים בזמן פעילות רגילה של הוויקי.',
+ 'config-db-web-account-same' => 'להשתמש באותו חשבון כמו עבור ההתקנה',
+ 'config-db-web-create' => 'ליצור חשבון אם הוא אינו קיים כבר.',
+ 'config-db-web-no-create-privs' => 'לחשבון שהקלדתם להתקנה אין מספיק הרשאות ליצירת חשבות.
+החשבון שאתם מקלידים כאן צריך להיות קיים.',
+ 'config-mysql-engine' => 'מנגנון האחסון:',
+ 'config-mysql-engine-help' => "'''InnoDB''' הוא כמעט תמיד האפשרות הטובה ביותר, כי במנוע הזה יש תמיכה טובה ביותר בעיבוד מקבילי.
+
+'''MyISAM''' עשוי להיות בהתקנות שמיועדות למשתמש אחד ולהתקנות לקריאה בלבד.
+מסדי נתונים עם MyISAM נוטים להיהרס לעתים קרובות יותר מאשר מסדי נתונים עם InnoDB.",
+ 'config-mysql-charset' => 'ערכת הקידוד של מסד הנתונים:',
+ 'config-mysql-binary' => 'בינרי',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "ב'''מצב בינרי''' (binary mode) מדיה־ויקי שומרת טקסט UTF-8 במסד הנתונים בשדות בינריים.
+זה יעיל יותר ממצב UTF-8 של MySQL ומאפשר לכם להשתמש בכל הטווח של תווי יוניקוד.
+
+ב'''מצב UTF-8'''&rlm; (UTF-8 mode)&rlm; MySQL יֵדַע מה קבוצת התווים (character set) של הטקסט שלכם ויציג וימיר אותו בהתאם, אבל לא יאפשר לכם לשמור תווים שאינם נמצאים בטווח הרב־לשוני הבסיסי ([http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane]).",
+ 'config-site-name' => 'שם הוויקי:',
+ 'config-site-name-help' => 'זה יופיע בשורת הכותרת של הדפדפן ובמקומות רבים אחרים.',
+ 'config-site-name-blank' => 'נא להזין שם לאתר.',
+ 'config-project-namespace' => 'מרחב שמות לדפי מיזם:',
+ 'config-ns-generic' => 'מיזם',
+ 'config-ns-site-name' => 'זהה לשם הוויקי: $1',
+ 'config-ns-other' => 'אחר (לציין)',
+ 'config-ns-other-default' => 'הוויקי־שלי',
+ 'config-project-namespace-help' => "בהתאם לדוגמה של ויקיפדיה, אתרי ויקי רבים שומרים על דפי המדיניות שלהם בנפרד מדפי התוכן שלהם ב\"'''מרחב השמות של המיזם'''\" (\"'''project namespace'''\").
+כל שמות הדפים במרחב השמות הזה נפתחים בתחילית מסוימת שאתם יכולים להגדיר כאן.
+באופן מסורתי התחילית הזאת מבוססת על שם הוויקי, אבל אינו יכול להכיל תווי פיסוק כגון \"#\" או \":\".",
+ 'config-ns-invalid' => 'מרחב השמות "<nowiki>$1</nowiki>" אינו תקין.
+הקלידו שם אחר למרחב השמות של המיזם.',
+ 'config-ns-conflict' => 'מרחב השמות שהגדרתם "<nowiki>$1</nowiki>" מתנגש עם מרחב שמות מובנה של מדיה־ויקי.
+הגדירו מרחב שמות מיזם שונה.',
+ 'config-admin-box' => 'חשבון מפעיל',
+ 'config-admin-name' => 'שמכם:',
+ 'config-admin-password' => 'ססמה:',
+ 'config-admin-password-confirm' => 'הססמה שוב:',
+ 'config-admin-help' => 'הקלידו כאן את שם המשתמש, למשל "שקד לוי" או "Joe Bloggs".
+זה השם שישמש אתכם כדי להיכנס לוויקי.',
+ 'config-admin-name-blank' => 'נא להזין את שם המשתמש של המפעיל.',
+ 'config-admin-name-invalid' => 'שם המשתמש שהוקלד "<nowiki>$1</nowiki>" אינו תקין.
+הקלידו שם משתמש אחר.',
+ 'config-admin-password-blank' => 'הקלידו ססמה לחשבון המפעיל.',
+ 'config-admin-password-same' => 'הססמה לא יכולה להיות זהה לשם המשתמש.',
+ 'config-admin-password-mismatch' => 'שתי הססמאות שהוזנו אינן מתאימות.',
+ 'config-admin-email' => 'כתובת הדוא״ל:',
+ 'config-admin-email-help' => 'הקלידו כתובת דוא״ל שתאפשר לכם לקבל מכתבים ממשתמשים אחרים בוויקי, לאתחל את הססמה, ולקבל הודעות על שינויים בדפים ברשימת המעקב שלכם. אפשר להשאיר את השדה הזה ריק.',
+ 'config-admin-error-user' => 'שגיאה פנימית ביצירת מפעיל בשם "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'שגיאה פנימית בהגדרת ססמה עבור המפעיל "<nowiki>$1</nowiki>"&rlm;: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'הכנסתם כתובת דוא״ל לא תקינה.',
+ 'config-subscribe' => 'להירשם ל[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה עם הודעות על גרסאות חדשות].',
+ 'config-subscribe-help' => 'זוהי רשימת תפוצה עם הודעות מעטות שמשמשת להודעות על הוצאת גרסאות, כולל עדכוני אבטחה חשובים.
+מומלץ להירשם אליה ולעדכן את מדיה־ויקי כאשר יוצאות גרסאות חדשות.',
+ 'config-almost-done' => 'כמעט סיימתם!
+אפשר לדלג על שאר ההגדרות ולהתקין את הוויקי כבר עכשיו.',
+ 'config-optional-continue' => 'הצגת שאלות נוספות.',
+ 'config-optional-skip' => 'משעמם לי, תתקינו לי כבר את הוויקי הזה.',
+ 'config-profile' => 'תסריט הרשאות משתמשים:',
+ 'config-profile-wiki' => 'ויקי מסורתי',
+ 'config-profile-no-anon' => 'נדרשת יצירת חשבון',
+ 'config-profile-fishbowl' => 'עורכים מורשים בלבד',
+ 'config-profile-private' => 'ויקי פרטי',
+ 'config-profile-help' => "אתרי ויקי עובדים הכי טוב כאשר אתם מאפשרים לכמה שיותר אנשים לערוך אותם.
+במדיה־ויקי קל לסקור את השינויים האחרונים ולשחזר כל נזק שעושים משתמשים תמימים או משחיתים.
+
+עם זאת, אנשים שונים מצאו למדיה־ויקי שימושים מגוּונים ולעתים לא קל לשכנע את כולם ביתרונות של \"דרך הוויקי\" המסורתית. ולכן יש לכם בררה.
+
+באתר '''{{int:config-profile-wiki}}''' – לכולם יש הרשאה לערוך, אפילו בלי להיכנס לחשבון.
+באתר וויקי מסוג '''{{int:config-profile-no-anon}}''' יש ביטחון גדול יותר, אבל הגדרה כזאת יכולה להרתיע תורמים מזדמנים.
+
+בתסריט '''{{int:config-profile-fishbowl}}''' רק משתמשים שקיבלו אישור יכולים לערוך, אבל כל הגולשים יכולים לקרוא את הדפים ואת גרסאותיהם הקודמות.
+ב'''{{int:config-profile-private}}''' רק משתמשים שקיבלו אישור יכולים לקרוא ולערוך דפים.
+
+הגדרות מורכבות של הרשאות אפשריות אחרי ההתקנה, ר׳ את [http://www.mediawiki.org/wiki/Manual:User_rights הפרק על הנושא הזה בספר ההדרכה].",
+ 'config-license' => 'זכויות יוצרים ורישיון:',
+ 'config-license-none' => 'ללא כותרת תחתית עם רישיון',
+ 'config-license-cc-by-sa' => 'קריאייטיב קומונז–ייחוס–שיתוף זהה',
+ 'config-license-cc-by-nc-sa' => 'קריאייטיב קומונז ייחוס–ללא שימוש מסחרי–שיתוף זהה',
+ 'config-license-cc-0' => 'Creative Commons אפס',
+ 'config-license-gfdl-old' => 'רישיון חופשי למסמכים של גנו, גרסה 1.2',
+ 'config-license-gfdl-current' => 'רישיון חופשי למסמכים של גנו, גרסה 1.3 או גרסה מאוחרת יותר',
+ 'config-license-pd' => 'נחלת הכלל',
+ 'config-license-cc-choose' => 'בחרו רישיון קריאייטיב קומונז מותאם אישית',
+ 'config-license-help' => "אתרי ויקי ציבוריים רבים מפרסמים את כל התרומות תחת [http://freedomdefined.org/Definition רישיון חופשי].
+זה עוזר ליצור תחושה של בעלות קהילתית ומעודד תרומה לאורך זמן.
+זה בדרך כלל לא נחוץ לאתר ויקי פרטי או בחברה מסחרית.
+
+אם אתם רוצים אפשרות להשתמש בטקסט מוויקיפדיה ואתם רוצים שוויקיפדיה תוכל לקבל עותקים של טקסטים מהוויקי שלכם, כדאי לכם לבחור ב'''רישיון קריאייטיב קומונז ייחוס–שיתוף זהה''' (CC-BY-SA).
+
+הרישיון החופשי למסמכים של גנו הוא הרישיון שבו ויקיפדיה השתמשה בעבר (GNU FDL או GFDL).
+הוא עדיין תקין, אבל יש בו תכונות מסוימות שמקשות על שימוש חוזר ועל פרשנות.",
+ 'config-email-settings' => 'הגדרות דוא״ל',
+ 'config-enable-email' => 'להפעיל דוא״ל יוצא',
+ 'config-enable-email-help' => 'אם אתם רוצים שדוא״ל יעבוד, [http://www.php.net/manual/en/mail.configuration.php אפשרויות הדוא״ל של PHP] צריכות להיות מוגדרות נכון.
+אם אינכם רוצים להפעיל שום אפשרויות דוא״ל, כבו אותן כאן ועכשיו.',
+ 'config-email-user' => 'לאפשר שליחת דוא״ל ממשתמש למשתמש',
+ 'config-email-user-help' => 'לאפשר לכל המשתמשים לשלוח אחד לשני דוא״ל אם הם הפעילו את זה בהעדפות שלהם.',
+ 'config-email-usertalk' => 'לאפשר הודעות על דף שיחת משתמש',
+ 'config-email-usertalk-help' => 'לאפשר למשתמשים לקבל הודעות על שינויים בדפי המשתמש שלהם, אם הם הפעילו את זה בהעדפות שלהם.',
+ 'config-email-watchlist' => 'הפעלת התרעה על רשימת המעקב',
+ 'config-email-watchlist-help' => 'לאפשר למשתמשים לקבל הודעות על הדפים ברשימת המעקב שלהם אם הם הפעילו את זה בהעדפות שלהם.',
+ 'config-email-auth' => 'הפעלת התרעה בדוא״ל',
+ 'config-email-auth-help' => "אם האפשרות הזאת מופעלת, משתמשים יצטרכו לאשר את כתובת הדוא״ל שלהם באמצעות קישור שיישלח אליהם בכל פעם שהם יגדירו או ישנו אותה.
+רק כתובות דוא״ל מאושרות יכולות לקבלת דוא״ל ממשתמשים אחרים או מכתבים עם הודעות על שינויים.
+'''מומלץ''' להגדיר את האפשרות הזאת לאתרי ויקי ציבוריים כי אפשר לעשות שימוש לרעה בתכונות הדוא״ל.",
+ 'config-email-sender' => 'כתובת דוא״ל לתשובות:',
+ 'config-email-sender-help' => 'הכניסו את כתובת הדוא״ל שתשמש ככתובת לתשובה לכל הדואר היוצא.
+לשם יישלחו תגובות שגיאה (bounce).
+שרתי דוא״ל רבים דורשים שלפחות החלק של המתחם יהיה תקין.',
+ 'config-upload-settings' => 'העלאת קבצים ותמונות',
+ 'config-upload-enable' => 'אפשור העלאת קבצים',
+ 'config-upload-help' => 'העלאות קבצים חושפות את השרת שלכם לסיכוני אבטחה.
+למידע נוסף, קִראו את [http://www.mediawiki.org/wiki/Manual:Security חלק האבטחה] בספר ההדרכה.
+
+כדי להפעיל העלאת קבצים שנו את ההרשאות של התיקייה <code>images</code> תחת תיקיית השורש של מדיה־ויקי כך ששרת הווב יוכל לכתוב אליה.
+זה מפעיל את האפשרות הזאת.',
+ 'config-upload-deleted' => 'תיקיית הקבצים שנמחקו:',
+ 'config-upload-deleted-help' => 'בחרו את התיקייה לארכוב קבצים מחוקים.
+כדאי שזה לא יהיה נגיש לכל העולם דרך הרשת.',
+ 'config-logo' => 'כתובת הסמל:',
+ 'config-logo-help' => 'המראה ההתחלתי של מדיה־ויקי מכיל מקום לסמל של 135 על 160 פיקסלים בפינה השמאלית העליונה (ימנית עבור שפות שנכתבות מימין לשמאל).
+יש להעלות תמונה בגודל מתאים ולהכניס את הכתובת כאן.
+
+אם אינכם רוצים סמל, השאירו את התיבה הזאת ריקה.',
+ 'config-instantcommons' => 'להפעיל את Instant Commons',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] היא תכונה שמאפשרת לאתרי ויקי להשתמש בתמונות, בצלילים ובמדיה אחרת שנמצאת באתר [http://commons.wikimedia.org/ ויקישיתוף] (Wikimedia Commons).
+כדי לעשות את זה, מדיה־ויקי צריך לגשת לאינטרנט.
+
+למידע נוסף על התכונה הזאת, כולל הוראות איך להפעיל את זה לאתרי ויקי שאינם ויקישיתוף, ר׳ [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos את ספר ההדרכה].',
+ 'config-cc-error' => 'בורר רישיונות קריאייטיב קומונז לא החזיר שום תוצאה.
+הקלידו את שם הרישיון ידנית.',
+ 'config-cc-again' => 'נא לבחור שוב...',
+ 'config-cc-not-chosen' => 'בחרו באיזה רישיון קריאייטיב קומונז להשתמש ולחצו "המשך".',
+ 'config-advanced-settings' => 'הגדרות מתקדמות',
+ 'config-cache-options' => 'הגדרות למטמון עצמים (object caching):',
+ 'config-cache-help' => 'מטמון עצמים משמש לשיפור המהירות של מדיה־ויקי על־ידי שמירה של נתונים שהשימוש בהם נפוץ במטמון.
+לאתרים בינוניים וגדולים כדאי מאוד להפעיל את זה, וגם אתרים קטנים ייהנו מזה.',
+ 'config-cache-none' => 'ללא מטמון (שום יכולת אינה מוסרת, אבל הביצועים באתרים גדולים ייפגעו)',
+ 'config-cache-accel' => 'מטמון עצמים (object caching) של PHP&rlm; (APC&rlm;, eAccelerator&rlm;, XCache או WinCache)',
+ 'config-cache-memcached' => 'להשתמש ב־Memcached (דורש התקנות והגדרות נוספות)',
+ 'config-memcached-servers' => 'שרתי Memcached:',
+ 'config-memcached-help' => 'רשימת כתובות IP ש־Memcached ישתמש בהן.
+יש לרשום כתובת אחת בכל שורה ולציין את הפִּתְחָה (port), למשל:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'בחרת ב־Memcached בתתור סוג המטמון שלכם, אבל לא הגדרתם שום שרת.',
+ 'config-memcache-badip' => 'הקלדתם כתובת IP בלתי תקינה ל־Memcached&lrm;: $1.',
+ 'config-memcache-noport' => 'לא הגדרתם פתחה לשימוש שרת Memcached&rlm;: $1.
+אם אינכם יודעים את מספר הפתחה, בררת המחדל היא 11211.',
+ 'config-memcache-badport' => 'מספרי פתחה של Memcached צריכים להיות בין $1 ל־$2',
+ 'config-extensions' => 'הרחבות',
+ 'config-extensions-help' => 'ההרחבות ברשימה לעיל התגלו בתיקיית <span dir="ltr"><code>./extensions</code></span> שלכם.
+
+ייתכן שזה ידרוש הגדרות נוספות, אבל תוכלו להפעיל אותן עכשיו.',
+ 'config-install-alreadydone' => "'''אזהרה:''' נראה שכבר התקנתם את מדיה־ויקי ואתם מנסים להתקין אותה שוב.
+אנה התקדמו לדף הבא.",
+ 'config-install-begin' => 'כשתלחצו על "{{int:config-continue}}", תתחילו את ההתקנה של מדיה־ויקי.
+אם אתם עדיין רוצים לשנות משהו, לחצו על "הקודם".',
+ 'config-install-step-done' => 'בוצע',
+ 'config-install-step-failed' => 'נכשל',
+ 'config-install-extensions' => 'כולל הרחבות',
+ 'config-install-database' => 'הקמת מסד נתונים',
+ 'config-install-pg-schema-not-exist' => 'סכמה של PostgreSQL אינה קיימת',
+ 'config-install-pg-schema-failed' => 'יצירת טבלאות נכשלה.
+ודאו כי המשתמש "$1" יכול לכתוב לסכמה "$2".',
+ 'config-install-pg-commit' => 'שמירת שינויים',
+ 'config-install-pg-plpgsql' => 'בדיקת שפת PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'צריך להתקין את שפת PL/pgSQL במסד הנתונים $1',
+ 'config-pg-no-create-privs' => 'לחשבון שהגדרתם להתקנה אין מספיק הרשאות ליצירת חשבון.',
+ 'config-install-user' => 'יצירת חשבון במסד נתונים',
+ 'config-install-user-alreadyexists' => 'המשתמש "$1" כבר קיים',
+ 'config-install-user-create-failed' => 'יצירת משתמש "$1" נכשלה: $2',
+ 'config-install-user-grant-failed' => 'מתן הרשאות למשתמש "$1" נכשל: $2',
+ 'config-install-tables' => 'יצירת טבלאות',
+ 'config-install-tables-exist' => "'''אזהרה:''' נראה שטבלאות מדיה־ויקי כבר קיימות.
+מדלג על יצירתן.",
+ 'config-install-tables-failed' => "'''שגיאה:''' יצירת הטבלה נכשלה עם השגיאה הבאה: $1",
+ 'config-install-interwiki' => 'אכלוס טבלת בינוויקי התחלתית',
+ 'config-install-interwiki-list' => 'קריאת הקובץ <code>interwiki.list</code> לא הצליחה.',
+ 'config-install-interwiki-exists' => "'''אזהרה:''': נראה שבטבלת הבינוויקי כבר יש רשומות.
+מדלג על הרשומה ההתחלתית.",
+ 'config-install-stats' => 'אתחול סטטיסטיקות',
+ 'config-install-keys' => 'יצירת מפתחות סודיים',
+ 'config-install-sysop' => 'יצירת חשבון מפעיל',
+ 'config-install-subscribe-fail' => 'הרישום ל־mediawiki-announce לא הצליח',
+ 'config-install-mainpage' => 'יצירת דף ראשי עם תוכן לפי בררת מחדל.',
+ 'config-install-extension-tables' => 'יצירת טבלאות להרחבות מופעלות',
+ 'config-install-mainpage-failed' => 'לא הצליחה הכנסת דף ראשי: $1.',
+ 'config-install-done' => "'''מזל טוב!'''
+התקנתם בהצלחה את מדיה־ויקי.
+
+תוכנת ההתקנה יצרה את הקובץ <code>LocalSettings.php</code>.
+הוא מכיל את כל ההגדרות שלכם.
+
+תצטרכו להוריד אותו ולשים אותו בבסיס ההתקנה של הוויקי שלכם (אות התיקייה שבה נמצא הקובץ index.php). ההורדה הייתה אמורה להתחיל באופן אוטומטי.
+
+אם ההורדה לא התחילה, אם אם ביטלתם אותה, אפשר להתחיל אותה מחדש בלחיצה על הקישור הבא:
+
+$3
+
+'''שימו לב''': אם לא תעשו זאת עכשיו, קובץ ההגדרות המחולל לא יהיה זמין לכם שוב.
+
+אחרי שתעשו את זה, תוכלו '''[$2 להיכנס לוויקי שלכם]'''.",
+ 'config-download-localsettings' => 'הורדת LocalSettings.php',
+ 'config-help' => 'עזרה',
+);
+
+/** Upper Sorbian (Hornjoserbsce)
+ * @author Michawiki
+ */
+$messages['hsb'] = array(
+ 'config-desc' => 'Instalaciski program za MediaWiki',
+ 'config-title' => 'Instalacija MediaWiki $1',
+ 'config-information' => 'Informacije',
+ 'config-localsettings-upgrade' => 'Dataja <code>LocalSettings.php</code> je so wotkryła.
+Zo by tutu instalaciju aktualizował, zapodaj prošu hódnotu za parameter <code>$wgUpgradeKey</code> do slědowaceho pola.
+Namakaš tón parameter w dataji LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Dataja LocalSettings.php bu wotkryta.
+Zo by tutu instalaciju aktualizował, wuwjedźće update.php',
+ 'config-localsettings-key' => 'Aktualizaciski kluč:',
+ 'config-localsettings-badkey' => 'Kluč, kotryž sy podał, je wopak',
+ 'config-upgrade-key-missing' => 'Eksistowaca instalacija MediaWiki je so wotkryła.
+Zo by tutu instalaciju aktualizował, staj prošu slědowacu linku deleka w dataji LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'Zda so, zo eksistwoaca dataja LocalSettings.php je njedospołna.
+Wariabla $1 njeje nastajena.
+Prošu změń dataju LocalSettings.php, zo by so tuta wariabla nastajiła a klikń na "Dale".',
+ 'config-localsettings-connection-error' => 'Při zwjazowanju z datowej banku z pomocu nastajenjow podatych w LocalSettings.php abo AdminSettings.php je zmylk wustupił. Prošu skoriguj tute nastajenja a spytaj hišće raz.
+
+$1',
+ 'config-session-error' => 'Zmylk při startowanju posedźenja: $1',
+ 'config-session-expired' => 'Zda so, zo twoje posedźenske daty su spadnjene.
+Posedźenja su za čas žiwjenja $1 skonfigurowane.
+Móžeš jón přez nastajenje <code>session.gc_maxlifetime</code> w php.ini powyšić.
+Startuj instalaciski proces znowa.',
+ 'config-no-session' => 'Twoje posedźenske daty su so zhubili!
+Skontroluj swój php.ini a zawěsć, zo <code>session.save_path</code> je na prawy zapis nastajeny.',
+ 'config-your-language' => 'Twoja rěč:',
+ 'config-your-language-help' => 'Wubjer rěč, kotraž ma so za instalaciski proces wužiwać.',
+ 'config-wiki-language' => 'Wikirěč:',
+ 'config-wiki-language-help' => 'Wubjer rěč, w kotrejž wiki ma so zwjetša pisać.',
+ 'config-back' => '← Wróćo',
+ 'config-continue' => 'Dale →',
+ 'config-page-language' => 'Rěč',
+ 'config-page-welcome' => 'Witaj do MediaWiki!',
+ 'config-page-dbconnect' => 'Z datowej banku zwjazać',
+ 'config-page-upgrade' => 'Eksistowacu instalaciju aktualizować',
+ 'config-page-dbsettings' => 'Nastajenja datoweje banki',
+ 'config-page-name' => 'Mjeno',
+ 'config-page-options' => 'Opcije',
+ 'config-page-install' => 'Instalować',
+ 'config-page-complete' => 'Dokónčeny!',
+ 'config-page-restart' => 'Instalaciju znowa startować',
+ 'config-page-readme' => 'Čitaj mje',
+ 'config-page-releasenotes' => 'Wersijowe informacije',
+ 'config-page-copying' => 'Kopěrowanje',
+ 'config-page-upgradedoc' => 'Aktualizowanje',
+ 'config-page-existingwiki' => 'Eksistowacy wiki',
+ 'config-help-restart' => 'Chceš wšě składowane daty hašeć, kotrež sy zapodał a instalaciski proces znowa startować?',
+ 'config-restart' => 'Haj, znowa startować',
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki Startowa strona MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Nawod za wužiwarjow]
+* [http://www.mediawiki.org/wiki/Manual:Contents Nawod za administratorow]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Huste prašenja]
+----
+* <doclink href=Readme>Čitaj mje</doclink>
+* <doclink href=ReleaseNotes>Wersijowe informacije</doclink>
+* <doclink href=Copying>Licencne postajenja</doclink>
+* <doclink href=UpgradeDoc>Aktualizacija</doclink>',
+ 'config-env-good' => 'Wokolina je so skontrolowała.
+Móžeš MediaWiki instalować.',
+ 'config-env-bad' => 'Wokolina je so skontrolowała.
+Njemóžeš MediaWiki instalować.',
+ 'config-env-php' => 'PHP $1 je instalowany.',
+ 'config-env-php-toolow' => 'PHP $1 je instalowany.
+Ale MediaWiki wužaduje sej PHP $2 abo wyši.',
+ 'config-unicode-using-utf8' => 'Za normalizaciju Unicode so utf8_normalize.so Briona Vibbera wužiwa.',
+ 'config-unicode-using-intl' => 'Za normalizaciju Unicode so [http://pecl.php.net/intl PECL-rozšěrjenje intl] wužiwa.',
+ 'config-no-db' => 'Njeda so přihódny ćěrjak datoweje banki namakać!',
+ 'config-no-fts3' => "'''Warnowanje''': SQLite je so bjez [http://sqlite.org/fts3.html FTS3-modula] kompilował, pytanske funkcije njebudu k dispoziciji stać.",
+ 'config-register-globals' => "'''Warnowanje: Funkcija <code>[http://php.net/register_globals register_globals]</code> PHP je zmóžnjena.'''
+'''Znjemóžń ju, jeli móžeš.'''
+MediaWiki budźe fungować, ale twój serwer je potencielnym wěstotnym njedostatkam wustajeny.",
+ 'config-ze1' => "'''Chutny zmylk: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] je aktiwny!'''
+Tuta opcija zawinuje grawěrowace zmylki při MediaWiki.
+Njemóžeš MediaWiki instalować abo wužiwać, chibazo tuta opcija je znjemóžnjena.",
+ 'config-safe-mode' => "'''Warnowanje:''' [http://www.php.net/features.safe-mode wěsty modus] PHP je aktiwny.
+To móže problemy zawinować, předewšěm, jeli so datajowe nahraća a podpěra <code>math</code> wužiwaja.",
+ 'config-xml-bad' => 'XML-modul za PHP faluje.
+MediaWiki trjeba funkcije w tutym modulu a njebudźe w tutej konfiguraciji fungować.
+Jeli wužiwaš Mandrake, instaluj paket php-xml.',
+ 'config-memory-raised' => 'PHP-parameter <code>memory_limit</code> je $1, je so na hódnotu $2 zwyšił.',
+ 'config-memory-bad' => "'''Warnowanje:''' PHP-parameter <code>memory_limit</code> ma hódnotu $1,
+To je najskerje přeniske.
+Instalacija móhła so njeporadźić!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] je instalowany',
+ 'config-apc' => '[http://www.php.net/apc APC] je instalowany',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] je instalowany',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] je instalowany',
+ 'config-diff3-bad' => 'GNU diff3 njenamakany.',
+ 'config-no-uri' => "'''Zmylk:''' Aktualny URI njeda so postajić.
+Instalacija bu přetorhnjena.",
+ 'config-db-type' => 'Typ datoweje banki:',
+ 'config-db-host' => 'Serwer datoweje banki:',
+ 'config-db-host-oracle' => 'Datowa banka TNS:',
+ 'config-db-wiki-settings' => 'Tutón wiki identifikować',
+ 'config-db-name' => 'Mjeno datoweje banki:',
+ 'config-db-name-oracle' => 'Šema datoweje banki:',
+ 'config-db-install-account' => 'Wužiwarske konto za instalaciju',
+ 'config-db-username' => 'Wužiwarske mjeno datoweje banki:',
+ 'config-db-password' => 'Hesło datoweje banki:',
+ 'config-db-password-empty' => 'Prošu zapodaj hesło za noweho wužiwarja datoweje banki: $1.
+Byrnjež było móžno wužiwarjow bjez hesłow wutworić, njeje to wěste.',
+ 'config-db-install-username' => 'Zapodaj wužiwarske mjeno, kotrež budźe so za zwisk z datowej banku za instalaciski proces wužiwać.
+To njeje wužiwarske mjeno konta MediaWiki; to je wužiwarske mjeno za twoju datowu banku.',
+ 'config-db-install-password' => 'Zapodaj hesło, kotrež budźe so za zwisk z datowej banku za instalaciski proces wužiwać.
+To njeje hesło konta MediaWiki; to je hesło za twoju datowu banku.',
+ 'config-db-install-help' => 'Zapodaj wužiwarske mjeno a hesło, kotrejž měłoj so za zwisk z datowej banku za instalaciski proces wužiwać.',
+ 'config-db-account-lock' => 'Samsne wužiwarske mjeno a hesło za normalnu operaciju wužiwać',
+ 'config-db-wiki-account' => 'Wužiwarske konto za normalnu operaciju',
+ 'config-db-prefix' => 'Tabelowy prefiks datoweje banki:',
+ 'config-db-charset' => 'Znamješkowa sadźba datoweje banki',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binarny',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 wróćokompatibelny UTF-8',
+ 'config-mysql-old' => 'MySQL $1 abo nowši trěbny, maš $2.',
+ 'config-db-port' => 'Port datoweje banki:',
+ 'config-db-schema' => 'Šema za MediaWiki',
+ 'config-db-schema-help' => 'Tuta šema da so zwjetša derje wužiwać.
+Změń ju jenož, jeli su přeswědčiwe přičiny za to.',
+ 'config-sqlite-dir' => 'Zapis SQLite-datow:',
+ 'config-oracle-def-ts' => 'Standardny tabelowy rum:',
+ 'config-oracle-temp-ts' => 'Nachwilny tabelowy rum:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-mysql' => '* $1 je primarny cil za MediaWiki a podpěruje so najlěpje ([http://www.php.net/manual/en/mysql.installation.php Nawod ke kompilowanju PHP z MySQL-podpěru])',
+ 'config-support-postgres' => '* $1 je popularny system datoweje banki zjawneho žórła jako alternatiwa k MySQL ([http://www.php.net/manual/en/pgsql.installation.php nawod za kompilowanje PHP z podpěru PostgreSQL])',
+ 'config-header-mysql' => 'Nastajenja MySQL',
+ 'config-header-postgres' => 'Nastajenja PostgreSQL',
+ 'config-header-sqlite' => 'Nastajenja SQLite',
+ 'config-header-oracle' => 'Nastajenja Oracle',
+ 'config-invalid-db-type' => 'Njepłaćiwy typ datoweje banki',
+ 'config-missing-db-name' => 'Dyrbiš hódnotu za "Mjeno datoweje banki" zapodać',
+ 'config-missing-db-host' => 'Dyrbiš hódnotu za "Database host" zapodać',
+ 'config-missing-db-server-oracle' => 'Dyrbiš hódnotu za "Database TNS" zapodać',
+ 'config-invalid-db-server-oracle' => 'Njepłaćiwa datowa banka TNS "$1".
+Wužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9), podsmužki (_) a dypki (.).',
+ 'config-invalid-db-name' => 'Njepłaćiwe mjeno "$1" datoweje banki.
+Wužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9),a podsmužki (_) a wjazawki (-).',
+ 'config-invalid-db-prefix' => 'Njepłaćiwy prefiks "$1" datoweje banki.
+Wužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9), podsmužki (_) a wjazawki (-).',
+ 'config-connection-error' => '$1.
+
+Skontroluj serwer, wužiwarske a hesło a spytaj hišće raz.',
+ 'config-invalid-schema' => 'Njepłaćiwe šema za MediaWiki "$1".
+Wužij jenož pismiki ASCII (a-z, A-Z), ličby (0-9) a podsmužki (_).',
+ 'config-db-sys-create-oracle' => 'Instalaciski program podpěruje jenož wužiwanje SYSDBA-konta za zakoženje noweho konta.',
+ 'config-db-sys-user-exists-oracle' => 'Wužiwarske konto "$1" hižo eksistuje. SYSDBA hodźi so jenož za załoženje noweho konta wužiwać!',
+ 'config-postgres-old' => 'PostgreSQL $1 abo nowši trěbny, maš $2.',
+ 'config-sqlite-name-help' => 'Wubjer mjeno, kotrež twój wiki identifikuje.
+Njewužij mjezery abo wjazawki.
+To budźe so za mjeno dataje SQLite-datow wužiwać.',
+ 'config-sqlite-mkdir-error' => 'Zmylk při wutworjenju datoweho zapisa "$1".
+Skontroluj městno a spytaj hišće raz.',
+ 'config-sqlite-dir-unwritable' => 'Njeje móžno do zapisa "$1" pisać.
+Změń jeho prawa, tak zo webserwer móže do njeho pisać a spytaj hišće raz.',
+ 'config-sqlite-connection-error' => '$1.
+
+Skontroluj datowy zapis a mjeno datoweje banki kaj spytaj hišće raz.',
+ 'config-sqlite-readonly' => 'Do dataje <code>$1</code> njeda so pisać.',
+ 'config-sqlite-cant-create-db' => 'Dataja <code>$1</code> datoweje banki njeda so wutworić.',
+ 'config-sqlite-fts3-downgrade' => 'PHP wo podpěrje FTS3 k dispoziciji njesteji, table so znižuja',
+ 'config-can-upgrade' => "Su tabele MediaWiki w tutej datowej bance.
+Zo by je na MediaWiki $1 aktualizował, klikń na '''Dale'''.",
+ 'config-upgrade-done-no-regenerate' => 'Aktualizacija dokónčena.
+
+Móžeš nětko [$1 swój wiki wužiwać].',
+ 'config-regenerate' => 'LocalSettings.php znowa wutworić →',
+ 'config-show-table-status' => 'Naprašowanje SHOW TABLE STATUS je so njeporadźiło!',
+ 'config-unknown-collation' => "'''Warnowanje:''' Datowa banka njeznatu kolaciju wužiwa.",
+ 'config-db-web-account' => 'Konto datoweje banki za webpřistup',
+ 'config-db-web-help' => 'wubjer wužiwarske mjeno a hesło, kotrejž webserwer budźe wužiwać, zo by z serwerom datoweje banki za wšědnu operaciju zwjazać',
+ 'config-db-web-account-same' => 'Samsne konto kaž za instalaciju wužiwać',
+ 'config-db-web-create' => 'Załož konto, jeli hišće njeeksistuje.',
+ 'config-db-web-no-create-privs' => 'Konto, kotrež sy za instalaciju podał, nima dosć woprawnjenjow, zo by konto wutworiło.
+Konto, kotrež tu podawaće, dyrbi hižo eksistować.',
+ 'config-mysql-engine' => 'Składowanska mašina:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => 'Znamješkowa sadźba datoweje banki:',
+ 'config-mysql-binary' => 'Binarny',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Mjeno wikija:',
+ 'config-site-name-help' => 'To zjewi so w titulowej lejstwje wobhladaka kaž tež na wšelakich druhich městnach.',
+ 'config-site-name-blank' => 'Zapodaj sydłowe mjeno.',
+ 'config-project-namespace' => 'Mjenowy rum projekta:',
+ 'config-ns-generic' => 'Projekt',
+ 'config-ns-site-name' => 'Samsne kaž wikimjeno: $1',
+ 'config-ns-other' => 'Druhe (podać)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-ns-invalid' => 'Podaty mjenowy rum "<nowiki>$1</nowiki>" je njepłaćiwy.
+Podaj druhi projektowy mjenowy rum.',
+ 'config-ns-conflict' => 'Podaty mjenowy rum "<nowiki>$1</nowiki>" je w konflikće ze standardnym mjenjowym rumom MediaWiki.
+Podaj druhi projektowy mjenowy rum.',
+ 'config-admin-box' => 'Administratorowe konto',
+ 'config-admin-name' => 'Twoje mjeno:',
+ 'config-admin-password' => 'Hesło:',
+ 'config-admin-password-confirm' => 'Hesło wospjetować:',
+ 'config-admin-help' => 'Zapodaj swoje preferowane wužiwarske mjeno, na přikład "Jurij Serb".
+To je mjeno, kotrež budźeš wužiwać, zo by so do wikija přizjewił.',
+ 'config-admin-name-blank' => 'Zapodaj administratorowe wužiwarske mjeno.',
+ 'config-admin-name-invalid' => 'Podate wužiwarske mjeno "<nowiki>$1</nowiki>" je njepłaćiwe.
+Podaj druhe wužiwarske mjeno.',
+ 'config-admin-password-blank' => 'Zapodaj hesło za administratorowe konto.',
+ 'config-admin-password-same' => 'Hesło dyrbi so wot wužiwarskeho mjena rozeznać.',
+ 'config-admin-password-mismatch' => 'Wobě hesle, kotrejž sy zapodał, njejstej jenakej.',
+ 'config-admin-email' => 'E-mejlowa adresa:',
+ 'config-admin-email-help' => 'Zapodaj tu e-mejlowu adresu, zo by přijimanje e-mejlow wot druhich wužiwarjow w tutym wikiju zmóžnił, swoje hesło wróćo stajił a zdźělenki wo změnach na swojich wobkedźbowanych stronach dostał.',
+ 'config-admin-error-user' => 'Interny zmylk při wutworjenju administratora z mjenom "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Interny zmylk při nastajenju hesła za administratora "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Sy njepłaćiwu e-mejlowu adresu zapodał.',
+ 'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Rozesyłansku lisćinu wo připowědźenjach nowych wersijow ].abonować',
+ 'config-almost-done' => 'Sy skoro hotowy!
+Móžeš nětko zbytnu konfiguraciju přeskočić a wiki hnydom instalować.',
+ 'config-optional-continue' => 'Dalše prašenja?',
+ 'config-optional-skip' => 'Instaluj nětko wiki.',
+ 'config-profile' => 'Profil wužiwarskich prawow:',
+ 'config-profile-wiki' => 'Tradicionelny wiki',
+ 'config-profile-no-anon' => 'Załoženje konto je trěbne',
+ 'config-profile-fishbowl' => 'Jenož awtorizowani wobdźěłarjo',
+ 'config-profile-private' => 'Priwatny wiki',
+ 'config-license' => 'Awtorske prawo a licenca:',
+ 'config-license-none' => 'Žane licencne podaća w nohowej lince',
+ 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
+ 'config-license-cc-0' => 'Creative Commons "Zero"',
+ 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
+ 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 abo nowša',
+ 'config-license-pd' => 'Powšitkownosći přistupny',
+ 'config-license-cc-choose' => 'Swójsku licencu Creative Commons wubrać',
+ 'config-email-settings' => 'E-mejlowe nastajenja',
+ 'config-enable-email' => 'Wuchadźace e-mejlki zmóžnić',
+ 'config-enable-email-help' => 'Jeli chceš e-mejl wužiwać, dyrbja so [http://www.php.net/manual/en/mail.configuration.php e-mejlowe nastajenja PHP] prawje konfigurować.
+Jeli nochceš e-mejlowe funkcije wužiwać, móžeš je tu znjemóžnić.',
+ 'config-email-user' => 'E-mejl mjez wužiwarjemi zmóžnić',
+ 'config-email-user-help' => 'Wšěm wužiwarjam dowolić, jednomu druhemu e-mejlki pósłać, jeli su tutu funkciju w swojich nastajenjach zmóžnili.',
+ 'config-email-usertalk' => 'Zdźělenja za wužiwarske diskusijne strony zmóžnić',
+ 'config-email-usertalk-help' => 'Wužiwarjam dowolić zdźělenki wo změnach na wužiwarskich diskusijnych stronach dóstać, jeli woni su to w swojich nastajenjach zmóžnili.',
+ 'config-email-watchlist' => 'Zdźělenja za wobkedźbowanki zmóžnić',
+ 'config-email-watchlist-help' => 'Wužiwarjam dowolić zdźělenki wo jich wobked´bowanych stronach dóstać, jeli woni su to w swojich nastajenjach zmóžnili.',
+ 'config-email-auth' => 'E-mejlowu awtentifikaciju zmóžnić',
+ 'config-email-sender' => 'E-mejlowa adresa za wotmołwy:',
+ 'config-upload-settings' => 'Wobrazy a nahraća datajow',
+ 'config-upload-enable' => 'Nahraće datajow zmóžnić',
+ 'config-upload-deleted' => 'Zapis za zhašane dataje:',
+ 'config-upload-deleted-help' => 'Wubjer zapis, w kotrymž zhašene dataje maja so archiwować.
+Idealnje tón njeměł z weba přistupny być.',
+ 'config-logo' => 'URL loga:',
+ 'config-instantcommons' => 'Instant commons zmóžnić',
+ 'config-cc-error' => 'Pytanje za licencu Creative Commons njeje žadyn wuslědk přinjesło.
+Zapodaj licencne mjeno manuelnje.',
+ 'config-cc-again' => 'Zaso wubrać...',
+ 'config-cc-not-chosen' => 'Wubjer licencu Creative Commons a klikń na "dale".',
+ 'config-advanced-settings' => 'Rozšěrjena konfiguraćija',
+ 'config-cache-options' => 'Nastajenja za objektowe pufrowanje:',
+ 'config-cache-none' => 'Žane pufrowanje (žana funkcionalnosć so njewotstronja, ale spěšnosć móže so na wjetšich wikijowych sydłach wobwliwować)',
+ 'config-cache-accel' => 'Objektowe pufrowanje PHP (APC, eAccelerator, XCache abo WinCache)',
+ 'config-cache-memcached' => 'Memcached wužiwać (wužaduje sej přidatnu instalaciju a konfiguraciju)',
+ 'config-memcached-servers' => 'Serwery memcached:',
+ 'config-memcached-help' => 'Lisćina IP-adresow, kotrež maja so za Memcached wužiwać.
+Kóžda linka měła jenož jednu IP-adresu a port, kotryž ma so wužiwać, wobsahować. Na přikład:
+127.0.0.1:11211
+192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Sy Memcached jako swój pufrowakowy typ wubrał, ale njejsy žane serwery podał',
+ 'config-memcache-badip' => 'Sy njepłaćiwu IP-adresu za Memcached zapodał: $1',
+ 'config-memcache-noport' => 'Njejsy žadyn port za wužiwanje serwera Memcached podał: $1.
+Jeli port njewěš, standard je 11211.',
+ 'config-memcache-badport' => 'Portowe čisła za Memcached měli mjez $1 a $2 być',
+ 'config-extensions' => 'Rozšěrjenja',
+ 'config-extensions-help' => 'Rozšěrjenja podate horjeka buchu w twojim zapisu <code>./extensions</code> namakane.
+
+To móže sej přidatnu konfiguraciju wužadać, ale móžeš je nětko zmóžnić.',
+ 'config-install-alreadydone' => "'''Warnowanje:''' Zda so, zo sy hižo MediaWiki instalował a pospytuješ jón znowa instalować.
+Prošu pokročuj z přichodnej stronu.",
+ 'config-install-begin' => 'Přez kliknjenje na "{{int:config-continue}}" budźe so instalacija MediaWiki startować.
+Jeli hišće chceš něšto změnić, klikń na "Wróćo".',
+ 'config-install-step-done' => 'dokónčene',
+ 'config-install-step-failed' => 'njeporadźiło',
+ 'config-install-extensions' => 'Inkluziwnje rozšěrjenja',
+ 'config-install-database' => 'Datowa banka so připrawja',
+ 'config-install-pg-schema-not-exist' => 'Šema PostgreSQL njeeksistuje',
+ 'config-install-pg-schema-failed' => 'Wutworjenje tabelow je so njeporadźiło.
+Zawěsć, zo wužiwar "$1" móže do šemy "$2" pisać.',
+ 'config-install-pg-commit' => 'Změny so wotesyłaja',
+ 'config-install-pg-plpgsql' => 'Pruwowanje za rěču PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Dyrbiš rěč PL/pgSQL w datowej bance $1 instalować',
+ 'config-pg-no-create-privs' => 'Konto, kotrež sy za instalaciju podał, nima dosahace prawa za wutworjenje konta.',
+ 'config-install-user' => 'Tworjenje wužiwarja datoweje banki',
+ 'config-install-user-alreadyexists' => 'Wužiwar "$1" hižo eksistuje',
+ 'config-install-user-create-failed' => 'Wutworjenje wužiwarja "$1" je so njeporadźiło: $2',
+ 'config-install-user-grant-failed' => 'Prawo njeda so wužiwarjej "$1" dać: $2',
+ 'config-install-tables' => 'Tworjenje tabelow',
+ 'config-install-tables-exist' => "'''Warnowanje''': Zda so, zo tabele MediaWiki hižo eksistuja.
+Wutworjenje so přeskakuje.",
+ 'config-install-tables-failed' => "'''Zmylk''': Wutworjenje tabele je so slědowaceho zmylka dla njeporadźiło: $1",
+ 'config-install-interwiki' => 'Standardna tabela interwikijow so pjelni',
+ 'config-install-interwiki-list' => '<code>interwiki.list</code> njeda so namakać.',
+ 'config-install-interwiki-exists' => "'''Warnowanje''': Zda so, zo tabela interwikjow hižo zapiski wobsahuje.
+Standardna lisćina sp přeskakuje.",
+ 'config-install-stats' => 'Statistika so inicializuje',
+ 'config-install-keys' => 'Tworjenje tajneho kluča',
+ 'config-install-sysop' => 'Tworjenje administratoroweho wužiwarskeho konta',
+ 'config-install-subscribe-fail' => 'Abonowanje "mediawiki-announce" njemóžno',
+ 'config-install-mainpage' => 'Hłowna strona so ze standardnym wobsahom wutworja',
+ 'config-install-extension-tables' => 'Tabele za zmóžnjene rozšěrjenja so tworja',
+ 'config-install-mainpage-failed' => 'Powěsć njeda so zasunyć: $1',
+ 'config-download-localsettings' => 'LocalSettings.php sćahnyć',
+ 'config-help' => 'pomoc',
+);
+
+/** Hungarian (Magyar)
+ * @author Dani
+ * @author Glanthor Reviol
+ */
+$messages['hu'] = array(
+ 'config-desc' => 'A MediaWiki telepítője',
+ 'config-title' => 'A MediaWiki $1 telepítése',
+ 'config-information' => 'Információ',
+ 'config-localsettings-upgrade' => 'Már létezik a <code>LocalSettings.php</code> fájl.
+A telepített szoftver frissítéséhez írd be az alábbi mezőbe a <code>$wgUpgradeKey</code> beállítás értékét, melyet a LocalSettings.php nevű fájlban találhatsz meg.',
+ 'config-localsettings-key' => 'Frissítési kulcs:',
+ 'config-localsettings-badkey' => 'A megadott kulcs érvénytelen.',
+ 'config-localsettings-connection-error' => 'Nem sikerült csatlakozni az adatbázishoz a LocalSettings.php-ben vagy az AdminSettings.php-ben megadott adatokkal. Ellenőrizd a beállításokat, majd próbáld újra.
+
+$1',
+ 'config-session-error' => 'Nem sikerült elindítani a munkamenetet: $1',
+ 'config-session-expired' => 'Úgy tűnik, hogy a munkamenetadatok lejártak.
+A munkamenetek élettartama a következőre van beállítva: $1.
+Az érték növelhető a php.ini <code>session.gc_maxlifetime</code> beállításának módosításával.
+Indítsd újra a telepítési folyamatot.',
+ 'config-no-session' => 'Elvesztek a munkamenetadatok!
+Ellenőrizd, hogy a php.ini-ben a <code>session.save_path</code> a megfelelő könyvtárra mutat-e.',
+ 'config-your-language' => 'Nyelv:',
+ 'config-your-language-help' => 'A telepítési folyamat során használandó nyelv.',
+ 'config-wiki-language' => 'A wiki nyelve:',
+ 'config-wiki-language-help' => 'Az a nyelv, amin a wiki tartalmának legnagyobb része íródik.',
+ 'config-back' => '← Vissza',
+ 'config-continue' => 'Folytatás →',
+ 'config-page-language' => 'Nyelv',
+ 'config-page-welcome' => 'Üdvözöl a MediaWiki!',
+ 'config-page-dbconnect' => 'Kapcsolódás az adatbázishoz',
+ 'config-page-upgrade' => 'Telepített változat frissítése',
+ 'config-page-dbsettings' => 'Adatbázis-beállítások',
+ 'config-page-name' => 'Név',
+ 'config-page-options' => 'Beállítások',
+ 'config-page-install' => 'Telepítés',
+ 'config-page-complete' => 'Kész!',
+ 'config-page-restart' => 'Telepítés újraindítása',
+ 'config-page-readme' => 'Tudnivalók',
+ 'config-page-releasenotes' => 'Kiadási megjegyzések',
+ 'config-page-copying' => 'Másolás',
+ 'config-page-upgradedoc' => 'Frissítés',
+ 'config-page-existingwiki' => 'Létező wiki',
+ 'config-help-restart' => 'Szeretnéd törölni az eddig megadott összes adatot és újraindítani a telepítési folyamatot?',
+ 'config-restart' => 'Igen, újraindítás',
+ 'config-welcome' => '=== A környezet ellenőrzése ===
+Néhány alapvető ellenőrzés lett végrehajtva, ami meghatározza, hogy ez a környezet alkalmas-e a MediaWiki telepítésére.
+Ha telepítéssel kapcsolatos segítségre van szükséged, add meg ezen ellenőrzések eredményét.',
+ 'config-copyright' => "=== Licenc és feltételek ===
+
+$1
+
+Ez a program szabad szoftver; terjeszthető illetve módosítható a Free Software Foundation által kiadott GNU General Public License dokumentumában leírtak; akár a licenc 2-es, akár (tetszőleges) későbbi változata szerint.
+
+Ez a program abban a reményben kerül közreadásra, hogy hasznos lesz, de minden egyéb '''garancia nélkül''', az '''eladhatóságra''' vagy '''valamely célra való alkalmazhatóságra''' való származtatott garanciát is beleértve. További részleteket a GNU General Public License tartalmaz.
+
+A felhasználónak a programmal együtt meg kell kapnia a <doclink href=Copying>GNU General Public License egy példányát</doclink>; ha mégsem kapta meg, akkor írjon a Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. címre, vagy [http://www.gnu.org/copyleft/gpl.html tekintse meg online].",
+ 'config-sidebar' => '* [http://www.mediawiki.org A MediaWiki honlapja]
+* [http://www.mediawiki.org/wiki/Help:Contents Felhasználói kézikönyv]
+* [http://www.mediawiki.org/wiki/Manual:Contents Útmutató adminisztrátoroknak]
+* [http://www.mediawiki.org/wiki/Manual:FAQ GyIK]
+----
+* <doclink href=Readme>Ismertető</doclink>
+* <doclink href=ReleaseNotes>Kiadási megjegyzések</doclink>
+* <doclink href=Copying>Másolás</doclink>
+* <doclink href=UpgradeDoc>Frissítés</doclink>',
+ 'config-env-good' => 'A környezet ellenőrzése befejeződött.
+A MediaWiki telepíthető.',
+ 'config-env-bad' => 'A környezet ellenőrzése befejeződött.
+A MediaWiki nem telepíthető.',
+ 'config-env-php' => 'A PHP verziója: $1',
+ 'config-env-php-toolow' => 'PHP $1 van telepítve,
+azonban a MediaWikinek PHP $2, vagy újabb szükséges.',
+ 'config-unicode-using-utf8' => 'A rendszer Unicode normalizálására Brion Vibber utf8_normalize.so könyvtárát használja.',
+ 'config-unicode-using-intl' => 'A rendszer Unicode normalizálására az [http://pecl.php.net/intl intl PECL kiterjesztést] használja.',
+ 'config-unicode-pure-php-warning' => "'''Figyelmeztetés''': Az Unicode normalizáláshoz szükséges [http://pecl.php.net/intl intl PECL kiterjesztés] nem érhető el, helyette a lassú, PHP alapú implementáció lesz használva.
+Ha nagy látogatottságú oldalt üzemeltetsz, itt találhatsz további információkat [http://www.mediawiki.org/wiki/Unicode_normalization_considerations a témáról].",
+ 'config-no-db' => 'Nem sikerült egyetlen használható adatbázismeghajtót sem találni.',
+ 'config-no-fts3' => "'''Figyelmeztetés''': Az SQLite [http://sqlite.org/fts3.html FTS3 modul] nélkül lett fordítva, a keresési funkciók nem fognak működni ezen a rendszeren.",
+ 'config-register-globals' => "'''Figyelmeztetés: A PHP <code>[http://php.net/register_globals register_globals]</code> beállítása engedélyezve van.'''
+'''Tiltsd le, ha van rá lehetőséged.'''
+A MediaWiki működőképes a beállítás használata mellett, de a szerver biztonsági kockázatnak lesz kitéve.",
+ 'config-magic-quotes-runtime' => "'''Kritikus hiba: a [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] aktív!'''
+Ez a beállítás kiszámíthatatlan károkat okoz a bevitt adatokban.
+A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
+ 'config-magic-quotes-sybase' => "'''Kritikus hiba: a [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] aktív!'''
+Ez a beállítás kiszámíthatatlan károkat okoz a bevitt adatokban.
+A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
+ 'config-mbstring' => "'''Kritikus hiba: az [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime mbstring.func_overload] aktív!'''
+Ez a beállítás hibákat okoz és kiszámíthatatlanul károsíthatja bevitt adatokat.
+A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
+ 'config-ze1' => "'''Kritikus hiba: a [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] aktív!'''
+Ez a beállítás borzalmas hibákat okoz a MediaWiki futása során.
+A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
+ 'config-xml-bad' => 'A PHP XML-modulja hiányzik.
+Egyes MediaWiki-funkciók, melyek ezt a modult igénylik, nem fognak működni ilyen beállítások mellett.
+Ha Madrake-et futtatsz, telepítsd a php-xml csomagot.',
+ 'config-pcre' => 'Úgy tűnik, hogy a PCRE támogató modul hiányzik.
+A MediaWikinek Perl-kompatibilis reguláriskifejezés-függvényekre van szüksége a működéshez.',
+ 'config-memory-raised' => 'A PHP <code>memory_limit</code> beállításának értéke: $1. Meg lett növelve a következő értékre: $2.',
+ 'config-memory-bad' => "'''Figyelmeztetés:''' A PHP <code>memory_limit</code> beállításának értéke $1.
+Ez az érték valószínűleg túl kevés, a telepítés sikertelen lehet.",
+ 'config-xcache' => 'Az [http://trac.lighttpd.net/xcache/ XCache] telepítve van',
+ 'config-apc' => 'Az [http://www.php.net/apc APC] telepítve van',
+ 'config-eaccel' => 'Az [http://eaccelerator.sourceforge.net/ eAccelerator] telepítve van',
+ 'config-wincache' => 'A [http://www.iis.net/download/WinCacheForPhp WinCache] telepítve van',
+ 'config-no-cache' => "'''Figyelmeztetés:''' Nem található [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] és [http://www.iis.net/download/WinCacheForPhp WinCache] sem.
+Az objektum-gyorsítótárazás nem lesz engedélyezve.",
+ 'config-diff3-bad' => 'GNU diff3 nem található.',
+ 'config-imagemagick' => 'Az ImageMagick megtalálható a rendszeren: <code>$1</code>.
+A bélyegképek készítése engedélyezve lesz a feltöltések engedélyezése esetén.',
+ 'config-gd' => 'A GD grafikai könyvtár elérhető.
+Bélyegképek készítése működni fog, miután engedélyezted a fájlfeltöltést.',
+ 'config-no-scaling' => 'Nem található a GD könyvtár és az ImageMagick.
+A bélyegképek készítése le lesz tiltva.',
+ 'config-no-uri' => "'''Hiba:''' Nem sikerült megállapítani a jelenlegi URI-t.
+Telepítés megszakítva.",
+ 'config-uploads-not-safe' => "'''Figyelmeztetés:''' a feltöltésekhez használt alapértelmezett könyvtárban (<code>$1</code>) tetszőleges külső szkript futtatható.
+Habár a MediaWiki ellenőrzi a feltöltött fájlokat az efféle biztonsági veszélyek megtalálása érdekében, a feltöltés engedélyezése előtt erősen ajánlott a [http://www.mediawiki.org/wiki/Manual:Security#Upload_security a sérülékenység megszüntetése].",
+ 'config-db-type' => 'Adatbázis típusa:',
+ 'config-db-host' => 'Adatbázis hosztneve:',
+ 'config-db-host-help' => 'Ha az adatbázisszerver másik szerveren található, add meg a hosztnevét vagy az IP-címét.
+
+Ha megosztott webtárhelyet használsz, a szolgáltató dokumentációjában megtalálható a helyes hosztnév.
+
+Ha Windows-alapú szerverre telepítesz, és MySQL-t használsz, a „localhost” nem biztos, hogy működni fog. Ha így van, próbáld meg a „127.0.0.1” helyi IP-cím használatát.',
+ 'config-db-host-oracle' => 'Adatbázis TNS:',
+ 'config-db-wiki-settings' => 'A wiki azonosítása',
+ 'config-db-name' => 'Adatbázisnév:',
+ 'config-db-name-help' => 'Válassz egy nevet a wiki azonosítására.
+Ne tartalmazzon szóközt.
+
+Ha megosztott webtárhelyet használsz, a szolgáltatód vagy egy konkrét adatbázisnevet ad neked használatra, vagy te magad hozhatsz létre adatbázisokat a vezérlőpulton keresztül.',
+ 'config-db-name-oracle' => 'Adatbázisséma:',
+ 'config-db-install-account' => 'A telepítéshez használt felhasználói fiók adatai',
+ 'config-db-username' => 'Felhasználónév:',
+ 'config-db-password' => 'Jelszó:',
+ 'config-db-install-username' => 'Írd be az adatbázisrendszerhez való csatlakozáshoz használt felhasználónevet.
+Ez nem a MediaWiki fiók felhasználóneve; ez az adatbázisrendszeren használt felhasználóneved.',
+ 'config-db-install-password' => 'Írd be az adatbázisrendszerhez való csatlakozáshoz használt jelszót.
+Ez nem a MediaWiki-fiók jelszava; ez az adatbázisrendszeren használt jelszavad.',
+ 'config-db-install-help' => 'Add meg a felhasználónevet és jelszót, amivel a telepítő csatlakozhat az adatbázishoz.',
+ 'config-db-account-lock' => 'Általános működés során is ezen információk használata',
+ 'config-db-wiki-account' => 'Általános működéshez használt felhasználói adatok',
+ 'config-db-wiki-help' => 'Add meg azt a felhasználónevet és jelszót, amivel a wiki fog csatlakozni az adatbázishoz működés közben.
+Ha a fiók nem létezik és a telepítést végző fiók rendelkezik megfelelő jogosultsággal, egy új fiók készül a megadott a névvel, azon minimális jogosultságkörrel, ami a wiki működéséhez szükséges.',
+ 'config-db-prefix' => 'Adatbázistáblák nevének előtagja:',
+ 'config-db-prefix-help' => 'Ha egyetlen adatbázison osztozik több wiki, vagy a MediaWiki és más webalkalmazás, választhatsz egy előtagot a táblaneveknek, hogy megelőzd a konfliktusokat.
+Ne használj szóközöket.
+
+A mezőt általában üresen kell hagyni.',
+ 'config-db-charset' => 'Az adatbázis karakterkészlete',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0, bináris',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0, visszafelé kompatibilis UTF-8',
+ 'config-charset-help' => "'''Figyelmezetés:''' Ha a '''visszafelé kompatibilis UTF-8''' beállítást használod MySQL 4.1 vagy újabb verziók esetén, és utána a <code>mysqldump</code> programmal készítesz róla biztonsági másolatot, az tönkreteheti az összes nem ASCII-karaktert, visszafordíthatatlanul károsítva a másolatokban tárolt adatokat!
+
+'''Bináris''' módban a MediaWiki az UTF-8-ban kódolt szöveget bináris mezőkben tárolja az adatbázisban.
+Ez sokkal hatékonyabb a MySQL UTF-8-módjától, és lehetővé teszi, hogy a teljes Unicode-karakterkészletet használd.
+'''UTF-8-módban''' MySQL tudja, hogy milyen karakterkészlettel van kódolva az adat, és megfelelően tárolja és konvertálja, de
+nem használhatod a [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] feletti karaktereket.",
+ 'config-mysql-old' => 'A MySQL $1 vagy újabb verziója szükséges, a rendszeren $2 van.',
+ 'config-db-port' => 'Adatbázisport:',
+ 'config-db-schema' => 'MediaWiki-séma',
+ 'config-db-schema-help' => 'A fenti sémák általában megfelelőek.
+Csak akkor módosíts rajtuk, ha tudod, hogy szükséges.',
+ 'config-sqlite-dir' => 'SQLite-adatkönyvtár:',
+ 'config-oracle-def-ts' => 'Alapértelmezett táblatér:',
+ 'config-oracle-temp-ts' => 'Ideiglenes táblatér:',
+ 'config-support-info' => 'A MediaWiki a következő adatbázisrendszereket támogatja:
+
+$1
+
+Ha az alábbi listán nem találod azt a rendszert, melyet használni szeretnél, a fenti linken található instrukciókat követve engedélyezheted a támogatását.',
+ 'config-support-mysql' => '* A $1 a MediaWiki elsődleges célpontja, így a legjobban támogatott ([http://www.php.net/manual/en/mysql.installation.php Hogyan fordítható a PHP MySQL-támogatással])',
+ 'config-support-postgres' => '* A $1 népszerű, nyílt forráskódú adatbázisrendszer, a MySQL alternatívája ([http://www.php.net/manual/en/pgsql.installation.php Hogyan fordítható a PHP PostgreSQL-támogatással]). Több apró, javítatlan hiba is előfordulhat, így nem ajánlott éles környezetben használni.',
+ 'config-support-sqlite' => '* Az $1 egy könnyű, nagyon jól támogatott adatbázisrendszer. ([http://www.php.net/manual/en/pdo.installation.php Hogyan fordítható a PHP SQLite-támogatással], PDO-t használ)',
+ 'config-support-oracle' => '* Az $1 kereskedelmi, vállalati adatbázisrendszer. ([http://www.php.net/manual/en/oci8.installation.php Hogyan fordítható a PHP OCI8-támogatással])',
+ 'config-header-mysql' => 'MySQL-beállítások',
+ 'config-header-postgres' => 'PostgreSQL-beállítások',
+ 'config-header-sqlite' => 'SQLite-beállítások',
+ 'config-header-oracle' => 'Oracle-beállítások',
+ 'config-invalid-db-type' => 'Érvénytelen adatbázistípus',
+ 'config-missing-db-name' => 'Meg kell adnod az „adatbázis nevét”',
+ 'config-missing-db-server-oracle' => 'Meg kell adnod az „Adatbázis TNS” értékét',
+ 'config-invalid-db-server-oracle' => 'Érvénytelen adatbázis TNS: „$1”
+Csak ASCII betűk (a-z, A-Z), számok (0-9), alulvonás (_) és pont (.) használható.',
+ 'config-invalid-db-name' => 'Érvénytelen adatbázisnév: „$1”.
+Csak ASCII-karakterek (a-z, A-Z), számok (0-9), alulvonás (_) és kötőjel (-) használható.',
+ 'config-invalid-db-prefix' => 'Érvénytelen adatbázisnév-előtag: „$1”.
+Csak ASCII-karakterek (a-z, A-Z), számok (0-9), alulvonás (_) és kötőjel (-) használható.',
+ 'config-connection-error' => '$1.
+
+Ellenőrizd a hosztot, felhasználónevet és jelszót, majd próbáld újra.',
+ 'config-invalid-schema' => 'Érvénytelen MediaWiki-séma: „$1”.
+Csak ASCII-karakterek (a-z, A-Z), számok (0-9) és alulvonás (_) használható.',
+ 'config-db-sys-create-oracle' => 'A telepítő csak a SYSDBA fiókkal tud új felhasználói fiókot létrehozni.',
+ 'config-db-sys-user-exists-oracle' => 'Már létezik „$1” nevű felhasználói fiók. A SYSDBA csak új fiók létrehozására használható!',
+ 'config-postgres-old' => 'A PostgreSQL $1 vagy újabb verziója szükséges, a rendszeren $2 van.',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Nem lehet létrehozni az adatok tárolásához szükséges <code><nowiki>$1</nowiki></code> könyvtárat, mert a webszerver nem írhat a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>).
+
+A telepítő nem tudta megállapíteni, hogy melyik felhasználói fiókon fut a webszerver.
+A folytatáshoz tedd írhatóvá ezen fiók (és más fiókok!) számára a következő könyvtárat: <code><nowiki>$3</nowiki></code>.
+Unix/Linux rendszereken tedd a következőt:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Nem sikerült létrehozni a következő adatkönyvtárat: „$1”.
+Ellenőrizd a helyet, majd próbáld újra.',
+ 'config-sqlite-dir-unwritable' => 'Nem sikerült írni a következő könyvtárba: „$1”.
+Módosítsd a jogosultságokat úgy, hogy a webszerver tudjon oda írni, majd próbáld újra.',
+ 'config-sqlite-connection-error' => '$1.
+
+Ellenőrizd az adatkönyvtárat és az adatbázisnevet, majd próbáld újra.',
+ 'config-sqlite-readonly' => 'A következő fájl nem írható: <code>$1</code>.',
+ 'config-regenerate' => 'LocalSettings.php elkészítése újra →',
+ 'config-show-table-status' => 'A SHOW TABLE STATUS lekérdezés nem sikerült!',
+ 'config-unknown-collation' => "'''Figyelmeztetés:''' az adatbázis ismeretlen egybevetést használ.",
+ 'config-db-web-account' => 'A webes hozzáférésnél használt adatbázisfiók',
+ 'config-db-web-help' => 'Add meg azt a felhasználónevet és jelszót, amit a webszerver a wiki általános működése során használ a csatlakozáshoz.',
+ 'config-db-web-account-same' => 'Ezen fiók használata a telepítéshez is',
+ 'config-db-web-create' => 'Fiók létrehozása, ha még nem létezik.',
+ 'config-mysql-engine' => 'Tárolómotor:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "A legtöbb esetben az '''InnoDB''' a legjobb választás, mivel megfelelően támogatja a párhuzamosságot.
+
+A '''MyISAM''' gyorsabb megoldás lehet egyfelhasználós vagy csak olvasható környezetekben, azonban a MyISAM-adatbázisok sokkal gyakrabban sérülnek meg, mint az InnoDB-adatbázisok.",
+ 'config-mysql-charset' => 'Adatbázis karakterkészlete:',
+ 'config-mysql-binary' => 'Bináris',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "'''Bináris módban''' a MediaWiki az UTF-8-as szövegeket bináris mezőkben tárolja az adatbázisban.
+Ez sokkal hatékonyabb a MySQL UTF-8-as módjánál, és lehetővé teszi a teljes Unicode-karakterkészlet használatát.
+
+'''UTF-8-as módban''' a MySQL tudni fogja,hogy az adatok milyen karakterkészlettel rendelkeznek, és megfelelően átalakítja őket, azonban nem tárolhatóak olyan karakterek, melyek a [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] felett vannak.",
+ 'config-site-name' => 'A wiki neve:',
+ 'config-site-name-help' => 'A böngésző címsorában és még számos más helyen jelenik meg.',
+ 'config-site-name-blank' => 'Add meg az oldal nevét.',
+ 'config-project-namespace' => 'Projektnévtér:',
+ 'config-ns-generic' => 'Projekt',
+ 'config-ns-site-name' => 'Ugyanaz, mint a wiki neve: $1',
+ 'config-ns-other' => 'Más (meg kell adni)',
+ 'config-ns-other-default' => 'SajátWiki',
+ 'config-project-namespace-help' => "A Wikipédia példáját követve számos wiki elkülöníti egy '''projekt névtérbe''' az irányelveit a tartalommal rendelkező lapoktól
+Az ebben a névtérben található lapok nevei egy előtaggal kezdődnek, amit itt adhatsz meg.
+Általában az előtag a wiki nevéből származik, de nem tartalmazhat írásjeleket, például „#”-t vagy „:”-t.",
+ 'config-admin-box' => 'Adminisztrátori fiók',
+ 'config-admin-name' => 'Név:',
+ 'config-admin-password' => 'Jelszó:',
+ 'config-admin-password-confirm' => 'Jelszó újra:',
+ 'config-admin-help' => 'Írd be a kívánt felhasználónevet, például „Kovács János”.
+Ezzel a névvel fogsz majd bejelentkezni a wikibe.',
+ 'config-admin-name-blank' => 'Add meg az adminisztrátor felhasználónevét!',
+ 'config-admin-name-invalid' => 'A megadott felhasználónév (<nowiki>$1</nowiki>) érvénytelen.
+Adj meg egy másik felhasználónevet.',
+ 'config-admin-password-blank' => 'Add meg az adminisztrátori fiók jelszavát!',
+ 'config-admin-password-same' => 'A jelszó nem lehet ugyanaz, mint a felhasználónév.',
+ 'config-admin-password-mismatch' => 'A megadott jelszavak nem egyeznek.',
+ 'config-admin-email' => 'E-mail cím:',
+ 'config-admin-email-help' => 'Add meg az e-mail címedet, hogy más felhasználók küldhessenek e-maileket a wikin keresztül, új jelszót tudj kérni, és értesülhess a figyelőlistádon lévő lapokon történt változásokról. Üresen is hagyhatod ezt a mezőt.',
+ 'config-subscribe-help' => 'Ez egy alacsony forgalmú levelezőlista, ahol a kiadásokkal kapcsolatos bejelentések jelennek meg, a fontos biztonsági javításokkal együtt.
+Ajánlott feliratkozni rá, és frissíteni a MediaWikit, ha új verzió jön ki.',
+ 'config-almost-done' => 'Már majdnem kész!
+A további konfigurációt kihagyhatod, és most azonnal elindíthatod a wiki telepítését.',
+ 'config-optional-continue' => 'További információk megadása.',
+ 'config-optional-skip' => 'Épp elég volt, települjön a wiki!',
+ 'config-profile' => 'Felhasználói jogosultságok profilja:',
+ 'config-profile-wiki' => 'Hagyományos wiki',
+ 'config-profile-no-anon' => 'Felhasználói fiók létrehozása szükséges',
+ 'config-profile-fishbowl' => 'Csak engedélyezett szerkesztők',
+ 'config-profile-private' => 'Privát wiki',
+ 'config-profile-help' => "A wikik akkor működnek a legjobban, ha minél több felhasználó számára engedélyezett a szerkesztés.
+A MediaWikiben könnyű ellenőrizni a legutóbbi változtatásokat,és visszaállítani a naiv vagy káros felhasználók által okozott károkat.
+
+A MediaWiki azonban számos helyzetben hasznos lehet, és néha nem könnyű mindenkit meggyőzni a wiki előnyeiről.
+Választhatsz!
+
+'''{{int:config-profile-wiki}}kben''' bárki szerkeszthet, akár bejelentkezés nélkül is. A '''{{int:config-profile-no-anon}}''' beállítás további biztonságot nyújt, azonban elijesztheti az alkalmi szerkesztőket.
+
+Lehetőség van arra is, hogy '''{{lc:{{int:config-profile-fishbowl}}}}''' módosíthassák a lapokat, de a nyilvánosság ekkor megtekintheti a lapokat és azok laptörténetét is. '''{{int:config-profile-private}}''' esetén csak az engedélyezett szerkesztők tekinthetik meg a lapokat, és ugyanez a csoport szerkeszthet.
+
+Telepítés után jóval összetettebb jogosultságrendszer állítható össze, további információ a [http://www.mediawiki.org/wiki/Manual:User_rights kézikönyv kapcsolódó bejegyzésében].",
+ 'config-license' => 'Szerzői jog és licenc:',
+ 'config-license-none' => 'Nincs licencjelzés',
+ 'config-license-cc-by-sa' => 'Creative Commons Nevezd meg! - Így add tovább!',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Nevezd meg! - Ne add el! - Így add tovább!',
+ 'config-license-cc-0' => 'Creative Commons Zero',
+ 'config-license-gfdl-old' => 'GNU Szabad Dokumentációs Licenc 1.2',
+ 'config-license-gfdl-current' => '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 [http://freedomdefined.org/Definition szabad licenc] alatt teszi közzé a szerkesztéseit.
+Ez erősíti a közösségi tulajdon érzését, és elősegíti a hosszú távú közreműködést.
+Általában szükségtelen magán- vagy vállalati wiki esetén.
+
+Ha a Wikipédiáról szeretnél szövegeket másolni, és a Wikipédián felhasználhassák a wikidben található szöveget, akkor a '''Creative Commons Nevezd meg! - Így add tovább!''' lehetőséget válaszd.
+
+A GNU Szabad Dokumentációs Licenc a Wikipédia korábbi licence.
+Még ma is érvényes, azonban van néhány tulajdonsága, amely nehezíti az újrafelhasználást és az értelmezését.",
+ 'config-email-settings' => 'E-mail beállítások',
+ 'config-enable-email' => 'Kimenő e-mailek engedélyezése',
+ 'config-enable-email-help' => 'E-mailek küldéséhez [http://www.php.net/manual/en/mail.configuration.php a PHP mail beállításait] megfelelően meg kell adni.
+Ha nem akarsz semmilyen e-mailes funkciót használni, itt tilthatod le őket.',
+ 'config-email-sender' => 'Válaszcím:',
+ 'config-upload-settings' => 'Képek és fájlok feltöltése',
+ 'config-upload-enable' => 'Fájlfeltöltés engedélyezése',
+ 'config-upload-deleted' => 'Törölt fájlok könyvtára:',
+ 'config-logo' => 'A logó URL-címe:',
+ 'config-instantcommons' => 'Instant Commons engedélyezése',
+ 'config-instantcommons-help' => 'Az [http://www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [http://commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.
+A használatához a MediaWikinek internethozzáférésre van szüksége.
+
+A funkcióról és hogy hogyan állítható be más wikik esetén [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos a kézikönyvben] találhatsz további információkat.',
+ 'config-cc-again' => 'Válassz újra…',
+ 'config-advanced-settings' => 'Haladó beállítások',
+ 'config-cache-options' => 'Objektum-gyorsítótárazás beállításai:',
+ 'config-cache-none' => 'Nincs gyorsítótárazás (minden funkció működik, de nagyobb wiki esetében lassabb működést eredményezhet)',
+ 'config-cache-accel' => 'PHP-objektumok gyorsítótárazása (APC, eAccelerator, XCache or WinCache)',
+ 'config-cache-memcached' => 'Memcached használata (további telepítés és konfigurálás szükséges)',
+ 'config-memcached-servers' => 'Memcached-szerverek:',
+ 'config-memcached-help' => 'Azon IP-címek listája, melyeket a Memcached használhat.
+Vesszővel kell elválasztani őket, és meg kell adni a portot is. Például:
+ 127.0.0.1:11211
+ 192.168.1.25:11211',
+ 'config-extensions' => 'Kiterjesztések',
+ 'config-install-step-done' => 'kész',
+ 'config-install-step-failed' => 'sikertelen',
+ 'config-install-extensions' => 'Kiterjesztések beillesztése',
+ 'config-install-database' => 'Adatbázis felállítása',
+ 'config-install-user' => 'Adatbázis-felhasználó létrehozása',
+ 'config-install-tables' => 'Táblák létrehozása',
+ 'config-install-tables-exist' => "'''Figyelmeztetés''': úgy tűnik, hogy a MediaWiki táblái már léteznek.
+Létrehozás kihagyása.",
+ 'config-install-tables-failed' => "'''Hiba''': a tábla létrehozása nem sikerült a következő miatt: $1",
+ 'config-install-interwiki' => 'Alapértelmezett nyelvközihivatkozás-tábla feltöltése',
+ 'config-install-interwiki-list' => 'Az <code>interwiki.list</code> fájl nem található.',
+ 'config-install-stats' => 'Statisztika inicializálása',
+ 'config-install-keys' => 'Titkos kulcsok generálása',
+ 'config-insecure-keys' => "'''Figyelmeztetés:''' A telepítés során generált $1 {{PLURAL:$2|biztonsági kulcs|biztonsági kulcsok}} nem teljesen $1 {{PLURAL:$2|biztonságos|biztonságosak}}. Érdemes {{PLURAL:$2||őket}} manuálisan megváltoztatni.",
+ 'config-install-sysop' => 'Az adminisztrátor felhasználói fiókjának létrehozása',
+ 'config-install-subscribe-fail' => 'Nem sikerült feliratkozni a mediawiki-announce levelezőlistára',
+ 'config-install-mainpage' => 'Kezdőlap létrehozása az alapértelmezett tartalommal',
+ 'config-install-extension-tables' => 'Táblák létrehozása az engedélyezett kiterjesztésekhez',
+ 'config-install-mainpage-failed' => 'Nemsikerült létrehozni a kezdőlapot: $1',
+ 'config-install-done' => "'''Gratulálunk!'''
+A MediaWiki telepítése sikeresen befejeződött.
+
+A telepítő elkészítette a <code>LocalSettings.php</code> fájlt, amely tartalmazza az összes beállítást.
+
+Ezt le kell tölteni, majd elhelyezni a wiki telepítési könyvtárába (az a könyvtár, ahol az index.php is található).
+
+A letöltés automatikusan elindul. Ha mégsem indulna el, vagy megszakítottad, az alábbi linkre kattintva újra letöltheted:
+
+$3
+
+'''Megjegyzés''': Ha ezt most nem teszed meg, és kilépsz a telepítésből, az elkészített konfigurációs fájlt nem tudod elérni a későbbiekben.
+
+Ha végeztél a fájl elhelyezésével, '''[$2 beléphetsz a wikibe]'''.",
+ 'config-download-localsettings' => 'LocalSettings.php letöltése',
+ 'config-help' => 'segítség',
+);
+
+/** Interlingua (Interlingua)
+ * @author McDutchie
+ */
+$messages['ia'] = array(
+ 'config-desc' => 'Le installator de MediaWiki',
+ 'config-title' => 'Installation de MediaWiki $1',
+ 'config-information' => 'Information',
+ 'config-localsettings-upgrade' => 'Un file <code>LocalSettings.php</code> ha essite detegite.
+Pro actualisar iste installation, per favor entra le valor de <code>$wgUpgradeKey</code> in le quadro hic infra.
+Iste se trova in LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Un file LocalSettings.php file ha essite detegite.
+Pro actualisar iste installation, per favor executa upgrade.php.',
+ 'config-localsettings-key' => 'Clave de actualisation:',
+ 'config-localsettings-badkey' => 'Le clave que tu forniva es incorrecte',
+ 'config-upgrade-key-missing' => 'Un installation existente de MediaWiki ha essite detegite.
+Pro actualisar iste installation, es necessari adjunger le sequente linea al fin del file LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'Le file LocalSettings.php existente pare esser incomplete.
+Le variabile $1 non es definite.
+Per favor cambia LocalSettings.php de sorta que iste variabile es definite, e clicca "Continuar".',
+ 'config-localsettings-connection-error' => 'Un error esseva incontrate durante le connexion al base de datos usante le configurationes specificate in LocalSettings.php o AdminSettings.php. Per favor repara iste configurationes e tenta lo de novo.
+
+$1',
+ 'config-session-error' => 'Error al comenciamento del session: $1',
+ 'config-session-expired' => 'Le datos de tu session pare haber expirate.
+Le sessiones es configurate pro un duration de $1.
+Tu pote augmentar isto per definir <code>session.gc_maxlifetime</code> in php.ini.
+Reinitia le processo de installation.',
+ 'config-no-session' => 'Le datos de tu session es perdite!
+Verifica tu php.ini e assecura te que un directorio appropriate es definite in <code>session.save_path</code>.',
+ 'config-your-language' => 'Tu lingua:',
+ 'config-your-language-help' => 'Selige un lingua a usar durante le processo de installation.',
+ 'config-wiki-language' => 'Lingua del wiki:',
+ 'config-wiki-language-help' => 'Selige le lingua in que le wiki essera predominantemente scribite.',
+ 'config-back' => '← Retro',
+ 'config-continue' => 'Continuar →',
+ 'config-page-language' => 'Lingua',
+ 'config-page-welcome' => 'Benvenite a MediaWiki!',
+ 'config-page-dbconnect' => 'Connecter al base de datos',
+ 'config-page-upgrade' => 'Actualisar le installation existente',
+ 'config-page-dbsettings' => 'Configuration del base de datos',
+ 'config-page-name' => 'Nomine',
+ 'config-page-options' => 'Optiones',
+ 'config-page-install' => 'Installar',
+ 'config-page-complete' => 'Complete!',
+ 'config-page-restart' => 'Reinitiar installation',
+ 'config-page-readme' => 'Lege me',
+ 'config-page-releasenotes' => 'Notas del version',
+ 'config-page-copying' => 'Copiar',
+ 'config-page-upgradedoc' => 'Actualisar',
+ 'config-page-existingwiki' => 'Wiki existente',
+ 'config-help-restart' => 'Vole tu rader tote le datos salveguardate que tu ha entrate e reinitiar le processo de installation?',
+ 'config-restart' => 'Si, reinitia lo',
+ 'config-welcome' => '=== Verificationes del ambiente ===
+Verificationes de base es exequite pro determinar si iste ambiente es apte pro le installation de MediaWiki.
+Tu deberea indicar le resultatos de iste verificationes si tu ha besonio de adjuta durante le installation.',
+ 'config-copyright' => "=== Copyright and Terms ===
+
+$1
+
+Iste programma es software libere; vos pote redistribuer lo e/o modificar lo sub le conditiones del Licentia Public General de GNU publicate per le Free Software Foundation; version 2 del Licentia, o (a vostre option) qualcunque version posterior.
+
+Iste programma es distribuite in le sperantia que illo sia utile, ma '''sin garantia''', sin mesmo le implicite garantia de '''commercialisation''' o '''aptitude pro un proposito particular'''.
+Vide le Licentia Public General de GNU pro plus detalios.
+
+Vos deberea haber recipite <doclink href=Copying>un exemplar del Licentia Public General de GNU</doclink> con iste programma; si non, scribe al Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [http://www.gnu.org/copyleft/gpl.html lege lo in linea].",
+ 'config-sidebar' => '* [http://www.mediawiki.org Pagina principal de MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Guida pro usatores]
+* [http://www.mediawiki.org/wiki/Manual:Contents Guida pro administratores]
+* [http://www.mediawiki.org/wiki/Manual:FAQ FAQ]
+----
+* <doclink href=Readme>Lege me</doclink>
+* <doclink href=ReleaseNotes>Notas de iste version</doclink>
+* <doclink href=Copying>Conditiones de copia</doclink>
+* <doclink href=UpgradeDoc>Actualisation</doclink>',
+ 'config-env-good' => 'Le ambiente ha essite verificate.
+Tu pote installar MediaWiki.',
+ 'config-env-bad' => 'Le ambiente ha essite verificate.
+Tu non pote installar MediaWiki.',
+ 'config-env-php' => 'PHP $1 es installate.',
+ 'config-env-php-toolow' => 'PHP $1 es installate.
+Nonobstante, MediaWiki require PHP $2 o plus recente.',
+ 'config-unicode-using-utf8' => 'utf8_normalize.so per Brion Vibber es usate pro le normalisation Unicode.',
+ 'config-unicode-using-intl' => 'Le [http://pecl.php.net/intl extension PECL intl] es usate pro le normalisation Unicode.',
+ 'config-unicode-pure-php-warning' => "'''Aviso''': Le [http://pecl.php.net/intl extension PECL intl] non es disponibile pro exequer le normalisation Unicode; le systema recurre al implementation lente in PHP pur.
+Si tu sito ha un alte volumine de traffico, tu deberea informar te un poco super le [http://www.mediawiki.org/wiki/Unicode_normalization_considerations normalisation Unicode].",
+ 'config-unicode-update-warning' => "'''Aviso''': Le version installate del bibliotheca inveloppante pro normalisation Unicode usa un version ancian del bibliotheca del [http://site.icu-project.org/ projecto ICU].
+Tu deberea [http://www.mediawiki.org/wiki/Unicode_normalization_considerations actualisar lo] si le uso de Unicode importa a te.",
+ 'config-no-db' => 'Non poteva trovar un driver appropriate pro le base de datos!',
+ 'config-no-db-help' => 'Tu debe installar un driver de base de datos pro PHP.
+Le sequente typos de base de datos es supportate: $1.
+
+Si tu sito usa un servitor partite (shared hosting), demanda a tu providitor de installar un driver de base de datos appropriate.
+Si tu compilava PHP tu mesme, reconfigura lo con un cliente de base de datos activate, per exemplo usante <code>./configure --with-mysql</code>.
+Si tu installava PHP ex un pacchetto Debian o Ubuntu, tu debe installar equalmente le modulo php5-mysql.',
+ 'config-no-fts3' => "'''Attention''': SQLite es compilate sin [http://sqlite.org/fts3.html modulo FTS3]; functionalitate de recerca non essera disponibile in iste back-end.",
+ 'config-register-globals' => "'''Attention: le option <code>[http://php.net/register_globals register_globals]</code> de PHP es activate.'''
+'''Disactiva lo si tu pote.'''
+MediaWiki functionara, ma tu servitor es exponite a potential vulnerabilitates de securitate.",
+ 'config-magic-quotes-runtime' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] es active!'''
+Iste option corrumpe le entrata de datos imprevisibilemente.
+Tu non pote installar o usar MediaWiki si iste option non es disactivate.",
+ 'config-magic-quotes-sybase' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] es active!'''
+Iste option corrumpe le entrata de datos imprevisibilemente.
+Tu non pote installar o usar MediaWiki si iste option non es disactivate.",
+ 'config-mbstring' => "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] es active!'''
+Iste option causa errores e pote corrumper datos imprevisibilemente.
+Tu non pote installar o usar MediaWiki si iste option non es disactivate.",
+ 'config-ze1' => "'''Fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] es active!'''
+Iste option causa horribile defectos con MediaWiki.
+Tu non pote installar o usar MediaWiki si iste option non es disactivate.",
+ 'config-safe-mode' => "'''Aviso:''' Le [http://www.php.net/features.safe-mode modo secur] de PHP es active.
+Isto pote causar problemas, particularmente si es usate le incargamento de files e le supporto de <code>math</code>.",
+ 'config-xml-bad' => 'Le modulo XML de PHP es mancante.
+MediaWiki require functiones de iste modulo e non functionara in iste configuration.
+Si tu usa Mandrake, installa le pacchetto php-xml.',
+ 'config-pcre' => 'Le modulo de supporto PCRE pare esser mancante.
+MediaWiki require le functiones de expression regular compatibile con Perl pro poter functionar.',
+ 'config-pcre-no-utf8' => "'''Fatal''': Le modulo PCRE de PHP pare haber essite compilate sin supporto de PCRE_UTF8.
+MediaWiki require supporto de UTF-8 pro functionar correctemente.",
+ 'config-memory-raised' => 'Le <code>memory_limit</code> de PHP es $1, elevate a $2.',
+ 'config-memory-bad' => "'''Aviso:''' Le <code>memory_limit</code> de PHP es $1.
+Isto es probabilemente troppo basse.
+Le installation pote faller!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] es installate',
+ 'config-apc' => '[http://www.php.net/apc APC] es installate',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] es installate',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] es installate',
+ 'config-no-cache' => "'''Aviso:''' Non poteva trovar [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
+Le cache de objectos non es activate.",
+ 'config-diff3-bad' => 'GNU diff3 non trovate.',
+ 'config-imagemagick' => 'ImageMagick trovate: <code>$1</code>.
+Le miniaturas de imagines essera activate si tu activa le incargamento de files.',
+ 'config-gd' => 'Le bibliotheca graphic GD se trova integrate in le systema.
+Le miniaturas de imagines essera activate si tu activa le incargamento de files.',
+ 'config-no-scaling' => 'Non poteva trovar le bibliotheca GD ni ImageMagick.
+Le miniaturas de imagines essera disactivate.',
+ 'config-no-uri' => "'''Error:''' Non poteva determinar le URI actual.
+Installation abortate.",
+ 'config-uploads-not-safe' => "'''Aviso:''' Le directorio predefinite pro files incargate <code>$1</code> es vulnerabile al execution arbitrari de scripts.
+Ben que MediaWiki verifica tote le files incargate contra le menacias de securitate, il es altemente recommendate [http://www.mediawiki.org/wiki/Manual:Security#Upload_security remediar iste vulnerabilitate de securitate] ante de activar le incargamento de files.",
+ 'config-brokenlibxml' => 'Vostre systema ha un combination de versiones de PHP e libxml2 que es defectuose e pote causar corruption celate de datos in MediaWiki e altere applicationes web.
+Actualisa a PHP 5.2.9 o plus recente e libxml2 2.7.3 o plus recente ([http://bugs.php.net/bug.php?id=45996 problema reportate presso PHP]).
+Installation abortate.',
+ 'config-using531' => 'MediaWiki non pote esser usate con PHP $1 a causa de un defecto concernente parametros de referentia a <code>__call()</code>.
+Actualisa a PHP 5.3.2 o plus recente, o retrograda a PHP 5.3.0 pro remediar isto.
+Installation abortate.',
+ 'config-db-type' => 'Typo de base de datos:',
+ 'config-db-host' => 'Servitor de base de datos:',
+ 'config-db-host-help' => 'Si tu servitor de base de datos es in un altere servitor, entra hic le nomine o adresse IP del servitor.
+
+Si tu usa un servitor web usate in commun, tu providitor deberea dar te le correcte nomine de servitor in su documentation.
+
+Si tu face le installation in un servitor Windows e usa MySQL, le nomine "localhost" possibilemente non functiona como nomine de servitor. Si non, essaya "127.0.0.1", i.e. le adresse IP local.',
+ 'config-db-host-oracle' => 'TNS del base de datos:',
+ 'config-db-host-oracle-help' => 'Entra un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nomine Local Connect] valide; un file tnsnames.ora debe esser visibile a iste installation.<br />Si tu usa bibliothecas de cliente 10g o plus recente, tu pote anque usar le methodo de nomination [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Identificar iste wiki',
+ 'config-db-name' => 'Nomine del base de datos:',
+ 'config-db-name-help' => 'Selige un nomine que identifica tu wiki.
+Illo non pote continer spatios.
+
+Si tu usa un servitor web usate in commun, tu providitor te fornira le nomine specific de un base de datos a usar, o te permitte crear un base de datos via un pannello de controlo.',
+ 'config-db-name-oracle' => 'Schema del base de datos:',
+ 'config-db-account-oracle-warn' => 'Il ha tres scenarios supportate pro le installation de Oracle como le base de datos de iste systema:
+
+Si tu vole crear un conto del base de datos como parte del processo de installation, per favor specifica un conto con le rolo SYSDBA como le conto del base de datos pro installation, e specifica le nomine e contrasigno desirate pro le conto de accesso per web. Alteremente tu pote crear le conto de accesso per web manualmente e specificar solmente iste conto (si illo ha le permissiones requisite pro crear le objectos de schema) o specifica duo contos differente, un con privilegios de creation e un conto restringite pro accesso per web.
+
+Un script pro crear un conto con le privilegios requisite se trova in le directorio "maintenance/oracle/" de iste installation. Non oblida que le uso de un conto restringite disactiva tote le capacitates de mantenentia in le conto predefinite.',
+ 'config-db-install-account' => 'Conto de usator pro installation',
+ 'config-db-username' => 'Nomine de usator del base de datos:',
+ 'config-db-password' => 'Contrasigno del base de datos:',
+ 'config-db-password-empty' => 'Per favor entra un contrasigno pro le nove usator del base de datos: $1.
+Ben que il es possibile crear usatores sin contrasigno, isto non es secur.',
+ 'config-db-install-username' => 'Entra le nomine de usator que essera usate pro connecter al base de datos durante le processo de installation. Isto non es le nomine de usator del conto MediaWiki; isto es le nomine de usator pro tu base de datos.',
+ 'config-db-install-password' => 'Entra le contrasigno que essera usate pro connecter al base de datos durante le processo de installation. Isto non es le contrasigno del conto MediaWiki; isto es le contrasigno pro tu base de datos.',
+ 'config-db-install-help' => 'Entra le nomine de usator e contrasigno que essera usate pro connecter al base de datos durante le processo de installation.',
+ 'config-db-account-lock' => 'Usar le mesme nomine de usator e contrasigno durante le operation normal',
+ 'config-db-wiki-account' => 'Conto de usator pro operation normal',
+ 'config-db-wiki-help' => 'Entra le nomine de usator e contrasigno que essera usate pro connecter al base de datos durante le operation normal del wiki.
+Si le conto non existe, e si le conto de installation possede sufficiente privilegios, iste conto de usator essera create con le minime privilegios necessari pro operar le wiki.',
+ 'config-db-prefix' => 'Prefixo de tabella del base de datos:',
+ 'config-db-prefix-help' => 'Si il es necessari usar un base de datos in commun inter multiple wikis, o inter MediaWiki e un altere application web, tu pote optar pro adder un prefixo a tote le nomines de tabella pro evitar conflictos.
+Non usa spatios.
+
+Iste campo usualmente resta vacue.',
+ 'config-db-charset' => 'Codification de characteres in le base de datos',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binari',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 retrocompatibile UTF-8',
+ 'config-charset-help' => "'''Aviso:''' Si tu usa '''UTF-8 retrocompatibile''' sur MySQL 4.1+, e postea face un copia de reserva del base de datos con <code>mysqldump</code>, tote le characteres non ASCII pote esser destruite, resultante in corruption irreversibile de tu copias de reserva!
+
+In '''modo binari''', MediaWiki immagazina texto in UTF-8 in le base de datos in campos binari.
+Isto es plus efficiente que le modo UTF-8 de MySQL, e permitte usar le rango complete de characteres de Unicode.
+In '''modo UTF-8''', MySQL sapera in qual codification de characteres tu datos es, e pote presentar e converter lo appropriatemente,
+ma non te permittera immagazinar characteres supra le [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Basic].",
+ 'config-mysql-old' => 'MySQL $1 o plus recente es requirite, tu ha $2.',
+ 'config-db-port' => 'Porto de base de datos:',
+ 'config-db-schema' => 'Schema pro MediaWiki',
+ 'config-db-schema-help' => 'Iste schema es generalmente correcte.
+Solmente cambia lo si tu es secur que es necessari.',
+ 'config-sqlite-dir' => 'Directorio pro le datos de SQLite:',
+ 'config-sqlite-dir-help' => "SQLite immagazina tote le datos in un sol file.
+
+Le directorio que tu forni debe permitter le accesso de scriptura al servitor web durante le installation.
+
+Illo '''non''' debe esser accessibile via web. Pro isto, nos non lo pone ubi tu files PHP es.
+
+Le installator scribera un file <code>.htaccess</code> insimul a illo, ma si isto falli, alcuno pote ganiar accesso directe a tu base de datos.
+Isto include le crude datos de usator (adresses de e-mail, contrasignos codificate) assi como versiones delite e altere datos restringite super le wiki.
+
+Considera poner le base de datos in un loco completemente differente, per exemplo in <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Spatio de tabellas predefinite:',
+ 'config-oracle-temp-ts' => 'Spatio de tabellas temporari:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki supporta le sequente systemas de base de datos:
+
+$1
+
+Si tu non vide hic infra le systema de base de datos que tu tenta usar, alora seque le instructiones ligate hic supra pro activar le supporto.',
+ 'config-support-mysql' => '* $1 es le systema primari pro MediaWiki e le melio supportate ([http://www.php.net/manual/en/mysql.installation.php como compilar PHP con supporto de MySQL])',
+ 'config-support-postgres' => '* $1 es un systema de base de datos popular e open source, alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar PHP con supporto de PostgreSQL]). Es possibile que resta alcun minor defectos non resolvite, dunque illo non es recommendate pro uso in un ambiente de production.',
+ 'config-support-sqlite' => '* $1 es un systema de base de datos legier que es multo ben supportate. ([http://www.php.net/manual/en/pdo.installation.php Como compilar PHP con supporto de SQLite], usa PDO)',
+ 'config-support-oracle' => '* $1 es un banca de datos commercial pro interprisas. ([http://www.php.net/manual/en/oci8.installation.php Como compilar PHP con supporto de OCI8])',
+ 'config-header-mysql' => 'Configuration de MySQL',
+ 'config-header-postgres' => 'Configuration de PostgreSQL',
+ 'config-header-sqlite' => 'Configuration de SQLite',
+ 'config-header-oracle' => 'Configuration de Oracle',
+ 'config-invalid-db-type' => 'Typo de base de datos invalide',
+ 'config-missing-db-name' => 'Tu debe entrar un valor pro "Nomine de base de datos"',
+ 'config-missing-db-host' => 'Tu debe entrar un valor pro "Host del base de datos"',
+ 'config-missing-db-server-oracle' => 'You must enter a value for "TNS del base de datos"',
+ 'config-invalid-db-server-oracle' => 'TNS de base de datos "$1" invalide.
+Usa solmente litteras ASCII (a-z, A-Z), numeros (0-9), characteres de sublineamento (_) e punctos (.).',
+ 'config-invalid-db-name' => 'Nomine de base de datos "$1" invalide.
+Usa solmente litteras ASCII (a-z, A-Z), numeros (0-9), characteres de sublineamento (_) e tractos de union (-).',
+ 'config-invalid-db-prefix' => 'Prefixo de base de datos "$1" invalide.
+Usa solmente litteras ASCII (a-z, A-Z), numeros (0-9), characteres de sublineamento (_) e tractos de union (-).',
+ 'config-connection-error' => '$1.
+
+Verifica le servitor, nomine de usator e contrasigno hic infra e reproba.',
+ 'config-invalid-schema' => 'Schema invalide pro MediaWiki "$1".
+Usa solmente litteras ASCII (a-z, A-Z), numeros (0-9) e characteres de sublineamento (_).',
+ 'config-db-sys-create-oracle' => 'Le installator supporta solmente le uso de un conto SYSDBA pro le creation de un nove conto.',
+ 'config-db-sys-user-exists-oracle' => 'Le conto de usator "$1" ja existe. SYSDBA pote solmente esser usate pro le creation de un nove conto!',
+ 'config-postgres-old' => 'PostgreSQL $1 o plus recente es requirite, tu ha $2.',
+ 'config-sqlite-name-help' => 'Selige un nomine que identifica tu wiki.
+Non usar spatios o tractos de union.
+Isto essera usate pro le nomine del file de datos de SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Impossibile crear le directorio de datos <code><nowiki>$1</nowiki></code>, proque le directorio superjacente <code><nowiki>$2</nowiki></code> non concede le accesso de scriptura al servitor web.
+
+Le installator ha determinate le usator sub que le servitor web es executate.
+Concede le accesso de scriptura in le directorio <code><nowiki>$3</nowiki></code> a iste usator pro continuar.
+In un systema Unix/Linux:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Impossibile crear le directorio de datos <code><nowiki>$1</nowiki></code>, proque le directorio superjacente <code><nowiki>$2</nowiki></code> non concede le accesso de scriptura al servitor web.
+
+Le installator non poteva determinar le usator sub que le servitor web es executate.
+Concede le accesso de scriptura in le directorio <code><nowiki>$3</nowiki></code> a iste usator (e alteres!) pro continuar.
+In un systema Unix/Linux:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Error al creation del directorio de datos "$1".
+Verifica le loco e reproba.',
+ 'config-sqlite-dir-unwritable' => 'Impossibile scriber in le directorio "$1".
+Cambia su permissiones de sorta que le servitor web pote scriber in illo, e reproba.',
+ 'config-sqlite-connection-error' => '$1.
+
+Verifica le directorio de datos e le nomine de base de datos hic infra e reproba.',
+ 'config-sqlite-readonly' => 'Le file <code>$1</code> non es accessibile pro scriptura.',
+ 'config-sqlite-cant-create-db' => 'Non poteva crear le file de base de datos <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'PHP non ha supporto pro FTS3. Le tabellas es retrogradate.',
+ 'config-can-upgrade' => "Il ha tabellas MediaWiki in iste base de datos.
+Pro actualisar los a MediaWiki $1, clicca super '''Continuar'''.",
+ 'config-upgrade-done' => "Actualisation complete.
+
+Tu pote ora [$1 comenciar a usar tu wiki].
+
+Si tu vole regenerar tu file <code>LocalSettings.php</code>, clicca super le button hic infra.
+Isto '''non es recommendate''' si tu non ha problemas con tu wiki.",
+ 'config-upgrade-done-no-regenerate' => 'Actualisation complete.
+
+Tu pote ora [$1 comenciar a usar tu wiki].',
+ 'config-regenerate' => 'Regenerar LocalSettings.php →',
+ 'config-show-table-status' => 'Le consulta SHOW TABLE STATUS falleva!',
+ 'config-unknown-collation' => "'''Aviso:''' Le base de datos usa un collation non recognoscite.",
+ 'config-db-web-account' => 'Conto de base de datos pro accesso via web',
+ 'config-db-web-help' => 'Selige le nomine de usator e contrasigno que le servitor web usara pro connecter al servitor de base de datos, durante le operation ordinari del wiki.',
+ 'config-db-web-account-same' => 'Usar le mesme conto que pro le installation',
+ 'config-db-web-create' => 'Crear le conto si illo non jam existe',
+ 'config-db-web-no-create-privs' => 'Le conto que tu specificava pro installation non ha sufficiente privilegios pro crear un conto.
+Le conto que tu specifica hic debe jam exister.',
+ 'config-mysql-engine' => 'Motor de immagazinage:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' es quasi sempre le melior option, post que illo ha bon supporto pro simultaneitate.
+
+'''MyISAM''' pote esser plus rapide in installationes a usator singule o a lectura solmente.
+Le bases de datos MyISAM tende a esser corrumpite plus frequentemente que le base de datos InnoDB.",
+ 'config-mysql-charset' => 'Codification de characteres in le base de datos:',
+ 'config-mysql-binary' => 'Binari',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "In '''modo binari''', MediaWiki immagazina le texto UTF-8 in le base de datos in campos binari.
+Isto es plus efficiente que le modo UTF-8 de MySQL, e permitte usar le rango complete de characteres Unicode.
+
+In '''modo UTF-8''', MySQL cognoscera le codification de characteres usate pro tu dats, e pote presentar e converter lo appropriatemente, ma illo non permittera immagazinar characteres supra le [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Basic].",
+ 'config-site-name' => 'Nomine del wiki:',
+ 'config-site-name-help' => 'Isto apparera in le barra de titulo del navigator e in varie altere locos.',
+ 'config-site-name-blank' => 'Entra un nomine de sito.',
+ 'config-project-namespace' => 'Spatio de nomines del projecto:',
+ 'config-ns-generic' => 'Projecto',
+ 'config-ns-site-name' => 'Mesme nomine que le wiki: $1',
+ 'config-ns-other' => 'Altere (specifica)',
+ 'config-ns-other-default' => 'MiWiki',
+ 'config-project-namespace-help' => 'Sequente le exemplo de Wikipedia, multe wikis tene lor paginas de politica separate de lor paginas de contento, in un "\'\'\'spatio de nomines de projecto\'\'\'".
+Tote le titulos de pagina in iste spatio de nomines comencia con un certe prefixo, le qual tu pote specificar hic.
+Traditionalmente, iste prefixo deriva del nomine del wiki, ma illo non pote continer characteres de punctuation como "#" o ":".',
+ 'config-ns-invalid' => 'Le spatio de nomines specificate "<nowiki>$1</nowiki>" es invalide.
+Specifica un altere spatio de nomines de projecto.',
+ 'config-ns-conflict' => 'Le spatio de nomines specificate "<nowiki>$1</nowiki>" conflige con un spatio de nomines predefinite de MediaWiki.
+Specifica un altere spatio de nomines pro le projecto.',
+ 'config-admin-box' => 'Conto de administrator',
+ 'config-admin-name' => 'Tu nomine:',
+ 'config-admin-password' => 'Contrasigno:',
+ 'config-admin-password-confirm' => 'Repete contrasigno:',
+ 'config-admin-help' => 'Entra hic tu nomine de usator preferite, per exemplo "Julio Cesare".
+Isto es le nomine que tu usara pro aperir session in le wiki.',
+ 'config-admin-name-blank' => 'Entra un nomine de usator pro administrator.',
+ 'config-admin-name-invalid' => 'Le nomine de usator specificate "<nowiki>$1</nowiki>" es invalide.
+Specifica un altere nomine de usator.',
+ 'config-admin-password-blank' => 'Entra un contrasigno pro le conto de administrator.',
+ 'config-admin-password-same' => 'Le contrasigno non pote esser le mesme que le nomine de usator.',
+ 'config-admin-password-mismatch' => 'Le duo contrasignos que tu scribeva non es identic.',
+ 'config-admin-email' => 'Adresse de e-mail:',
+ 'config-admin-email-help' => 'Entra un adresse de e-mail hic pro permitter le reception de e-mail ab altere usatores del wiki, pro poter reinitialisar tu contrasigno, e pro reciper notification de cambios a paginas in tu observatorio. Iste campo pote esser lassate vacue.',
+ 'config-admin-error-user' => 'Error interne durante le creation de un administrator con le nomine "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Error interne durante le definition de un contrasigno pro le administrator "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Tu ha entrate un adresse de e-mail invalide',
+ 'config-subscribe' => 'Subscribe al [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de diffusion pro annuncios de nove versiones].',
+ 'config-subscribe-help' => 'Isto es un lista de e-mail a basse volumine pro annuncios de nove versiones, includente importante annuncios de securitate.
+Tu deberea subscriber a illo e actualisar tu installation de MediaWiki quando nove versiones es editate.',
+ 'config-almost-done' => 'Tu ha quasi finite!
+Tu pote ora saltar le configuration remanente e installar le wiki immediatemente.',
+ 'config-optional-continue' => 'Pone me plus questiones.',
+ 'config-optional-skip' => 'Isto me es jam tediose. Simplemente installa le wiki.',
+ 'config-profile' => 'Profilo de derectos de usator:',
+ 'config-profile-wiki' => 'Wiki traditional',
+ 'config-profile-no-anon' => 'Creation de conto obligatori',
+ 'config-profile-fishbowl' => 'Modificatores autorisate solmente',
+ 'config-profile-private' => 'Wiki private',
+ 'config-profile-help' => "Le wikis functiona melio si tu permitte a tante personas como possibile de modificar los.
+In MediaWiki, il es facile revider le modificationes recente, e reverter omne damno facite per usatores naive o malitiose.
+
+Nonobstante, multes ha trovate MediaWiki utile in un grande varietate de rolos, e alcun vices il non es facile convincer omnes del beneficios del principio wiki.
+Dunque, a te le option.
+
+Un '''{{int:config-profile-wiki}}''' permitte a omnes de modificar, sin mesmo aperir un session.
+Un wiki con '''{{int:config-profile-no-anon}}''' attribue additional responsabilitate, ma pote dissuader contributores occasional.
+
+Le scenario '''{{int:config-profile-fishbowl}}''' permitte al usatores approbate de modificar, ma le publico pote vider le paginas, includente lor historia.
+Un '''{{int:config-profile-private}}''' permitte solmente al usatores approbate de vider le paginas e de modificar los.
+
+Configurationes de derectos de usator plus complexe es disponibile post installation, vide le [http://www.mediawiki.org/wiki/Manual:User_rights pertinente section del manual].",
+ 'config-license' => 'Copyright e licentia:',
+ 'config-license-none' => 'Nulle licentia in pede de paginas',
+ 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
+ 'config-license-cc-0' => 'Creative Commons Zero',
+ 'config-license-gfdl-old' => 'Licentia GNU pro Documentation Libere 1.2',
+ 'config-license-gfdl-current' => 'Licentia GNU pro Documentation Libere 1.3 o plus recente',
+ 'config-license-pd' => 'Dominio public',
+ 'config-license-cc-choose' => 'Seliger un licentia Creative Commons personalisate',
+ 'config-license-help' => "Multe wikis public pone tote le contributiones sub un [http://freedomdefined.org/Definition/Ia?uselang=ia licentia libere].
+Isto adjuta a crear un senso de proprietate communitari e incoragia le contribution in longe termino.
+Isto non es generalmente necessari pro un wiki private o de interprisa.
+
+Si tu vole poter usar texto de Wikipedia, e si tu vole que Wikipedia pote acceptar texto copiate de tu wiki, tu debe seliger '''Creative Commons Attribution Share Alike'''.
+
+Le Licentia GNU pro Documentation Libere esseva le ancian licentia de publication de Wikipedia.
+Iste licentia continua a esser valide, ma illo ha alcun characteristicas que rende le re-uso e interpretation difficile.",
+ 'config-email-settings' => 'Configuration de e-mail',
+ 'config-enable-email' => 'Activar le e-mail sortiente',
+ 'config-enable-email-help' => 'Si tu vole que e-mail functiona, [http://www.php.net/manual/en/mail.configuration.php le optiones de e-mail de PHP] debe esser configurate correctemente.
+Si tu non vole functiones de e-mail, tu pote disactivar los hic.',
+ 'config-email-user' => 'Activar le e-mail de usator a usator',
+ 'config-email-user-help' => 'Permitter a tote le usatores de inviar e-mail inter se, si illes lo ha activate in lor preferentias.',
+ 'config-email-usertalk' => 'Activar notification de cambios in paginas de discussion de usatores',
+ 'config-email-usertalk-help' => 'Permitter al usatores de reciper notification de modificationes in lor paginas de discussion personal, si illes lo ha activate in lor preferentias.',
+ 'config-email-watchlist' => 'Activar notification de observatorio',
+ 'config-email-watchlist-help' => 'Permitter al usatores de reciper notification super lor paginas sub observation, si illes lo ha activate in lor preferentias.',
+ 'config-email-auth' => 'Activar authentication de e-mail',
+ 'config-email-auth-help' => "Si iste option es activate, le usatores debe confirmar lor adresse de e-mail usante un ligamine inviate a illes, quandocunque illes lo defini o cambia.
+Solmente le adresses de e-mail authenticate pote reciper e-mail de altere usatores o alterar le e-mails de notification.
+Es '''recommendate''' activar iste option pro wikis public a causa de abuso potential del functionalitate de e-mail.",
+ 'config-email-sender' => 'Adresse de e-mail de retorno:',
+ 'config-email-sender-help' => 'Entra le adresse de e-mail a usar como adresse de retorno in e-mail sortiente.
+Hic es recipite le notificationes de non-livration.
+Multe servitores de e-mail require que al minus le parte de nomine de dominio sia valide.',
+ 'config-upload-settings' => 'Incargamento de imagines e files',
+ 'config-upload-enable' => 'Activar le incargamento de files',
+ 'config-upload-help' => 'Le incargamento de files potentialmente expone tu servitor a riscos de securitate.
+Pro plus information, lege le [http://www.mediawiki.org/wiki/Manual:Security section de securitate] in le manual.
+
+Pro activar le incargamento de files, cambia le modo in le subdirectorio <code>images</code> sub le directorio-radice de MediaWiki de sorta que le servitor web pote scriber in illo.
+Postea activa iste option.',
+ 'config-upload-deleted' => 'Directorio pro files delite:',
+ 'config-upload-deleted-help' => 'Selige un directorio in le qual archivar le files delite.
+Idealmente, isto non debe esser accessibile ab le web.',
+ 'config-logo' => 'URL del logotypo:',
+ 'config-logo-help' => 'Le apparentia predefinite de MediaWiki include spatio pro un logotypo de 135×160 pixeles in le angulo superior sinistre.
+Incarga un imagine con le dimensiones appropriate, e entra le URL hic.
+
+Si tu non vole un logotypo, lassa iste quadro vacue.',
+ 'config-instantcommons' => 'Activar "Instant Commons"',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] es un function que permitte a wikis de usar imagines, sonos e altere multimedia trovate in le sito [http://commons.wikimedia.org/ Wikimedia Commons].
+Pro poter facer isto, MediaWiki require accesso a Internet.
+
+Pro plus information super iste function, includente instructiones super como configurar lo pro wikis altere que Wikimedia Commons, consulta [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos le manual].',
+ 'config-cc-error' => 'Le selector de licentia Creative Commons non dava un resultato.
+Entra le nomine del licentia manualmente.',
+ 'config-cc-again' => 'Selige de novo…',
+ 'config-cc-not-chosen' => 'Selige le licentia Creative Commons que tu prefere e clicca "proceder".',
+ 'config-advanced-settings' => 'Configuration avantiate',
+ 'config-cache-options' => 'Configuration del cache de objectos:',
+ 'config-cache-help' => 'Le cache de objectos es usate pro meliorar le rapiditate de MediaWiki per immagazinar le datos frequentemente usate.
+Le sitos medie o grande es multo incoragiate de activar isto, ma anque le sitos parve percipera le beneficios.',
+ 'config-cache-none' => 'Nulle cache (nulle functionalitate es removite, ma le rapiditate pote diminuer in grande sitos wiki)',
+ 'config-cache-accel' => 'Cache de objectos de PHP (APC, eAccelerator, XCache o WinCache)',
+ 'config-cache-memcached' => 'Usar Memcached (require additional installation e configuration)',
+ 'config-memcached-servers' => 'Servitores Memcached:',
+ 'config-memcached-help' => 'Lista de adresses IP a usar pro Memcached.
+Debe specificar un per linea e specificar le porto a usar. Per exemplo:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Tu seligeva Memcached como typo de cache ma non specificava alcun servitores',
+ 'config-memcache-badip' => 'Tu ha entrate un adresse IP invalide pro Memcached: $1',
+ 'config-memcache-noport' => 'Tu non specificava un porto a usar pro le servitor Memcached: $1.
+Si tu non cognosce le porto, le standard es 11211',
+ 'config-memcache-badport' => 'Le numeros de porto de Memcached debe esser inter $1 e $2',
+ 'config-extensions' => 'Extensiones',
+ 'config-extensions-help' => 'Le extensiones listate hic supra esseva detegite in tu directorio <code>./extensions</code>.
+
+Istes pote requirer additional configuration, ma tu pote activar los ora.',
+ 'config-install-alreadydone' => "'''Aviso:''' Il pare que tu ha jam installate MediaWiki e tenta installar lo de novo.
+Per favor continua al proxime pagina.",
+ 'config-install-begin' => 'Un clic sur "{{int:config-continue}}" comencia le installation de MediaWiki.
+Pro facer alterationes, clicca sur "Retro".',
+ 'config-install-step-done' => 'finite',
+ 'config-install-step-failed' => 'fallite',
+ 'config-install-extensions' => 'Include le extensiones',
+ 'config-install-database' => 'Configura le base de datos',
+ 'config-install-pg-schema-not-exist' => 'Iste schema de PostgreSQL non existe',
+ 'config-install-pg-schema-failed' => 'Le creation del tabellas falleva.
+Assecura te que le usator "$1" pote scriber in le schema "$2".',
+ 'config-install-pg-commit' => 'Committer cambiamentos',
+ 'config-install-pg-plpgsql' => 'Verifica le presentia del linguage PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Es necessari installar le linguage PL/pgSQL in le base de datos $1',
+ 'config-pg-no-create-privs' => 'Le conto que tu specificava pro installation non ha sufficiente privilegios pro crear un conto.',
+ 'config-install-user' => 'Crea usator pro base de datos',
+ 'config-install-user-alreadyexists' => 'Le usator "$1" ja existe',
+ 'config-install-user-create-failed' => 'Le creation del usator "$1" ha fallite: $2',
+ 'config-install-user-grant-failed' => 'Le concession de permission al usator "$1" falleva: $2',
+ 'config-install-tables' => 'Crea tabellas',
+ 'config-install-tables-exist' => "'''Aviso''': Il pare que le tabellas de MediaWiki jam existe.
+Le creation es saltate.",
+ 'config-install-tables-failed' => "'''Error''': Le creation del tabellas falleva con le sequente error: $1",
+ 'config-install-interwiki' => 'Plena le tabella interwiki predefinite',
+ 'config-install-interwiki-list' => 'Non poteva trovar le file <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Aviso''': Le tabella interwiki pare jam haber entratas.
+Le lista predefinite es saltate.",
+ 'config-install-stats' => 'Initialisation del statisticas',
+ 'config-install-keys' => 'Genera clave secrete',
+ 'config-install-sysop' => 'Crea conto de usator pro administrator',
+ 'config-install-subscribe-fail' => 'Impossibile subscriber a mediawiki-announce',
+ 'config-install-mainpage' => 'Crea pagina principal con contento predefinite',
+ 'config-install-extension-tables' => 'Creation de tabellas pro le extensiones activate',
+ 'config-install-mainpage-failed' => 'Non poteva inserer le pagina principal: $1',
+ 'config-install-done' => "'''Felicitationes!'''
+Tu ha installate MediaWiki con successo.
+
+Le installator ha generate un file <code>LocalSettings.php</code>.
+Iste contine tote le configuration.
+
+Es necessari discargar lo e poner lo in le base del installation wiki (le mesme directorio que index.php).
+Le discargamento debe haber comenciate automaticamente.
+
+Si le discargamento non ha comenciate, o si illo esseva cancellate, es possibile recomenciar le discargamento con un clic sur le ligamine sequente:
+
+$3
+
+'''Nota''': Si tu non discarga iste file de configuration ora, illo non essera disponibile plus tarde.
+
+Post facer isto, tu pote '''[$2 entrar in tu wiki]'''.",
+ 'config-download-localsettings' => 'Discargar LocalSettings.php',
+ 'config-help' => 'adjuta',
+);
+
+/** Indonesian (Bahasa Indonesia)
+ * @author Farras
+ * @author IvanLanin
+ * @author Reedy
+ */
+$messages['id'] = array(
+ 'config-desc' => 'Penginstal untuk MediaWiki',
+ 'config-title' => 'Instalasi MediaWiki $1',
+ 'config-information' => 'Informasi',
+ 'config-localsettings-upgrade' => 'Berkas <code>LocalSettings.php</code> sudah ada.
+Untuk memutakhirkan instalasi ini, masukkan nilai <code>$wgUpgradeKey</code> dalam kotak yang tersedia di bawah ini.
+Anda dapat menemukan nilai tersebut dalam LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Berkas LocalSettings.php terdeteksi.
+Untuk meningkatkan versi, sertakan pilihan --upgrade=yes.',
+ 'config-localsettings-key' => 'Kunci pemutakhiran:',
+ 'config-localsettings-badkey' => 'Kunci yang Anda berikan tidak benar',
+ 'config-upgrade-key-missing' => 'Suatu instalasi MediaWiki telah terdeteksi.
+Untuk memutakhirkan instalasi ini, silakan masukkan baris berikut di bagian bawah LocalSettings.php Anda:
+
+$1',
+ 'config-localsettings-incomplete' => 'LocalSettings.php yang ada tampaknya tidak lengkap.
+Variabel $1 tidak diatur.
+Silakan ubah LocalSettings.php untuk mengatur variabel ini dan klik "Lanjutkan".',
+ 'config-localsettings-connection-error' => 'Timbul galat saat menghubungkan ke basis data dengan menggunakan setelan yang ditentukan di LocalSettings.php atau AdminSettings.php. Harap perbaiki setelan ini dan coba lagi.
+
+$1',
+ 'config-session-error' => 'Kesalahan sesi mulai: $1',
+ 'config-session-expired' => 'Data sesi tampaknya telah kedaluwarsa.
+Sesi dikonfigurasi untuk berlaku selama $1.
+Anda dapat menaikkannya dengan menetapkan <code>session.gc_maxlifetime</code> dalam php.ini.
+Ulangi proses instalasi.',
+ 'config-no-session' => 'Data sesi Anda hilang!
+Cek php.ini Anda dan pastikan bahwa <code>session.save_path</code> diatur ke direktori yang sesuai.',
+ 'config-your-language' => 'Bahasa Anda:',
+ 'config-your-language-help' => 'Pilih bahasa yang akan digunakan selama proses instalasi.',
+ 'config-wiki-language' => 'Bahasa wiki:',
+ 'config-wiki-language-help' => 'Pilih bahasa yang akan digunakan tulisan-tulisan wiki.',
+ 'config-back' => '← Kembali',
+ 'config-continue' => 'Lanjut →',
+ 'config-page-language' => 'Bahasa',
+ 'config-page-welcome' => 'Selamat datang di MediaWiki',
+ 'config-page-dbconnect' => 'Hubungkan ke basis data',
+ 'config-page-upgrade' => 'Perbarui instalasi yang ada',
+ 'config-page-dbsettings' => 'Pengaturan basis data',
+ 'config-page-name' => 'Nama',
+ 'config-page-options' => 'Pilihan',
+ 'config-page-install' => 'Instal',
+ 'config-page-complete' => 'Selesai!',
+ 'config-page-restart' => 'Ulangi instalasi',
+ 'config-page-readme' => 'Baca saya',
+ 'config-page-releasenotes' => 'Catatan pelepasan',
+ 'config-page-copying' => 'Menyalin',
+ 'config-page-upgradedoc' => 'Memerbarui',
+ 'config-page-existingwiki' => 'Wiki yang ada',
+ 'config-help-restart' => 'Apakah Anda ingin menghapus semua data tersimpan yang telah Anda masukkan dan mengulang proses instalasi?',
+ 'config-restart' => 'Ya, nyalakan ulang',
+ 'config-welcome' => '=== Pengecekan lingkungan ===
+Pengecekan dasar dilakukan untuk melihat apakah lingkungan ini memadai untuk instalasi MediaWiki.
+Anda harus memberikan hasil pemeriksaan ini jika Anda memerlukan bantuan selama instalasi.',
+ 'config-copyright' => "=== Hak cipta dan persyaratan ===
+
+\$1
+
+Program ini adalah perangkat lunak bebas; Anda dapat mendistribusikan dan/atau memodifikasi di bawah persyaratan GNU General Public License seperti yang diterbitkan oleh Free Software Foundation; baik versi 2 lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.
+
+Program ini didistribusikan dengan harapan bahwa itu akan berguna, tetapi '''tanpa jaminan apa pun'''; bahkan tanpa jaminan tersirat untuk '''dapat diperjualbelikan ''' atau '''sesuai untuk tujuan tertentu'''.
+Lihat GNU General Public License untuk lebih jelasnya.
+
+Anda seharusnya telah menerima <doclink href=\"Copying\">salinan dari GNU General Public License</doclink> bersama dengan program ini; jika tidak, kirimkan surat untuk Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. atau [http://www.gnu.org/copyleft/gpl.html baca versi daring].",
+ 'config-sidebar' => '* [http://www.mediawiki.org Halaman utama MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Panduan Pengguna]
+* [http://www.mediawiki.org/wiki/Manual:Contents Panduan Pengurus]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Pertanyaan yang Sering Diajukan]',
+ 'config-env-good' => 'Kondisi telah diperiksa.
+Anda dapat menginstal MediaWiki.',
+ 'config-env-bad' => 'Kondisi telah diperiksa.
+Anda tidak dapat menginstal MediaWiki.',
+ 'config-env-php' => 'PHP $1 diinstal.',
+ 'config-unicode-using-utf8' => 'Menggunakan utf8_normalize.so Brion Vibber untuk normalisasi Unicode.',
+ 'config-unicode-using-intl' => 'Menggunakan [http://pecl.php.net/intl ekstensi PECL intl] untuk normalisasi Unicode.',
+ 'config-unicode-pure-php-warning' => "'''Peringatan''': [http://pecl.php.net/intl Ekstensi intl PECL] untuk menangani normalisasi Unicode tidak tersedia, kembali menggunakan implementasi murni PHP yang lambat.
+Jika Anda menjalankan situs berlalu lintas tinggi, Anda harus sedikit membaca [http://www.mediawiki.org/wiki/Unicode_normalization_considerations normalisasi Unicode].",
+ 'config-unicode-update-warning' => "'''Peringatan''': Versi terinstal dari pembungkus normalisasi Unicode menggunakan versi lama pustaka [http://site.icu-project.org/ proyek ICU].
+Anda harus [http://www.mediawiki.org/wiki/Unicode_normalization_considerations memutakhirkannya] jika Anda ingin menggunakan Unicode.",
+ 'config-no-db' => 'Tidak dapat menemukan pengandar basis data yang sesuai!',
+ 'config-no-db-help' => 'Anda perlu menginstal pengandar basis data untuk PHP.
+Jenis basis data yang didukung: $1.
+
+Jika Anda menggunakan inang bersama, mintalah penyedia inang Anda untuk menginstal pengandar basis data yang cocok.
+Jika Anda mengompilasi sendiri PHP, ubahlah konfigurasinya dengan mengaktifkan klien basis data, misalnya menggunakan <code>./configure --with-mysql</code>.
+Jika Anda menginstal PHP dari paket Debian atau Ubuntu, maka Anda juga perlu menginstal modul php5-mysql.',
+ 'config-no-fts3' => "'''Peringatan''': SQLite dikompilasi tanpa [http://sqlite.org/fts3.html modul FTS3], fitur pencarian tidak akan tersedia pada konfigurasi ini.",
+ 'config-register-globals' => "'''Peringatan: Opsi <code>[http://php.net/register_globals register_globals]</code> PHP diaktifkan.'''
+'''Nonaktifkan kalau bisa.'''
+MediaWiki akan bekerja, tetapi server Anda memiliki potensi kerentanan keamanan.",
+ 'config-magic-quotes-runtime' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] aktif!'''
+Pilihan ini dapat merusak masukan data secara tidak terduga.
+Anda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
+ 'config-magic-quotes-sybase' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic_quotes_sybase magic_quotes_sybase] aktif!'''
+Pilihan ini dapat merusak masukan data secara tidak terduga.
+Anda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
+ 'config-mbstring' => "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] aktif!'' '
+Pilihan ini dapat menyebabkan kesalahan dan kerusakan data yang tidak terduga.
+Anda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
+ 'config-ze1' => "'''Fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] aktif!'''
+Pilihan ini dapat menyebabkan bug yang mengerikan pada MediaWiki.
+Anda tidak dapat menginstal atau menggunakan MediaWiki kecuali pilihan ini dinonaktifkan.",
+ 'config-safe-mode' => "''' Peringatan:''' [http://www.php.net/features.safe-mode Mode aman] PHP aktif.
+Hal ini akan menyebabkan masalah, terutama jika menggunakan pengunggahan berkas dan dukungan <code>math</code>.",
+ 'config-xml-bad' => 'Modul XML PHP hilang.
+MediaWiki membutuhkan fungsi dalam modul ini dan tidak akan bekerja dalam konfigurasi ini.
+Jika Anda menggunakan Mandrake, instal paket php-xml.',
+ 'config-pcre' => 'Modul pendukung PCRE tampaknya hilang.
+MediaWiki memerlukan fungsi persamaan reguler kompatibel Perl untuk bekerja.',
+ 'config-pcre-no-utf8' => "'''Fatal''': Modul PCRE PHP tampaknya dikompilasi tanpa dukungan PCRE_UTF8.
+MediaWiki memerlukan dukungan UTF-8 untuk berfungsi dengan benar.",
+ 'config-memory-raised' => '<code>memory_limit</code> PHP adalah $1, dinaikkan ke $2.',
+ 'config-memory-bad' => "'''Peringatan:''' <code>memory_limit</code> PHP adalah $1.
+Ini terlalu rendah.
+Instalasi terancam gagal!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] telah diinstal',
+ 'config-apc' => '[http://www.php.net/apc APC] telah diinstal',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] telah diinstal',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] telah diinstal',
+ 'config-no-cache' => "'''Peringatan:''' Tidak dapat menemukan [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache], atau [http://www.iis.net/download/WinCacheForPhp WinCache]. Pinggahan obyek tidak dinonaktifkan.",
+ 'config-diff3-bad' => 'GNU diff3 tidak ditemukan.',
+ 'config-imagemagick' => 'ImageMagick ditemukan: <code>$1</code> .
+Pembuatan gambar mini akan diaktifkan jika Anda mengaktifkan pengunggahan.',
+ 'config-gd' => 'Pustaka grafis GD terpasang ditemukan.
+Pembuatan gambar mini akan diaktifkan jika Anda mengaktifkan pengunggahan.',
+ 'config-no-scaling' => 'Pustaka GD atau ImageMagick tidak ditemukan.
+Pembuatan gambar mini dinonaktifkan.',
+ 'config-no-uri' => "'''Kesalahan:''' URI saat ini tidak dapat ditentukan.
+Instalasi dibatalkan.",
+ 'config-uploads-not-safe' => "'''Peringatan:''' Direktori bawaan pengunggahan <code>$1</code> Anda rentan terhadap eksekusi skrip yang sewenang-wenang.
+Meskipun MediaWiki memeriksa semua berkas unggahan untuk ancaman keamanan, sangat dianjurkan untuk [http://www.mediawiki.org/wiki/Manual:Security#Upload_security menutup kerentanan keamanan ini] sebelum mengaktifkan pengunggahan.",
+ 'config-brokenlibxml' => 'Sistem Anda memiliki kombinasi versi PHP dan libxml2 yang memiliki bug dan dapat menyebabkan kerusakan data tersembunyi pada MediaWiki dan aplikasi web lain.
+Mutakhirkan ke PHP 5.2.9 atau yang lebih baru dan libxml2 2.7.3 atau yang lebih baru ([http://bugs.php.net/bug.php?id=45996 arsip bug di PHP]).
+Instalasi dibatalkan.',
+ 'config-using531' => 'PHP $1 tidak kompatibel dengan MediaWiki karena bug yang melibatkan parameter referensi untuk <code>__call()</code> .
+Tingkatkan ke PHP 5.3.2 atau yang lebih baru, atau turunkan ke PHP versi 5.3.0 untuk memperbaiki ini ([http://bugs.php.net/bug.php?id=50394 arsip bug di PHP]).
+Instalasi dibatalkan.',
+ 'config-db-type' => 'Jenis basis data:',
+ 'config-db-host' => 'Inang basis data:',
+ 'config-db-host-help' => 'Jika server basis data Anda berada di server yang berbeda, masukkan nama inang atau alamat IP di sini.
+
+Jika Anda menggunakan inang web bersama, penyedia inang Anda harus memberikan nama inang yang benar di dokumentasi mereka.
+
+Jika Anda menginstal pada server Windows dan menggunakan MySQL, "localhost" mungkin tidak dapat digunakan sebagai nama server. Jika demikian, coba "127.0.0.1" untuk alamat IP lokal.',
+ 'config-db-host-oracle' => 'TNS basis data:',
+ 'config-db-host-oracle-help' => 'Masukkan [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] yang sah; berkas tnsnames.ora harus dapat diakses oleh instalasi ini.<br />Jika Anda menggunakan pustaka klien 10g atau lebih baru, Anda juga dapat menggunakan metode penamaan [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Identifikasi wiki ini',
+ 'config-db-name' => 'Nama basis data:',
+ 'config-db-name-help' => 'Pilih nama yang mengidentifikasikan wiki Anda.
+Nama tersebut tidak boleh mengandung spasi.
+
+Jika Anda menggunakan inang web bersama, penyedia inang Anda dapat memberikan Anda nama basis data khusus untuk digunakan atau mengizinkan Anda membuat basis data melalui panel kontrol.',
+ 'config-db-name-oracle' => 'Skema basis data:',
+ 'config-db-install-account' => 'Akun pengguna untuk instalasi',
+ 'config-db-username' => 'Nama pengguna basis data:',
+ 'config-db-password' => 'Kata sandi basis data:',
+ 'config-db-install-username' => 'Masukkan nama pengguna yang akan digunakan untuk terhubung ke basis data selama proses instalasi.
+Ini bukan nama pengguna akun MediaWiki, melainkan nama pengguna untuk basis data Anda.',
+ 'config-db-install-password' => 'Masukkan sandi yang akan digunakan untuk terhubung ke basis data selama proses instalasi.
+Ini bukan sandi untuk akun MediaWiki, melainkan sandi untuk basis data Anda.',
+ 'config-db-install-help' => 'Masukkan nama pengguna dan sandi yang akan digunakan untuk terhubung ke basis data pada saat proses instalasi.',
+ 'config-db-account-lock' => 'Gunakan nama pengguna dan kata sandi yang sama selama operasi normal',
+ 'config-db-wiki-account' => 'Akun pengguna untuk operasi normal',
+ 'config-db-wiki-help' => 'Masukkan nama pengguna dan sandi yang akan digunakan untuk terhubung ke basis data wiki selama operasi normal.
+Jika akun tidak ada, akun instalasi memiliki hak yang memadai, akun pengguna ini akan dibuat dengan hak akses minimum yang diperlukan untuk mengoperasikan wiki.',
+ 'config-db-prefix' => 'Prefiks tabel basis data:',
+ 'config-db-prefix-help' => 'Jika Anda perlu berbagi satu basis data di antara beberapa wiki, atau antara MediaWiki dan aplikasi web lain, Anda dapat memilih untuk menambahkan prefiks terhadap semua nama tabel demi menghindari konflik.
+Jangan gunakan spasi.
+
+Prefiks ini biasanya dibiarkan kosong.',
+ 'config-db-charset' => 'Set karakter basis data',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 biner',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'UTF-8 yang kompatibel balik dengan MySQL 4.0',
+ 'config-charset-help' => "'''Peringatan:''' Jika Anda menggunakan '''UTF-8 kompatibel balik''' pada MySQL 4.1+, dan kemudian mencadangkan basis data dengan <code>mysqldump</code>, proses itu mungkin menghancurkan semua karakter non-ASCII dan merusak cadangan Anda tanpa dapat dikembalikan!
+
+Dalam '''modus biner''', MediaWiki menyimpan teks UTF-8 ke basis data dalam bidang biner.
+Ini lebih efisien dibandingkan modus UTF-8 MySQL dan memungkinkan Anda untuk menggunakan berbagai karakter Unicode.
+Dalam '''modus UTF-8''', MySQL akan tahu apa set karakter data anda dan dapat menyajikan dan mengubahnya denga tepat, namun tidak akan mengizinkan Anda menyimpan karakter di atas [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ 'config-mysql-old' => 'MySQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.',
+ 'config-db-port' => 'Porta basis data:',
+ 'config-db-schema' => 'Skema untuk MediaWiki',
+ 'config-db-schema-help' => 'Skema di atas biasanya benar.
+Ubah hanya jika Anda tahu Anda perlu mengubahnya.',
+ 'config-sqlite-dir' => 'Direktori data SQLite:',
+ 'config-sqlite-dir-help' => "SQLite menyimpan semua data dalam satu berkas.
+
+Direktori yang Anda berikan harus dapat ditulisi oleh server web selama instalasi.
+
+Direktori itu '''tidak''' boleh dapat diakses melalui web, inilah sebabnya kami tidak menempatkannya bersama dengan berkas PHP lain.
+
+Penginstal akan membuat berkas <code>.htaccess</code> bersamaan dengan itu, tetapi jika gagal, orang dapat memperoleh akses ke basis data mentah Anda.
+Itu termasuk data mentah pengguna (alamat surel, hash sandi) serta revisi yang dihapus dan data lainnya yang dibatasi pada wiki.
+
+Pertimbangkan untuk menempatkan basis data di tempat lain, misalnya di <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Tablespace bawaan:',
+ 'config-oracle-temp-ts' => 'Tablespace sementara:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki mendukung sistem basis data berikut:
+
+$1
+
+Jika Anda tidak melihat sistem basis data yang Anda gunakan tercantum di bawah ini, ikuti petunjuk terkait di atas untuk mengaktifkan dukungan.',
+ 'config-support-mysql' => '* $1 adalah target utama MediaWiki dan memiliki dukungan terbaik ([http://www.php.net/manual/en/mysql.installation.php cara mengompilasi PHP dengan dukungan MySQL])',
+ 'config-support-postgres' => '* $1 adalah sistem basis data sumber terbuka populer sebagai alternatif untuk MySQL ([http://www.php.net/manual/en/pgsql.installation.php cara mengompilasi PHP dengan dukungan PostgreSQL])',
+ 'config-support-sqlite' => '* $1 adalah sistem basis data yang ringan yang sangat baik dukungannya. ([http://www.php.net/manual/en/pdo.installation.php cara mengompilasi PHP dengan dukungan SQLite], menggunakan PDO)',
+ 'config-support-oracle' => '* $1 adalah basis data komersial untuka perusahaan. ([http://www.php.net/manual/en/oci8.installation.php cara mengompilasi PHP dengan dukungan OCI8])',
+ 'config-header-mysql' => 'Pengaturan MySQL',
+ 'config-header-postgres' => 'Pengaturan PostgreSQL',
+ 'config-header-sqlite' => 'Pengaturan SQLite',
+ 'config-header-oracle' => 'Pengaturan Oracle',
+ 'config-invalid-db-type' => 'Jenis basis data tidak sah',
+ 'config-missing-db-name' => 'Anda harus memasukkan nilai untuk "Nama basis data"',
+ 'config-missing-db-host' => 'Anda harus memasukkan nilai untuk "Inang basis data"',
+ 'config-missing-db-server-oracle' => 'Anda harus memasukkan nilai untuk "TNS basis data"',
+ 'config-invalid-db-server-oracle' => 'TNS basis data "$1" tidak sah.
+Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan titik (.).',
+ 'config-invalid-db-name' => 'Nama basis data "$1" tidak sah.
+Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).',
+ 'config-invalid-db-prefix' => 'Prefiks basis data "$1" tidak sah.
+Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).',
+ 'config-connection-error' => '$1.
+
+Periksa nama inang, pengguna, dan sandi di bawah ini dan coba lagi.',
+ 'config-invalid-schema' => 'Skema MediaWiki "$1" tidak sah.
+Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), dan garis bawah (_).',
+ 'config-postgres-old' => 'PostgreSQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.',
+ 'config-sqlite-name-help' => 'Pilih nama yang mengidentifikasi wiki Anda.
+Jangan gunakan spasi atau tanda hubung.
+Nama ini akan digunakan untuk nama berkas data SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Tidak dapat membuat direktori data <code><nowiki>$1</nowiki></code>, karena direktori induk <code><nowiki>$2</nowiki></code> tidak bisa ditulisi oleh server web.
+
+Penginstal telah menentukan pengguna yang menjalankan server web Anda.
+Buat direktori <code><nowiki>$3</nowiki></code> menjadi dapat ditulisi olehnya.
+Pada sistem Unix/Linux lakukan hal berikut:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Tidak dapat membuat direktori data <code><nowiki>$1</nowiki></code>, karena direktori induk <code><nowiki>$2</nowiki></code> tidak bisa ditulisi oleh server web.
+
+Penginstal tidak dapat menentukan pengguna yang menjalankan server web Anda.
+Buat direktori <code><nowiki>$3</nowiki></code> menjadi dapat ditulisi oleh semua orang.
+Pada sistem Unix/Linux lakukan hal berikut:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Kesalahan saat membuat direktori data "$1".
+Periksa lokasi dan coba lagi.',
+ 'config-sqlite-dir-unwritable' => 'Tidak dapat menulisi direktori "$1".
+Ubah hak akses direktori sehingga server web dapat menulis ke sana, dan coba lagi.',
+ 'config-sqlite-connection-error' => '$1.
+
+Periksa direktori data dan nama basis data di bawah dan coba lagi.',
+ 'config-sqlite-readonly' => 'Berkas <code>$1</code> tidak dapat ditulisi.',
+ 'config-sqlite-cant-create-db' => 'Tidak dapat membuat berkas basis data <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'PHP tidak memiliki dukungan FTS3, tabel dituruntarafkan.',
+ 'config-can-upgrade' => "Ada tabel MediaWiki di basis dataini.
+Untuk memperbaruinya ke MediaWiki $1, klik '''Lanjut'''.",
+ 'config-upgrade-done' => "Pemutakhiran selesai.
+
+Anda sekarang dapat [$1 mulai menggunakan wiki Anda].
+
+Jika Anda ingin membuat ulang berkas <code>LocalSettings.php</code>, klik tombol di bawah ini.
+Tindakan ini '''tidak dianjurkan''' kecuali jika Anda mengalami masalah dengan wiki Anda.",
+ 'config-upgrade-done-no-regenerate' => 'Pemutakhiran selesai.
+
+Anda sekarang dapat [$1 mulai menggunakan wiki Anda].',
+ 'config-regenerate' => 'Regenerasi LocalSettings.php →',
+ 'config-show-table-status' => 'Kueri SHOW TABLE STATUS gagal!',
+ 'config-unknown-collation' => "'''Peringatan:''' basis data menggunakan kolasi yang tidak dikenal.",
+ 'config-db-web-account' => 'Akun basis data untuk akses web',
+ 'config-db-web-help' => 'Masukkan nama pengguna dan sandi yang akan digunakan server web untuk terhubung ke server basis data saat operasi normal wiki.',
+ 'config-db-web-account-same' => 'Gunakan akun yang sama seperti untuk instalasi',
+ 'config-db-web-create' => 'Buat akun jika belum ada',
+ 'config-db-web-no-create-privs' => 'Akun Anda berikan untuk instalasi tidak memiliki hak yang cukup untuk membuat akun.
+Akun yang Anda berikan harus sudah ada.',
+ 'config-mysql-engine' => 'Mesin penyimpanan:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' hampir selalu merupakan pilihan terbaik karena memiliki dukungan konkurensi yang baik.
+
+'''MyISAM''' mungkin lebih cepat dalam instalasi pengguna-tunggal atau hanya-baca.
+Basis data MyISAM cenderung lebih sering rusak daripada basis data InnoDB.",
+ 'config-mysql-charset' => 'Set karakter basis data:',
+ 'config-mysql-binary' => 'Biner',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "Dalam '''modus biner''', MediaWiki menyimpan teks UTF-8 untuk basis data dalam bidang biner.
+Ini lebih efisien daripada modus UTF-8 MySQL dan memungkinkan Anda untuk menggunakan ragam penuh karakter Unicode.
+
+Dalam '''modus UTF-8''', MySQL akan tahu apa set karakter data dan dapat menampilkan dan mengubahnya sesuai keperluan, tetapi tidak akan mengizinkan Anda menyimpan karakter di atas [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ 'config-site-name' => 'Nama wiki:',
+ 'config-site-name-help' => 'Ini akan muncul di bilah judul peramban dan di berbagai tempat lainnya.',
+ 'config-site-name-blank' => 'Masukkan nama situs.',
+ 'config-project-namespace' => 'Ruang nama proyek:',
+ 'config-ns-generic' => 'Proyek',
+ 'config-ns-site-name' => 'Sama seperti nama wiki: $1',
+ 'config-ns-other' => 'Lainnya (sebutkan)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-project-namespace-help' => 'Mengikuti contoh Wikipedia, banyak wiki menyimpan halaman kebijakan mereka terpisah dari halaman konten mereka, dalam "\'\'\'ruang nama proyek\'\'\'".
+Semua judul halaman dalam ruang nama ini diawali dengan prefiks tertentu yang dapat Anda tetapkan di sini.
+Biasanya, prefiks ini berasal dari nama wiki, tetapi tidak dapat berisi karakter tanda baca seperti "#" atau ":".',
+ 'config-ns-invalid' => 'Ruang nama "<nowiki>$1</nowiki>" yang ditentukan tidak sah.
+Berikan ruang nama proyek lain.',
+ 'config-admin-box' => 'Akun pengurus',
+ 'config-admin-name' => 'Nama Anda:',
+ 'config-admin-password' => 'Kata sandi:',
+ 'config-admin-password-confirm' => 'Kata sandi lagi:',
+ 'config-admin-help' => 'Masukkan nama pengguna pilihan Anda di sini, misalnya "Udin Wiki".
+Ini adalah nama yang akan Anda gunakan untuk masuk ke wiki.',
+ 'config-admin-name-blank' => 'Masukkan nama pengguna pengurus.',
+ 'config-admin-name-invalid' => 'Nama pengguna "<nowiki>$1</nowiki>" yang diberikan tidak sah.
+Berikan nama pengguna lain.',
+ 'config-admin-password-blank' => 'Masukkan kata sandi untuk akun pengurus.',
+ 'config-admin-password-same' => 'Kata sandi harus tidak sama seperti nama pengguna.',
+ 'config-admin-password-mismatch' => 'Dua kata sandi yang Anda masukkan tidak cocok.',
+ 'config-admin-email' => 'Alamat surel:',
+ 'config-admin-email-help' => 'Masukkan alamat surel untuk memungkinkan Anda menerima surel dari pengguna lain, menyetel ulang sandi, dan mendapat pemberitahuan tentang perubahan atas daftar pantauan Anda.',
+ 'config-admin-error-user' => 'Kesalahan internal saat membuat admin dengan nama "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Kesalahan internal saat membuat sandi untuk admin "<nowiki>$1</nowiki>":<pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Anda memasukkan alamat surel yang tidak sah',
+ 'config-subscribe' => 'Berlangganan ke [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce milis pengumuman rilis].',
+ 'config-subscribe-help' => 'Ini adalah milis bervolume rendah yang digunakan untuk pengumuman rilis, termasuk pengumuman keamanan penting.
+Anda sebaiknya berlangganan dan memperbarui instalasi MediaWiki saat versi baru keluar.',
+ 'config-almost-done' => 'Anda hampir selesai!
+Anda sekarang dapat melewati sisa konfigurasi dan menginstal wiki sekarang.',
+ 'config-optional-continue' => 'Berikan saya pertanyaan lagi.',
+ 'config-optional-skip' => 'Saya sudah bosan, instal saja wikinya.',
+ 'config-profile' => 'Profil hak pengguna:',
+ 'config-profile-wiki' => 'Wiki tradisional',
+ 'config-profile-no-anon' => 'Pembuatan akun diperlukan',
+ 'config-profile-fishbowl' => 'Khusus penyunting terdaftar',
+ 'config-profile-private' => 'Wiki pribadi',
+ 'config-profile-help' => "Wiki paling baik bekerja jika Anda membiarkan sebanyak mungkin orang untuk menyunting.
+Dengan MediaWiki, sangat mudah meninjau perubahan terbaru dan mengembalikan kerusakan yang dilakukan oleh pengguna naif atau berbahaya.
+
+Namun, berbagai kegunaan lain dari MediaWiki telah ditemukan, dan kadang tidak mudah untuk meyakinkan semua orang manfaat dari cara wiki.
+Jadi, Anda yang menentukan.
+
+'''{{int:config-profil-wiki}}''' memungkinkan setiap orang untuk menyunting, bahkan tanpa masuk.
+'''{{int:config-profil-no-anon}}''' menyediakan akuntabilitas tambahan, tetapi dapat mencegah kontributor biasa.
+
+'''{{int:config-profil-fishbowl}}''' memungkinkan pengguna yang disetujui untuk menyunting, tetapi publik dapat melihat halaman, termasuk riwayatnya.
+'''{{int:config-profil-private}}''' hanya memungkinkan pengguna yang disetujui untuk melihat dan menyunting halaman.
+
+Konfigurasi hak pengguna yang lebih kompleks tersedia setelah instalasi. Lihat [http://www.mediawiki.org/wiki/Manual:User_rights/id entri manual terkait].",
+ 'config-license' => 'Hak cipta dan lisensi:',
+ 'config-license-none' => 'Tidak ada lisensi',
+ 'config-license-cc-by-sa' => 'Creative Commons Atribusi Berbagi Serupa',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Atribusi Non-Komersial Berbagi Serupa',
+ 'config-license-gfdl-old' => 'Lisensi Dokumentasi Bebas GNU 1.2',
+ 'config-license-gfdl-current' => 'Lisensi Dokumentasi Bebas GNU 1.3 atau versi terbaru',
+ 'config-license-pd' => 'Domain Umum',
+ 'config-license-cc-choose' => 'Pilih lisensi Creative Commons kustom',
+ 'config-license-help' => "Banyak wiki publik meletakkan semua kontribusi di bawah [http://freedomdefined.org/Definition lisensi bebas].
+Hal ini membantu untuk menciptakan rasa kepemilikan komunitas dan mendorong kontribusi jangka panjang.
+Ini umumnya tidak diperlukan untuk wiki pribadi atau perusahaan.
+
+Jika Anda ingin dapat menggunakan teks dari Wikipedia dan Anda ingin Wikipedia untuk dapat menerima teks yang disalin dari wiki Anda, Anda harus memilih'''Creative Commons Attribution Share Alike'''.
+
+GNU Free Documentation License adalah lisensi sebelumnya dari Wikipedia.
+Lisensi ini masih sah, namun memiliki beberapa fitur yang menyulitkan pemakaian ulang dan interpretasi.",
+ 'config-email-settings' => 'Pengaturan surel',
+ 'config-enable-email' => 'Aktifkan surel keluar',
+ 'config-enable-email-help' => 'Jika Anda ingin mengaktifkan surel, [http://www.php.net/manual/en/mail.configuration.php setelah surel PHP] perlu dikonfigurasi dengan benar.
+Jika Anda tidak perlu fitur surel, Anda dapat menonaktifkannya di sini.',
+ 'config-email-user' => 'Aktifkan surel antarpengguna',
+ 'config-email-user-help' => 'Memungkinkan semua pengguna untuk saling berkirim surel jika mereka mengaktifkan pilihan tersebut dalam preferensi mereka.',
+ 'config-email-usertalk' => 'Aktifkan pemberitahuan perubahan halaman pembicaraan pengguna',
+ 'config-email-usertalk-help' => 'Memungkinkan pengguna untuk menerima pemberitahuan tentang perubahan halaman pembicaraan pengguna, jika pilihan tersebut telah diaktifkan dalam preferensi mereka.',
+ 'config-email-watchlist' => 'Aktifkan pemberitahuan daftar pantau',
+ 'config-email-watchlist-help' => 'Memungkinkan pengguna untuk menerima pemberitahuan tentang perubahan halaman yang ada dalam daftar pantauan mereka, jika pilihan tersebut telah diaktifkan dalam preferensi mereka.',
+ 'config-email-auth' => 'Aktifkan otentikasi surel',
+ 'config-email-auth-help' => "Jika opsi ini diaktifkan, pengguna harus mengonfirmasi alamat surel dengan menggunakan pranala yang dikirim kepadanya setiap kali mereka mengatur atau mengubahnya.
+Hanya alamat surel yang dikonfirmasi yang dapat menerima surel dari pengguna lain atau surel pemberitahuan perubahan.
+Penetapan opsi ini '''direkomendasikan''' untuk wiki publik karena adanya potensi penyalahgunaan fitur surel.",
+ 'config-email-sender' => 'Alamat surel balasan:',
+ 'config-email-sender-help' => 'Masukkan alamat surel untuk digunakan sebagai alamat pengirim pada surel keluar.
+Alamat ini akan menerima pentalan.
+Banyak server surel mensyaratkan paling tidak bagian nama domain yang sah.',
+ 'config-upload-settings' => 'Pengunggahan gambar dan berkas',
+ 'config-upload-enable' => 'Aktifkan pengunggahan berkas',
+ 'config-upload-help' => 'Pengunggahan berkas berpotensi memaparkan server Anda dengan risiko keamanan.
+Untuk informasi lebih lanjut, baca [http://www.mediawiki.org/wiki/Manual:Security/id manual keamanan].
+
+Untuk mengaktifkan pengunggahan berkas, ubah modus subdirektori <code>images</code> di bawah direktori akar MediaWiki agar server web dapat menulis ke sana.
+Kemudian aktifkan opsi ini.',
+ 'config-upload-deleted' => 'Direktori untuk berkas terhapus:',
+ 'config-upload-deleted-help' => 'Pilih direktori tempat mengarsipkan berkas yang dihapus.
+Idealnya, direktori ini tidak boleh dapat diakses dari web.',
+ 'config-logo' => 'URL logo:',
+ 'config-logo-help' => 'Kulit bawaan MediaWiki memberikan ruang untuk logo ukuran 135x160 pixel di sudut kiri atas.
+Unggah gambar dengan ukuran yang sesuai, lalu masukkan URL di sini.
+
+Jika Anda tidak ingin menyertakan logo, biarkan kotak ini kosong.',
+ 'config-instantcommons' => 'Aktifkan Instant Commons',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] adalah fitur yang memungkinkan wiki untuk menggunakan gambar, suara, dan media lain dari [http://commons.wikimedia.org/ Wikimedia Commons].
+Untuk melakukannya, MediaWiki memerlukan akses ke Internet.
+
+Untuk informasi lebih lanjut tentang fitur ini, termasuk petunjuk tentang cara untuk mengatur untuk wiki selain Wikimedia Commons, baca [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos manual].',
+ 'config-cc-error' => 'Pemilih lisensi Creative Commons tidak memberikan hasil.
+Masukkan nama lisensi secara manual.',
+ 'config-cc-again' => 'Pilih lagi...',
+ 'config-cc-not-chosen' => 'Pilih lisensi Creative Commons yang Anda inginkan dan klik "lanjutkan".',
+ 'config-advanced-settings' => 'Konfigurasi lebih lanjut',
+ 'config-cache-options' => 'Pengaturan untuk penyinggahan objek:',
+ 'config-cache-help' => 'Penyinggahan objek digunakan untuk meningkatkan kecepatan MediaWiki dengan menyinggahkan data yang sering digunakan.
+Situs berukuran sedang hingga besar sangat dianjurkan untuk mengaktifkan fitur ini, dan situs kecil juga akan merasakan manfaatnya.',
+ 'config-cache-none' => 'Tidak ada penyinggahan (tidak ada fungsi yang dibuang, tetapi kecepatan dapat terpengaruh pada situs wiki yang besar)',
+ 'config-cache-accel' => 'Penyinggahan objek PHP (APC, eAccelerator, XCache atau WinCache)',
+ 'config-cache-memcached' => 'Gunakan Memcached (memerlukan setup dan konfigurasi tambahan)',
+ 'config-memcached-servers' => 'Server Memcached:',
+ 'config-memcached-help' => 'Daftar alamat IP yang digunakan untuk Memcached.
+Harus dipisahkan dengan koma dan sebutkan port yang akan digunakan (contoh: 127.0.0.1:11211, 192.168.1.25:11211).',
+ 'config-extensions' => 'Ekstensi',
+ 'config-extensions-help' => 'Ekstensi yang tercantum di atas terdeteksi di direktori <code>./extensions</code>.
+
+Ekstensi tersebut mungkin memerlukan konfigurasi tambahan, tetapi Anda dapat mengaktifkannya sekarang.',
+ 'config-install-alreadydone' => "'''Peringatan:''' Anda tampaknya telah menginstal MediaWiki dan mencoba untuk menginstalnya lagi.
+Lanjutkan ke halaman berikutnya.",
+ 'config-install-step-done' => 'selesai',
+ 'config-install-step-failed' => 'gagal',
+ 'config-install-extensions' => 'Termasuk ekstensi',
+ 'config-install-database' => 'Mendirikan basis data',
+ 'config-install-pg-schema-failed' => 'Pembuatan tabel gagal.
+Pastikan bahwa pengguna "$1" dapat menulis ke skema "$2".',
+ 'config-install-pg-commit' => 'Melakukan perubahan',
+ 'config-install-pg-plpgsql' => 'Memeriksa bahasa PL / pgSQL',
+ 'config-pg-no-plpgsql' => 'Anda perlu menginstal bahasa PL/pgSQL pada basis data $1',
+ 'config-install-user' => 'Membuat pengguna basis data',
+ 'config-install-user-grant-failed' => 'Memberikan izin untuk pengguna "$1" gagal: $2',
+ 'config-install-tables' => 'Membuat tabel',
+ 'config-install-tables-exist' => "'''Peringatan''': Tabel MediaWiki sepertinya sudah ada.
+Melompati pembuatan.",
+ 'config-install-tables-failed' => "'''Kesalahan''': Pembuatan tabel gagal dengan kesalahan berikut: $1",
+ 'config-install-interwiki' => 'Mengisi tabel bawaan antarwiki',
+ 'config-install-interwiki-list' => 'Tidak dapat menemukan berkas <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Peringatan''': Tabel antarwiki tampaknya sudah memiliki entri.
+Mengabaikan daftar bawaan.",
+ 'config-install-keys' => 'Menciptakan kunci rahasia',
+ 'config-install-sysop' => 'Membuat akun pengguna pengurus',
+ 'config-install-subscribe-fail' => 'Tidak dapat berlangganan mediawiki-announce',
+ 'config-install-mainpage' => 'Membuat halaman utama dengan konten bawaan',
+ 'config-install-mainpage-failed' => 'Tidak dapat membuat halaman utama: $1',
+ 'config-install-done' => "'''Selamat!'''
+Anda telah berhasil menginstal MediaWiki.
+
+Penginstal telah membuat berkas <code>LocalSettings.php</code>.
+Berkas itu berisi semua konfigurasi Anda.
+
+Anda perlu [$1 mengunduhnya] dan meletakkannya di basis instalasi wiki (direktori yang sama dengan index.php).
+'''Catatan''': Jika Anda tidak melakukannya sekarang, berkas konfigurasi yang dihasilkan ini tidak akan tersedia lagi setelah Anda keluar instalasi tanpa mengunduhnya.
+
+Setelah melakukannya, Anda dapat '''[$2 memasuki wiki Anda]'''.",
+ 'config-download-localsettings' => 'Unduh LocalSettings.php',
+ 'config-help' => 'bantuan',
+);
+
+/** Igbo (Igbo)
+ * @author Ukabia
+ */
+$messages['ig'] = array(
+ 'config-admin-password' => 'Okwúngáfè:',
+ 'config-admin-password-confirm' => 'Okwúngáfè mgbe ozor:',
+);
+
+/** Italian (Italiano)
+ * @author Beta16
+ */
+$messages['it'] = array(
+ 'config-information' => 'Informazioni',
+ 'config-back' => '← Indietro',
+ 'config-continue' => 'Continua →',
+ 'config-page-language' => 'Lingua',
+ 'config-page-name' => 'Nome',
+ 'config-page-options' => 'Opzioni',
+ 'config-page-install' => 'Installa',
+ 'config-page-complete' => 'Completa!',
+ 'config-page-readme' => 'Leggimi',
+ 'config-page-releasenotes' => 'Note di versione',
+);
+
+/** Japanese (日本語)
+ * @author Aphaia
+ * @author Iwai.masaharu
+ * @author Mizusumashi
+ * @author Ohgi
+ * @author Whym
+ * @author Yanajin66
+ * @author 青子守歌
+ */
+$messages['ja'] = array(
+ 'config-desc' => 'MediaWikiのためのインストーラー',
+ 'config-title' => 'MediaWiki $1のインストール',
+ 'config-information' => '情報',
+ 'config-localsettings-upgrade' => '<code>LocalSettings.php</code>ファイルが検出されました。
+アップグレードするため、ボックス中の<code>$wgUpgradeKey</code>の値を入力してください。
+LocalSettings.phpの中にそれはあるでしょう。',
+ 'config-localsettings-key' => 'アップグレードキー:',
+ 'config-localsettings-badkey' => '与えられたキーが間違っています',
+ 'config-localsettings-incomplete' => '現在のLocalSettings.phpは不完全であるようです。
+変数$1が設定されていません。
+LocalSettings.phpを変更してこの変数を設定して、『{{int:Config-continue}}』を押してください。',
+ 'config-session-error' => 'セッションの開始エラー:$1',
+ 'config-session-expired' => 'セッションの有効期限が切れたようです。
+セッションの有効期間は$1に設定されています。
+php.iniの<code>session.gc_maxlifetime</code>を設定することで、この問題を改善できます。
+インストール作業を再起動させてください。',
+ 'config-no-session' => 'セッションのデータが損失しました!
+php.iniを確認し、<code>session.save_path</code>が適切なディレクトリに設定されていることを確かめて下さい。',
+ 'config-your-language' => 'あなたの言語:',
+ 'config-your-language-help' => 'インストール作業中に利用する言語を選んで下さい。',
+ 'config-wiki-language' => 'ウィキの言語:',
+ 'config-wiki-language-help' => 'そのウィキで主に書き込まれる言語を選んで下さい。',
+ 'config-back' => '←戻る',
+ 'config-continue' => '続行→',
+ 'config-page-language' => '言語',
+ 'config-page-welcome' => 'MediaWikiへようこそ!',
+ 'config-page-dbconnect' => 'データベースへ接続',
+ 'config-page-upgrade' => '既存のインストールを更新',
+ 'config-page-dbsettings' => 'データベースの設定',
+ 'config-page-name' => '名前',
+ 'config-page-options' => 'オプション',
+ 'config-page-install' => 'インストール',
+ 'config-page-complete' => '完了!',
+ 'config-page-restart' => 'インストールを再起動',
+ 'config-page-readme' => 'リードミー',
+ 'config-page-releasenotes' => 'リリースノート',
+ 'config-page-copying' => 'コピー',
+ 'config-page-upgradedoc' => '更新',
+ 'config-page-existingwiki' => '既存のウィキ',
+ 'config-help-restart' => '入力された全て保存データを消去し、インストール作業を再起動しますか?',
+ 'config-restart' => 'はい、再起動します',
+ 'config-welcome' => '=== 環境の確認 ===
+基本的な確認では、この環境がMediaWikiの導入に適しているかを確認します。
+インストール中に必要になったとき、この確認結果を利用して下さい。',
+ 'config-copyright' => '=== 著作権および規約 ===
+$1
+
+この作品はフリーソフトウェアです。あなたは、フリーソフトウェア財団の発行するGNU一般公衆利用許諾書 (GNU General Public License)(バージョン2、またはそれ以降のライセンス)の規約にもとづき、このライブラリの再配布や改変をすることができます。
+
+この作品は、有用であることを期待して配布されていますが、商用あるいは特定の目的に適するかどうかも含めて、暗黙的にも、一切保証されません。
+詳しくは、GNU一般公衆利用許諾書をご覧下さい。
+
+あなたはこのプログラムと共に、<doclink href=Copying>GNU一般公衆利用許諾契約書の複製</doclink>を一部受け取ったはずです。もし受け取っていなければ、フリーソフトウェア財団(宛先は the Free Software Foundation, Inc., 59Temple Place, Suite 330, Boston, MA 02111-1307 USA)まで請求してください。',
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWikiのホーム]
+* [http://www.mediawiki.org/wiki/Help:Contents 利用者向け案内]
+* [http://www.mediawiki.org/wiki/Manual:Contents 管理者向け案内]
+* [http://www.mediawiki.org/wiki/Manual:FAQ FAQ]
+----
+* <doclink href=Readme>お読みください</doclink>
+* <doclink href=ReleaseNotes>リリースノート</doclink>
+* <doclink href=Copying>コピー</doclink>
+* <doclink href=UpgradeDoc>アップグレード</doclink>',
+ 'config-env-good' => '環境は確認されました。
+MediaWikiをインストール出来ます。',
+ 'config-env-bad' => '環境が確認されました。
+MediaWikiをインストール出来ません。',
+ 'config-env-php' => 'PHP $1がインストールされています。',
+ 'config-unicode-using-utf8' => 'Unicode正規化に、Brion Vibberのutf8_normalize.soを利用。',
+ 'config-unicode-using-intl' => 'Unicode正規化に[http://pecl.php.net/intl intl PECL 拡張機能]を利用。',
+ 'config-unicode-pure-php-warning' => "'''警告''':Unicode正規化の処理に [http://pecl.php.net/intl intl PECL 拡張機能]ではなく、ピュア PHP な実装を用いています。この処理は遅いです。
+高トラフィックのサイトを運営する場合は、[http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode正規化に関するページ]をお読み下さい。",
+ 'config-unicode-update-warning' => "'''警告''':Unicode正規化ラッパーのインストールされているバージョンは、[http://site.icu-project.org/ ICUプロジェクト]のライブラリの古いバージョンを使用しています。
+Unicodeを少しでも利用する可能性があるなら、[http://www.mediawiki.org/wiki/Unicode_normalization_considerations 更新]する必要があります。",
+ 'config-no-db' => '適切なデータベースドライバを見つけられませんでした!',
+ 'config-no-db-help' => 'PHPのデータベースドライバーをインストールする必要があります。
+以下のデータベースの種類がサポートされます:$1。
+
+共有ホスト上の場合、ホスト元に適切なデータベースドライバをインストールするように依頼してください。
+PHPを自分自身でコンパイルした場合、<code>./configure --with-mysql</code>などを利用して、データベースクライアントを有効化する設定をしてください。
+DebianもしくはUbuntuパッケージからPHPをインストールした場合、php5-mysqlモジュールもインストールする必要があります。',
+ 'config-no-fts3' => "'''警告''':SQLiteは[http://sqlite.org/fts3.html FTS3]モジュール以外でコンパイルされており、検索機能はこのバックエンドで利用不可能になります。",
+ 'config-register-globals' => "'''警告:PHPの<code>[http://php.net/register_globals register_globals]</code>オプションが有効になっています。'''
+'''可能なら無効化してください。'''
+MediaWikiは動作しますが、サーバーは、潜在的なセキュリティ脆弱性を露呈します。",
+ 'config-magic-quotes-runtime' => "'''致命的エラー:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]が動作しています!'''
+このオプションは、予期せずデータ入力を破壊します。
+このオプションが無効化されないかぎり、MediaWikiをインストールし利用することはできません。",
+ 'config-magic-quotes-sybase' => "'''致命的エラー:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]が動作しています!'''
+このオプションは、予期せずデータ入力を破壊します。
+このオプションが無効化されないかぎり、MediaWikiをインストールし利用することはできません。",
+ 'config-mbstring' => "'''致命的エラー:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]が動作しています!'''
+このオプションは、エラーを引き起こし、予期せずデータ入力を破壊する可能性があります。
+このオプションが無効化されないかぎり、MediaWikiをインストールし利用することはできません。",
+ 'config-ze1' => "'''致命的エラー:[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]が動作しています!'''
+このオプションは、MediaWikiにおいて深刻なバグを引き起こします。
+このオプションが無効化されないかぎり、MediaWikiをインストールし利用することはできません。",
+ 'config-safe-mode' => "'''警告:'''PHPの[http://www.php.net/features.safe-mode セーフモード]が有効です。
+特にファイルのアップロード<code>math</code>のサポートにおいて、問題が発生する可能性があります。",
+ 'config-xml-bad' => 'PHPのXMLモジュールが不足しています。
+MediaWikiは、このモジュールの関数を必要としているため、この構成では動作しません。
+Mandrakeを実行している場合、php-xmlパッケージをインストールしてください。',
+ 'config-pcre' => 'PCREをサポートしているモジュールが不足しているようです。
+MediaWikiは、Perl互換の正規表現関数の動作が必要です。',
+ 'config-pcre-no-utf8' => "'''致命的エラー''': PHPのPCREがPCRE_UTF8サポート無しでコンパイルされています。
+MediaWikiにはUTF-8サポートの関数が必要です。",
+ 'config-memory-raised' => 'PHPの<code>memory_limit</code>は$1で、$2に引き上げられました。',
+ 'config-memory-bad' => "'''警告:'''PHPの<code>memory_limit</code>は$1です。
+これは、非常に遅い可能性があります。
+インストールが失敗するかもしれません!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache]がインストール済み',
+ 'config-apc' => '[http://www.php.net/apc APC]がインストール済み',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator]がインストール済み',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache]がインストール済み',
+ 'config-no-cache' => "'''警告:'''[http://eaccelerator.sourceforge.net eAccelerator]、[http://www.php.net/apc APC]、[http://trac.lighttpd.net/xcache/ XCache]あるいは[http://www.iis.net/download/WinCacheForPhp WinCache]のいずれも見つかりませんでした。
+オブジェクトのキャッシュは有効化されません。",
+ 'config-diff3-bad' => 'GNU diff3が見つかりません。',
+ 'config-imagemagick' => 'ImageMagickが見つかりました:<code>$1</code>。
+アップロードが有効なら、画像のサムネイルが利用できます。',
+ 'config-gd' => 'GD画像ライブラリが内蔵されていることが確認されました。
+アップロードが有効なら、画像のサムネイルが利用できます。',
+ 'config-no-scaling' => 'GDライブラリもImageMagickも見つかりませんでした。
+画像のサムネイル生成は無効になります。',
+ 'config-no-uri' => "'''エラー:'''現在のURIを決定できませんでした。
+インストールは中止されました。",
+ 'config-uploads-not-safe' => "'''警告:'''アップロードの既定ディレクトリ<code>$1</code>が、任意のスクリプト実行に関して脆弱性があります。
+MediaWikiはアップロードされたファイルのセキュリティ上の脅威を確認しますが、アップロードを有効化するまえに、[http://www.mediawiki.org/wiki/Manual:Security#Upload_security このセキュリティ上の脆弱性を閉じる]ことが強く推奨されます。",
+ 'config-brokenlibxml' => 'このシステムで使われているPHPとlibxml2のバージョンのこの組み合わせにはバグがあります。具体的には、MediaWikiやその他のウェブアプリケーションでhiddenデータが破損する可能性があります。
+PHPを5.2.9かそれ以降のバージョンに、libxml2を2.7.3かそれ以降のバージョンにアップグレードしてください([http://bugs.php.net/bug.php?id=45996 PHPでのバグ情報])。
+インストールを終了します。',
+ 'config-using531' => 'PHP$1は<code>__call()</code>の引数参照に関するバグのため、MediaWikiと互換性がありません。
+PHP5.3.2以降に更新するか、この([http://bugs.php.net/bug.php?id=50394 PHPに提出されたバグ])を修正するためにPHP5.3.0へ戻してください。
+インストールは中止されました。',
+ 'config-db-type' => 'データベースの種類:',
+ 'config-db-host' => 'データベースのホスト:',
+ 'config-db-host-help' => 'データベースサーバーが異なったサーバー上にある場合、ホスト名またはIPアドレスをここに入力してください。
+
+もし、共有されたウェブホスティングを使用している場合、ホスティング・プロバイダは正確なホストネームを解説しているはずです。
+
+WindowsでMySQLを使用している場合に、「localhost」は、サーバー名としてはうまく働かないでしょう。もしそのような場合は、ローカルIPアドレスとして「127.0.0.1」を試してみてください。',
+ 'config-db-host-oracle' => 'データベースTNS:',
+ 'config-db-host-oracle-help' => '有効な[http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm ローカル接続名]を入力してください。tnsnames.oraファイルは、このインストールに対して表示されてなければなりません、<br />もしクライアントライブラリ10gもしくはそれ以上を使用している場合、メソッドの名前を[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 簡易接続]で利用できます。',
+ 'config-db-wiki-settings' => 'このウィキを識別',
+ 'config-db-name' => 'データベース名:',
+ 'config-db-name-help' => 'このウィキを識別する名前を選んで下さい。
+スペースを含めることはできません。
+
+共有ウェブホストを利用している場合、ホスト・プロバイダーは特定の利用可能なデータベース名を提供するか、あるいは管理パネルからデータベースを作成できるようにしているでしょう。',
+ 'config-db-name-oracle' => 'データベースのスキーマ:',
+ 'config-db-install-account' => 'インストールのための利用者アカウント',
+ 'config-db-username' => 'データベースの利用者名:',
+ 'config-db-password' => 'データベースのパスワード:',
+ 'config-db-install-username' => 'インストール中にデータベースに接続するために使うユーザ名を入力してください。これは MediaWiki アカウントのユーザ名 (利用者名) のことではありません。あなたのデータベースでのユーザ名です。',
+ 'config-db-install-password' => 'インストール中にデータベースに接続するために使うパスワードを入力してください。これは MediaWiki アカウントパスワードのことではありません。あなたのデータベースでのパスワードです。',
+ 'config-db-install-help' => 'インストール作業中にデータベースに接続するための利用者名とパスワードを入力してください。',
+ 'config-db-account-lock' => 'インストール作業終了後も同じ利用者名とパスワードを使用する',
+ 'config-db-wiki-account' => 'インストール作業終了後の利用者アカウント',
+ 'config-db-wiki-help' => '通常のウィキ操作中にデータベースへの接続する時に利用する利用者名とパスワードを入力してください。
+アカウントがないが、インストールのアカウントに十分な権限があれば、このユーザーアカウントは、ウィキを操作するうえで最小限の権限を持った状態で作成されます。',
+ 'config-db-prefix' => 'データベーステーブルの接頭辞:',
+ 'config-db-prefix-help' => 'データベースを複数のウィキ間、もしくはMediaWikiと他のウェブアプリケーションで共有する必要がある場合、衝突を避けるために、すべてのテーブル名に接頭辞をつける必要があります。
+スペースは使用できません。
+
+このフィールドは、通常は空のままです。',
+ 'config-db-charset' => 'データベースの文字セット',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0バイナリ',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 下位互換UTF-8',
+ 'config-charset-help' => "'''警告:'''MySQL 4.1+で'''下位互換UTF-8'''を使用し、その後<code>mysqldump</code>でデータベースをバックアップすると、すべての非ASCII文字が破壊され、不可逆的にバップアップが壊れるかもしれません。
+
+'''バイナリー系式'''では、MediaWikiは、UTF-8テキストを、データベースのバイナリーフィールドに格納します。
+これは、MySQLのUTF-8形式より効率的で、ユニコード文字の全範囲を利用することが出来るようになります。
+'''UTF-8形式'''では、MySQLは、なんの文字集合がデータのなかに含まれているかを知り、それに対して適切な提示と変換をするでしょうが、
+[http://ja.wikipedia.org/wiki/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E8%A8%80%E8%AA%9E%E9%9D%A2 基本多言語面]の外にある文字を格納できるようにはなりません。",
+ 'config-mysql-old' => 'MySQLの$1以降が要求されています。あなたの所有のものは$2です。',
+ 'config-db-port' => 'データベースポート:',
+ 'config-db-schema' => 'メディアウィキの図式',
+ 'config-db-schema-help' => '上の図式は常に正確です。
+必要である場合のみ、変更してください。',
+ 'config-sqlite-dir' => 'SQLiteのデータディレクトリ:',
+ 'config-sqlite-dir-help' => 'SQLiteは単一のファイル中に全てのデータを保持しています。
+
+あなたが供給するディレクトリーはインストール時にウェブサーバーによって書き込み可能でなければならない。
+
+ウェブを通してアクセス可能"不可能"でなければならない。これはあなたのPHPファイルのある所に配置不能な理由です。
+
+インストーラーは共に<code>.htaccess</code>ファイルを書き込むことでしょう。しかし、例え失敗しても誰かがあなたの生のデータベースにアクセスすることが可能となるでしょう。
+
+例えば<code>/var/lib/mediawiki/yourwiki</code>のように、全く違う場所にデータベースを配置することを考えてください。',
+ 'config-oracle-def-ts' => '既定のテーブル領域:',
+ 'config-oracle-temp-ts' => '一時的なテーブル領域:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'メディアウィキは次のようなデータベースシステムをサポートする:
+
+$1
+
+もし、データベースシステムが不可視であるならば、以下のようにリスト化されたものを使用してみてください。可能なサポートの指示に従ってください。',
+ 'config-support-mysql' => '* $1はMediaWikiの主要な対象で、もっともサポートされています([http://www.php.net/manual/en/mysql.installation.php MySQLのサポート下でPHPをコンパイルする方法])',
+ 'config-support-postgres' => '* $1は、MySQLの代替として、人気のあるオープンソースデータベースシステムです([http://www.php.net/manual/en/pgsql.installation.php PostgreSQLのサポート下でPHPをコンパイルする方法])',
+ 'config-support-sqlite' => '* $1は、良くサポートされている、軽量データベースシステムです。([http://www.php.net/manual/en/pdo.installation.php SQLiteのサポート下でPHPをコンパイルする方法]、PDOを使用)',
+ 'config-support-oracle' => '* $1は商業企業のデータベースです。([http://www.php.net/manual/en/oci8.installation.php OCI8サポートなPHPをコンパイルする方法])',
+ 'config-header-mysql' => 'MySQLの設定',
+ 'config-header-postgres' => 'PostgreSQLの設定',
+ 'config-header-sqlite' => 'SQLiteの設定',
+ 'config-header-oracle' => 'Oracleの設定',
+ 'config-invalid-db-type' => '不正なデータベースの種類',
+ 'config-missing-db-name' => '「データベース名」を入力する必要があります',
+ 'config-missing-db-server-oracle' => '「データベースTNS」に値を入力する必要があります',
+ 'config-invalid-db-server-oracle' => '不正なデータベースTNS「$1」です。
+アスキー文字(a-z, A-Z)、数字(0-9)およびアンダーバー(_)とドット(.)のみを使用してください。',
+ 'config-invalid-db-name' => '無効なデータベース名 "$1"。
+アスキー文字(a-z, A-Z)、数字(0-9)、アンダーバー(_)、ハイフン(-)のみを使用してください。',
+ 'config-invalid-db-prefix' => 'データベースの接頭語 "$1" が無効です。
+アスキー文字(a-z, A-Z)、数字(0-9)、下線(_)、ハイフン(-)のみを使用してください。',
+ 'config-connection-error' => '$1。
+
+以下のホスト名、ユーザ名、パスワードをチェックして、再度試してみてください。',
+ 'config-invalid-schema' => 'メディアウィキ"$1"における無効な図式です。
+アスキー文字(a-z, A-Z)、数字(0-9)、下線(_)のみを使用してください。',
+ 'config-postgres-old' => 'PostgreSQLの$1あるいはそれ以降が必要で、いまのバージョンは$2です。',
+ 'config-sqlite-name-help' => 'あなたのウェキと同一性のある名前を選んでください。
+空白およびハイフンは使用しないでください。
+SQLiteのデータファイル名として使用されます。',
+ 'config-sqlite-parent-unwritable-group' => 'データディレクトリ<code><nowiki>$1</nowiki></code>を作成できません。親ディレクトリ<code><nowiki>$2</nowiki></code>は、ウェブサーバから書き込みできませんでした。
+
+インストール機能は、実行しているウェブサーバのユーザーを特定しました。
+続行するには、<code><nowiki>$3</nowiki></code>ディレクトリを書き込み可能にしてください。
+UnixあるいはLinux上では、以下を実行してください:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'データディレクトリ<code><nowiki>$1</nowiki></code>を作成できません。親ディレクトリ<code><nowiki>$2</nowiki></code>は、ウェブサーバから書き込みできませんでした。
+
+インストール機能は、実行しているウェブサーバのユーザーを特定できませんでした。
+続行するには、<code><nowiki>$3</nowiki></code>ディレクトリを、ウェブサーバ(と他のユーザ!)からグローバルに書き込み出来るようにしてください。
+UnixあるいはLinux上では、以下を実行してください:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'データディレクトリー"$1"を作成したことによるエラー。
+場所をチェックして、再度試してください。',
+ 'config-sqlite-dir-unwritable' => 'ディレクトリー"$1"を書き込むことができません。
+パーミッションを変更すれば、ウェブサーバーが書き込み可能となります。再度試してください。',
+ 'config-sqlite-connection-error' => '$1。
+
+以下のデータディレクトリーとデータベースをチェックし、再度試してみてください。',
+ 'config-sqlite-readonly' => 'ファイル<code>$1</code>は書き込み不能です。',
+ 'config-sqlite-cant-create-db' => 'データベースファイル<code>$1</code>を作成できませんでした。',
+ 'config-sqlite-fts3-downgrade' => 'PHPはFTS3のサポート、テーブルのダウングレードが無効です。',
+ 'config-can-upgrade' => 'このデータベースにはメディアウィキテーブルが存在します。
+それらをメディアウィキ$1にアップグレードするために「続行」をクリックしてください。',
+ 'config-upgrade-done' => "更新は完了しました。
+
+[$1 ウィキを使い始める]ことができます。
+
+もし、<code>LocalSettings.php</code>ファイルを再生成したいのならば、下のボタンを押してください。
+ウィキに問題がないのであれば、これは'''推奨されません'''。",
+ 'config-upgrade-done-no-regenerate' => 'アップグレードが完了しました。
+
+[$1 ウィキの使用を開始]することができます。',
+ 'config-regenerate' => 'LocalSettings.phpを再生成→',
+ 'config-show-table-status' => 'SHOW TABLE STATUSクエリーが失敗しました!',
+ 'config-unknown-collation' => "'''警告:''' データベースは認識されない照合を使用しています。",
+ 'config-db-web-account' => 'ウェブアクセスのためのデータベースアカウント',
+ 'config-db-web-help' => 'ウィキの元来の操作中、ウェブサーバーがデーターベースサーバーに接続できるように、ユーザ名とパスワードを選択してください。',
+ 'config-db-web-account-same' => 'インストールのために同じアカウントを使用してください',
+ 'config-db-web-create' => '既に存在していないのであれば、アカウントを作成してください',
+ 'config-db-web-no-create-privs' => 'あなたがインストールのために定義したアカウントは、アカウント作成のための特権としては不充分です。
+あなたがここで特定したアカウントはすでに存在していなければなりません。',
+ 'config-mysql-engine' => 'ストレージエンジン:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB'''は、並行処理のサポートに優れているので、ほとんどの場合において最良の選択肢です。
+
+'''MyISAM'''は、利用者が1人の場合、あるいは読み込み専用でインストールする場合に、より処理が早くなるでしょう。
+ただし、MyISAMのデータベースは、InnoDBより高頻度で破損する傾向があります。",
+ 'config-mysql-charset' => 'データベースの文字セット:',
+ 'config-mysql-binary' => 'バイナリ',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "'''バイナリー系式'''では、MediaWikiは、UTF-8テキストを、データベースのバイナリーフィールドに格納します。
+これは、MySQLのUTF-8形式より効率的で、ユニコード文字の全範囲を利用することが出来るようになります。
+
+'''UTF-8形式'''では、MySQLは、なんの文字集合がデータのなかに含まれているかを知り、それに対して適切な提示と変換をするでしょうが、
+[http://ja.wikipedia.org/wiki/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E8%A8%80%E8%AA%9E%E9%9D%A2 基本多言語面]の外にある文字を格納できるようにはなりません。",
+ 'config-site-name' => 'ウィキの名前:',
+ 'config-site-name-help' => 'この事象はブラウザのタイトルバーと他の様々な場所において出現する。',
+ 'config-site-name-blank' => 'サイト名を入力してください。',
+ 'config-project-namespace' => 'プロジェクト名前空間:',
+ 'config-ns-generic' => 'プロジェクト',
+ 'config-ns-site-name' => 'ウィキ名と同じ:$1',
+ 'config-ns-other' => 'その他(特化されたもの)',
+ 'config-ns-other-default' => 'マイウィキ',
+ 'config-project-namespace-help' => "ウィキペディアの例に従えば、多くのウィキは「'''プロジェクトの名前空間'''」において、コンテンツのページとは分離した独自のポリシーページを持つ。
+伝統的にはこの接頭辞はウィキのページから派生される。しかし、\"#\" や \":\"のような句切り記号は含んでいない。",
+ 'config-ns-invalid' => '"<nowiki>$1</nowiki>"のように指定された名前空間は無効です。
+違うプロジェクト名前空間を指定してください。',
+ 'config-admin-box' => '管理アカウント',
+ 'config-admin-name' => '名前:',
+ 'config-admin-password' => 'パスワード:',
+ 'config-admin-password-confirm' => 'パスワードの再入力:',
+ 'config-admin-help' => 'ここにあなたの希望するユーザ名を入力してください(例えば"Joe Bloggs"など)。
+この名前でこのウィキにログインすることになります。',
+ 'config-admin-name-blank' => '管理者のユーザ名を入力してください。',
+ 'config-admin-name-invalid' => '指定されたユーザ名 "<nowiki>$1</nowiki>" は無効です。
+別のユーザ名を指定してください。',
+ 'config-admin-password-blank' => '管理者アカウントのパスワードを入力してください。',
+ 'config-admin-password-same' => 'ユーザ名と同じパスワードは使えません。',
+ 'config-admin-password-mismatch' => '入力された二つのパスワードが一致しません。',
+ 'config-admin-email' => 'Eメールアドレス:',
+ 'config-admin-email-help' => '電子メールアドレスを入力してください。他のユーザーからの電子メールの受け取りと、パスワードのリセット、ウォッチリストに登録したページの更新通知に用いられます。',
+ 'config-admin-error-user' => '"<nowiki>$1</nowiki>"という名前の管理者を作成する際に内部エラーが発生しました。',
+ 'config-admin-error-password' => '管理者"<nowiki>$1</nowiki>"のパスワードを設定する際に内部エラーが発生しました: <pre>$2</pre>',
+ 'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce リリース告知のメーリングリスト]を購読する。',
+ 'config-subscribe-help' => 'これは、リリースの告知(重要なセキュリティに関する案内を含む)に使われる、低容量のメーリングリストです。
+このメーリングリストを購読して、新しいバージョンが出た場合にMediaWikiを更新してください。',
+ 'config-almost-done' => 'あなたはほとんど完璧です!
+設定を残すことをはぶいて、今すぐにウィキをインストールできます。',
+ 'config-optional-continue' => '私にもっと質問してください。',
+ 'config-optional-skip' => 'すでに飽きてしまった、ウィキをインストールするだけです。',
+ 'config-profile' => '正しいプロフィールのユーザ:',
+ 'config-profile-wiki' => '伝統的なウィキ',
+ 'config-profile-no-anon' => 'アカウントの作成が必要',
+ 'config-profile-fishbowl' => '承認された編集者のみ',
+ 'config-profile-private' => '非公開ウィキ',
+ 'config-profile-help' => "ウィキは、たくさんの人が可能な限りそのウィキを編集できるとき、最も優れた働きをします。
+MediaWikiでは、最近の更新を確認し、神経質な、もしくは悪意を持った利用者からの損害を差し戻すことが、簡単にできます。
+
+しかし一方で、MediaWikiは、さらに様々な形態でもの利用も優れていると言われています。また、時には、すべての人にウィキ手法の利点を説得させるのは容易ではないかもしれません。
+そこで、選択肢があります。
+
+'''{{int:config-profile-wiki}}'''は、ログインをせずとも、誰でも編集が可能なものです。
+'''{{int:config-profile-no-anon}}'''なウィキは、各編集に対してより強い説明責任を付与しますが、気軽な投稿を阻害するかもしれません。
+
+'''{{int:config-profile-fishbowl}}'''のウィキは、承認された利用者は編集でき、一方、一般の人はページ(とその履歴)の閲覧が可能です。
+'''{{int:config-profile-private}}'''は、承認された利用者がページを閲覧可能で、そのグループが編集可能です。
+
+より複雑な利用者権限の設定は、インストール後に設定可能です。詳細は[http://www.mediawiki.org/wiki/Manual:User_rights 関連するマニュアル]をご覧ください。",
+ 'config-license' => '著作権とライセンス:',
+ 'config-license-none' => 'ライセンスのフッターを付けない',
+ 'config-license-cc-by-sa' => 'クリエイティブ・コモンズ 表示-継承',
+ 'config-license-cc-by-nc-sa' => 'クリエイティブ・コモンズ 表示-非営利-継承',
+ 'config-license-gfdl-old' => 'GNUフリー文書利用許諾契約書 1.2',
+ 'config-license-gfdl-current' => 'GNUフリー文書利用許諾契約書 1.3 またはそれ以降',
+ 'config-license-pd' => 'パブリック・ドメイン',
+ 'config-license-cc-choose' => 'その他のクリエイティブ・コモンズ・ライセンスを選択する',
+ 'config-license-help' => "多くの公開ウィキでは、すべての寄稿物が[http://freedomdefined.org/Definition フリーライセンス]の元に置かれています。
+こうすることにより、コミュニティによる共有の感覚が生まれ、長期的な寄稿が促されます。
+私的ウィキや企業のウィキでは、通常、フリーライセンスにする必要はありません。
+
+ウィキペディアにあるテキストをあなたのウィキで利用し、逆にあなたのウィキにあるテキストをウィキペディアに複製することを許可したい場合には、'''クリエイティブ・コモンズ 表示-継承'''を選択するべきです。
+
+GNUフリー文書利用許諾契約書はウィキペディアが採用していた古いライセンスです。
+今も有効なライセンスではありますが、再利用や解釈を難しくする条項が含まれています。",
+ 'config-email-settings' => '電子メールの設定',
+ 'config-enable-email' => '電子メール送信の有効',
+ 'config-enable-email-help' => "もし、電子メールの作動を欲するならば、[http://www.php.net/manual/en/mail.configuration.php PHP's mail settings]のページが正確に設定されている必要がある。
+もし、電子メールに関するいかなる機能を欲しないのであれば、ここで無効にできます。",
+ 'config-email-user' => 'ユーザ間同士の電子メールの許可',
+ 'config-email-user-help' => '設定において有効になっている場合、全てのユーザがお互いに電子メールのやりとりを行うことを許可する。',
+ 'config-email-usertalk' => 'ユーザのトークページにおける通知を有効にする',
+ 'config-email-usertalk-help' => '設定で有効にしているならば、ユーザのトークページの変更の通知を受けることをユーザに許可する。',
+ 'config-email-watchlist' => 'ウォッチリストの通知を有効にする',
+ 'config-email-watchlist-help' => '設定で有効にしているならば、閲覧されたページに関する通知を受け取ることをユーザに許可する。',
+ 'config-email-auth' => '電子メールの認証を有効にする',
+ 'config-email-auth-help' => "この選択肢が有効化されると、利用者が電子メールのアドレスを設定あるいは変更したときに送信されるリンクにより、そのアドレスを確認しなければならなくなります。
+認証済みのアドレスだけが、他の利用者からのメールや、変更通知のメールを受け取ることができます。
+公開ウィキでは、メール機能による潜在的な不正利用の防止のため、この選択肢を設定することが'''推奨'''されます。",
+ 'config-email-sender' => '電子メールのアドレスを返す:',
+ 'config-email-sender-help' => '送信メールの返信アドレスとして利用するメールアドレスを入力してください。
+宛先不明の場合、このアドレスにその通知が送信されます。
+多くのメールサーバーでは、少なくともドメイン名の一部が有効であることが必要になっています。',
+ 'config-upload-settings' => '画像およびファイルのアップロード',
+ 'config-upload-enable' => 'ファイルのアップロードを有効にする',
+ 'config-upload-help' => 'ファイルのアップロードは潜在的にあなたのサーバにセキュリティー上の危険をさらします。
+更なる情報のために、マニュアルの[http://www.mediawiki.org/wiki/Manual:Security security section] を読むことをすすめます。
+
+ファイルのアップロードを可能にするために、メディアウィキのルートディレクトリ下の<code>images</code>サブディレクトリのモードを変更します。そうすることにより、ウェブサーバはそこに書き込みが可能になります。
+そして、このオプションを有効にしてください。',
+ 'config-upload-deleted' => '削除されたファイルのためのディレクトリ:',
+ 'config-upload-deleted-help' => '削除されるファイルを保存するためのディレクトリを選択してください。
+これがウェブからアクセスできないことが理想です。',
+ 'config-logo' => 'ロゴのURL:',
+ 'config-logo-help' => 'メディアウィキの初期のスキンは最上部左角にある135x160ピクセルのロゴのためにスペースを含んでいます。
+適切なサイズのイメージをアップロードし、ここにURLを入力してください。
+
+もし、ロゴを望まないならば、このボックスを空白状態のままにしてください。',
+ 'config-instantcommons' => 'InstantCommons機能を有効にする',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons InstantCommons]は、[http://commons.wikimedia.org/ ウィキメディア・コモンズ]のサイトで見つかった画像や音声、その他のメディアをウィキ上で利用することができるようになる機能です。
+これを有効化するには、MediaWikiはインターネットに接続できなければなりません。
+
+ウィキメディアコモンズ以外のウィキを同じように設定する方法など、この機能に関する詳細な情報は、[http://mediawiki.org/wiki/Manual:$wgForeignFileRepos マニュアル]をご覧ください。',
+ 'config-cc-error' => 'クリエイティブ・コモンズ・ライセンスの選択器から結果が得られませんでした。
+ライセンスの名前を手動で入力してください。',
+ 'config-cc-again' => 'もう一度選択してください...',
+ 'config-cc-not-chosen' => 'あなたの求めるクリエイティブコモンズのライセンスを選んで、"続行"をクリックしてください。',
+ 'config-advanced-settings' => '高度な設定',
+ 'config-cache-options' => 'オブジェクトのキャッシュの設定:',
+ 'config-cache-help' => 'オブジェクトのキャッシュは、使用したデータを頻繁にキャッシングすることによって、メディアウィキのスピード改善に使用されます。
+中〜大サイトにおいては、これを有効にするために大変望ましいことです。また小さなサイトにおいても同様な利点をもたらすと考えられます。',
+ 'config-cache-none' => 'キャッシングしない(機能は取り払われます、しかもより大きなウィキサイト上でスピードの問題が発生します)',
+ 'config-cache-accel' => 'PHPオブジェクトキャッシング(APC、eAccelerator、XCacheあるいはWinCache)',
+ 'config-cache-memcached' => 'Memcachedを使用(追加の設定が必要です)',
+ 'config-memcached-servers' => 'メモリをキャッシュされたサーバ:',
+ 'config-memcached-help' => 'Memcachedを使用するIPアドレスの一覧。
+カンマ区切りで、利用する特定のポートの指定が必要です。例:
+127.0.0.1:11211
+192.168.1.25:1234',
+ 'config-extensions' => '拡張機能',
+ 'config-extensions-help' => '<code>./extensions</code>ディレクトリ内で、上記リストの拡張機能が発見されました。
+
+これらは更に多くの設定を要求するかもしれませんが、今これらを有効にすることができます。',
+ 'config-install-alreadydone' => "'''警告:''' 既にMediaWikiがインストール済みで、再びインストールし直そうとしています。
+次のページへ進んでください。",
+ 'config-install-begin' => '「{{int:config-continue}}」を押すと、MediaWikiのインストールを開始することができます。
+変更したい設定があれば、「{{int:Config-back}}」を押してください。',
+ 'config-install-step-done' => '実行',
+ 'config-install-step-failed' => '失敗した',
+ 'config-install-extensions' => '拡張機能を含む',
+ 'config-install-database' => 'データベースの構築',
+ 'config-install-pg-schema-failed' => 'テーブルの作成に失敗した。
+ユーザ"$1"が図式"$2"に書き込みができるようにしてください。',
+ 'config-install-pg-commit' => '変更を送信',
+ 'config-install-user' => 'データベースユーザを作成する',
+ 'config-install-user-grant-failed' => 'ユーザー「$1」に許可を与えることに失敗しました。:$2',
+ 'config-install-tables' => 'テーブルの作成',
+ 'config-install-tables-exist' => "'''警告''':MediaWikiテーブルが、すでに存在しているようです。
+作成を飛ばします。",
+ 'config-install-tables-failed' => "'''エラー''':テーブルの作成が、次のエラーにより失敗しました:$1",
+ 'config-install-interwiki' => '既定のウィキ間テーブルを導入しています',
+ 'config-install-interwiki-list' => 'ファイル<code>interwiki.list</code>を見つけることができませんでした。',
+ 'config-install-interwiki-exists' => "'''警告''':ウィキ間テーブルはすでに登録されているようです。
+既定のテーブルを無視します。",
+ 'config-install-keys' => '秘密鍵を生成する',
+ 'config-install-sysop' => '管理者のユーザーアカウントを作成する',
+ 'config-install-mainpage' => '既定の接続でメインページを作成',
+ 'config-install-mainpage-failed' => 'メインページを挿入できませんでした:$1',
+ 'config-install-done' => "'''おめでとうございます!'''
+MediaWikiのインストールに成功しました。
+
+<code>LocalSettings.php</code>ファイルが生成されました。
+すべての設定がそのファイルに含まれています。
+
+それをダウンロードし、ウィキをインストールした基準ディレクトリー(index.phpと同じディレクトリー)に置く必要があります。ダウンロードは自動的に開始しているはずです。
+
+ダウンロードが開始していない場合、またダウンロードをキャンセルした場合は、以下のリンクからダウンロードを再開することができます:
+
+$3
+
+'''注意''': もし、これを今しなければ、つまり、このファイルをダウンロードせずインストールを終了した場合、この生成された設定ファイルは利用されません。
+
+それを完了すれば、'''[$2 ウィキに入る]'''ことができます。",
+ 'config-download-localsettings' => 'LocalSettings.phpをダウンロード',
+ 'config-help' => 'ヘルプ',
+);
+
+/** Khmer (ភាសាខ្មែរ)
+ * @author គីមស៊្រុន
+ */
+$messages['km'] = array(
+ 'config-your-language' => 'ភាសារបស់អ្នក៖',
+ 'config-your-language-help' => 'ជ្រើសយកភាសាដើម្បីប្រើក្នុងពេលតំលើង។',
+ 'config-wiki-language' => 'ភាសាវិគី៖',
+ 'config-wiki-language-help' => 'ជ្រើសរើសភាសាដែលវិគីនេះប្រើជាចំបង។',
+ 'config-back' => '← ត្រលប់ក្រោយ',
+ 'config-continue' => 'បន្ត →',
+ 'config-page-language' => 'ភាសា',
+ 'config-page-welcome' => 'មេឌាវិគីសូមស្វាគមន៍!',
+ 'config-page-dbconnect' => 'ភ្ជាប់ទៅមូលដ្ឋានទិន្នន័យ',
+ 'config-page-name' => 'ឈ្មោះ',
+ 'config-page-options' => 'ជំរើស',
+ 'config-page-install' => 'តំលើង',
+ 'config-page-complete' => 'បញ្ចប់!',
+ 'config-page-restart' => 'តំលើងឡើងវិញ',
+ 'config-help' => 'ជំនួយ',
+);
+
+/** Colognian (Ripoarisch)
+ * @author Purodha
+ */
+$messages['ksh'] = array(
+ 'config-desc' => 'Et Projramm för Mediwiki opzesäze.',
+ 'config-title' => 'MediaWiki $1 opsäze',
+ 'config-information' => 'Enfomazjuhn',
+ 'config-localsettings-upgrade' => 'De Dattei <code lang="en">LocalSettings.php</code> es ald doh.
+De Projramme vum Wiki künne op der neußte Shtand jebraat wääde:
+Donn doför dä Wäät vum <code lang="en">$wgUpgradeKey</code> en dat heh Feld enjävve.
+Do fenggs_et en dä Dattei <code lang="en">LocalSettings.php</code> om ẞööver.',
+ 'config-localsettings-cli-upgrade' => 'En Dattei <code lang="en">LocalSettings.php</code> es jefonge woode.
+Öm et Wiki_Projramm op ene neue Shtand ze bränge, donn <code lang="en">update.php</code> oproofe.',
+ 'config-localsettings-key' => 'Der Schlößel för et Projramm op ene neue Schtand ze bränge:',
+ 'config-localsettings-badkey' => 'Dinge Schlößel paß nit.',
+ 'config-upgrade-key-missing' => 'Mer han jefonge, dat MediaWiki ald enschtalleed es.
+Üm de Projramme un Daate o der neue Schtand bränge ze künne, dunn aan et Engk vun dä Dattei <code lang="en">LocalSettings.php</code> op dämm ẞööver:
+
+$1
+
+aanhange.',
+ 'config-localsettings-incomplete' => 'Mer han en Dattei <code lang="en">LocalSettings.php:</code> jefonge, ävver di schingk nit kumplätt ze sin.
+De Varijable <code lang="en">$1</code> es nit jesatz.
+Bes esu joot, un donn di Dattei esu aanpaße, dat se jesaz ea, un dann donn op „{{int:config-continue}}“ klecke.',
+ 'config-localsettings-connection-error' => 'Ene Fähler es opjetrodde wi mer en Verbendung noh de Datebangk opmaache wullte met dä Enshtellunge uß dä Dattei <code lang="en">LocalSettings</code> udder uß dä Dattei <code lang="en">LocalSettings</code> un et hät nit jeflupp. Bes esu joot un dat repareere un versöhg et dann norr_ens.
+
+$1',
+ 'config-session-error' => 'Ene Fähler es opjetrodde beim Aanmelde för en Sezung: $1',
+ 'config-session-expired' => 'De Daate för Ding Setzung sinn wall övverholld of afjeloufe.
+De Setzungunge sin esu enjeshtallt, nit mieh wi $1 ze doore.
+Dat kanns De verlängere, endämm dat De de <code lang="en">session.gc_maxlifetime</code> en dä Dattei <code>php.ini</code> jrüüßer määß.
+Don dat Projramm för et Opsäze norr_ens aanschmiiße.',
+ 'config-no-session' => 'De Daate för Ding Setzung sinn verschött jejange.
+Donn en dä Dattei <code>php.ini</code> nohloore, ov dä <code lang="en">session.save_path</code> op e zopaß Verzeijschneß zeisch.',
+ 'config-your-language' => 'Ding Shprooch:',
+ 'config-your-language-help' => 'Donn heh di Shprooch ußsöhke, di dat Enshtallzjuhnsprojramm kalle sull.',
+ 'config-wiki-language' => 'Dem Wiki sing Shprooch:',
+ 'config-wiki-language-help' => 'Donn heh di Shprooch ußsöhke, di et Wiki shtandattmääßesch kalle sull.',
+ 'config-back' => '← Retuur',
+ 'config-continue' => 'Wigger →',
+ 'config-page-language' => 'Shprooch',
+ 'config-page-welcome' => 'Wellkumme beim MediaWiki!',
+ 'config-page-dbconnect' => 'Met dä Daatebangk Verbenge',
+ 'config-page-upgrade' => 'En Inshtallzjuhn op der neuste Shtand bränge',
+ 'config-page-dbsettings' => 'Parrameeter för de Daatebangk',
+ 'config-page-name' => 'Name',
+ 'config-page-options' => 'Ennställunge',
+ 'config-page-install' => 'Opsäzze',
+ 'config-page-complete' => 'Fäädesch!',
+ 'config-page-restart' => 'Et Opsäze norr_ens neu aanfange',
+ 'config-page-readme' => 'Donn mesch lässe! (<i lang="en">read me</i>)',
+ 'config-page-releasenotes' => 'Henwies för heh di Version vum Projramm (<i lang="en">Release notes</i>)',
+ 'config-page-copying' => 'Ben aam Kopeere',
+ 'config-page-upgradedoc' => 'Ben op der neuste Stand aam bränge',
+ 'config-page-existingwiki' => 'Mer han ald e Wiki!',
+ 'config-help-restart' => 'Wells De all Ding enjejovve Sachee fottjeschmesse han, un dä janze Vörjang vun fürre aan neu aanfange?',
+ 'config-restart' => 'Joh, neu aanfange!',
+ 'config-welcome' => '=== Ömjevong Prööfe ===
+Mer maache en Aanzal jrundlääje Prövunge, öm erus ze fenge, ov di Ömjevong heh paß, för Mediawiki opzesäze.
+Wann de Hölp bem Opsäze bruchs, donn wigger ssare, wat erus kohm, wat heh shteiht.',
+ 'config-copyright' => "=== Urhävverrääsch un Lizänzbedengunge ===
+
+\$1
+
+Dat Projramm heh es frei, mer kann et wiggerjävve un verdeijle un och verändere ungger dä Bedengunge vun de GNU <i lang=\"en\">General Public License</i> (Alljemeine öffentlesche Lizänz) wi se vun de <i lang=\"en\">Free Software Foundation</i> (de Shteftung för frei Projramme) veröffentlesch woode es. Dobei kanns De Der de Version 2 vun dä Lizanz ußsöhke, udder jeede Version donoh, wi et Der jefällt.
+
+Dat Projramm weed wigger jejovve met dä Hoffnung, dat et jät nöz, ävver '''ohne Jarrantie''', sujaa ohne de onußjeshproche Jarantie, '''verkoufbaa''' ze sin, udder '''för öhnds_ene beshtemmpte Zweck ze bruche''' ze sin.
+Liß de GNU <i lang=\"en\">General Public License</i> sellver, öm mieh ze erfahre.
+
+Do sullts en <doclink href=Copying>Kopie vun dä alljemene öffentlesche Lizänz vun dä GNU</doclink> (<i lang=\"en\">GNU General Public License</i>) zosamme met heh däm Projramm krääje han. Wann dat nit esu es, schrief aan de <i lang=\"en\">Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA</i>. udder [http://www.gnu.org/copyleft/gpl.html liß se online övver et Internet].",
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki sing Hompäjdsch]
+* [http://www.mediawiki.org/wiki/Help:Contents Handbooch för Aanwender]
+* [http://www.mediawiki.org/wiki/Manual:Contents Handbooch för Administratore un Wiki_Köbesse]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Öff jeshtallte Froore met Antwoote]
+----
+* <doclink href=Readme>Liß Mesch! (<i lang="en">Read me</i>)</doclink>
+* <doclink href=ReleaseNotes><i lang="en">Release notes</i> Övver heh di Projrammversion</doclink>
+* <doclink href=Copying><i lang="en">Copying</i> — Lizänzbeshtemmunge</doclink>
+* <doclink href=UpgradeDoc><i lang="en">Upgrading</i> — Ob en neu Projrammversion jonn</doclink>',
+ 'config-env-good' => 'De Ömjävung es jeprööf.
+Do kanns MediaWiki opsäze.',
+ 'config-env-bad' => 'De Ömjävung es jeprööf.
+Do kanns MediaWiki nit opsäze.',
+ 'config-env-php' => 'PHP $1 es doh.',
+ 'config-env-php-toolow' => 'PHP $1 es enshtalleert.
+Ävver MediaWiki bruch PHP $2 udder hühter.',
+ 'config-unicode-using-utf8' => 'För et <i lang="en">Unicode</i>-Nommaliseere dom_mer däm <i lang="en">Brion Vibber</i> sing Projramm <code lang="en">utf8_normalize.so</code> nämme.',
+ 'config-unicode-using-intl' => 'För et <i lang="en">Unicode</i>-Nommaliseere dom_mer dä [http://pecl.php.net/intl Zohsaz <code lang="en">intl</code> uss em <code lang="en">PECL</code>] nämme.',
+ 'config-unicode-pure-php-warning' => '\'\'\'Opjepaß:\'\'\' Mer kunnte dä [http://pecl.php.net/intl Zohsaz <code lang="en">intl</code> uss em <code lang="en">PECL</code>] för et <i lang="en">Unicode</i>-Nommaliseere nit fenge. Dröm nämme mer dat eijfache, ävver ärsh lahme, <i lang="en">PHP</i>-Projrammshtöck doför.
+För jruuße Wikis met vill Metmaachere doht Üsch die Sigg övver et [http://www.mediawiki.org/wiki/Unicode_normalization_considerations <i lang="en">Unicode</i>-Nommaliseere] (es op Änglesch) aanloore.',
+ 'config-unicode-update-warning' => "'''Opjepaß:''' Dat Projramm för der <i lang=\"en\">Unicode</i> zo normaliseere boud em Momang op en ählter Version vun dä Bibliothek vum [http://site.icu-project.org/ ICU-Projäk] op.
+Doht di [http://www.mediawiki.org/wiki/Unicode_normalization_considerations op der neuste Shtand bränge], wann auf dat Wiki em Äänz <i lang=\"en\">Unicode</i> bruche sull.",
+ 'config-no-db' => 'Mer kunnte kei zopaß Daatebangk-Driiverprojamm fenge.',
+ 'config-no-db-help' => 'Mer bruche e Daatebangk-Driiverprojamm för PHP. Dat moß enjeresht wääde.
+Mer künne met heh dä Daatebangke ömjonn: $1.
+
+Wann De nit om eijene Rääshner bes, moß De Dinge <i lang="en">provider</i> bedde, dat hä Der ene zopaß Driiver enresht.
+Wann de PHP sellver övversaz häs, donn ene Zohjang för en Daatebangk enbenge, för e Beishpell met: <code lang="en">./configure --with-mysql</code> op ene <i lang="en">command shell</i>.
+Wann De PHP uss enem <i lang="en">Debian</i> udder <i lang="en">Ubuntu</i> Pakätt enjeresht häs, moß De dann och noch et <code lang="en">php5-mysql</code> op Dinge Räschner bränge.',
+ 'config-no-fts3' => "'''Opjepaß:''' De Projramme vum <i lang=\"en\">SQLite</i> sin der ohne et [http://sqlite.org/fts3.html FTS3-Modul] övversaz, dröm wääde de Funxjohne för et Söhke fähle.",
+ 'config-register-globals' => "'''Opjepaß:''' dem PHP singe Schallder <code lang=\"en\">[http://php.net/register_globals register_globals]</code> es enjeschalldt.
+'''Donn dä ußmaache, wann De kann.'''
+MediaWiki löp och esu, dä künnt ävver Sesherheitslöcke opmaache, di mer noch nit jefonge un eruß jemaat hät.",
+ 'config-magic-quotes-runtime' => "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]</code> es enjeschalldt.
+Dä määt enjejovve Daate kapott, un doh draan kam_mer dann nix mieh repareere.
+Domet kam_mer MediaWiki nit ennreeshte un och nit loufe lohße.
+Dat heiß, mer moß en affschallde, söns jeiht nix.",
+ 'config-magic-quotes-sybase' => "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> es enjeschalldt.
+Dä määt enjejovve Daate kapott, un doh draan kam_mer dann nix mieh repareere.
+Domet kam_mer MediaWiki nit ennreeshte un och nit loufe lohße.
+Dat heiß, mer moß en affschallde, söns jeiht nix.",
+ 'config-mbstring' => "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> es enjeschalldt.
+Dat sorresch för Fähler un kann enjejovve Daate esu kapott maach, dat doh draan nix mieh ze repareere es.
+Domet kam_mer MediaWiki nit ennreeshte un och nit loufe lohße.
+Dat heiß, mer moß en affschallde, söns jeiht nix.",
+ 'config-ze1' => "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]</code> es enjeschalldt.
+Dat sorresch för schräcklejje Fähler em MediaWiki.
+Dat kam_mer domet nit ennreeshte un och nit loufe lohße.
+Dat heiß, mer moß en affschallde, söns jeiht nix.",
+ 'config-safe-mode' => "'''Opjepaß:''' Dem PHP singe <code lang=\"en\">[http://www.php.net/features.safe-mode safe mode]</code> es aanjeschalldt. Dat kann Ärjer maache, besönders beim Datteie Huhlaade bei de Ongershtözung för <code lang=\"en\">math</code>-Befähle.",
+ 'config-xml-bad' => 'Dem PHP sing XML-Modul es nit ze fenge.
+MediaWiki bruch Funxjohne en däm Modul un deiht et esu nit.
+Wann De <i lang="en">Mandrake</i> aam loufehäs, donn dat Pakätt <code lang="en">php-xml</code> enstalleere.',
+ 'config-pcre' => 'Dem PHP sing Modul för <i lang="en">PCRE</i> schingk ze fähle.
+MediaWiki deiht et nit ohne de Funxjohne för de <i lang="en">Perl-compatible regular expressions</i>.',
+ 'config-pcre-no-utf8' => "'''Dä:''' Et PHP-Modul <i lang=\"en\">PCRE</i> schingk ohne de <i lang=\"en\">PCRE_UTF8</i>-Aandeile övversaz ze sin.
+MediaWiki bruch dä UTF-8-Krohm ävver, öm ohne Fähler loufe ze künne.",
+ 'config-memory-raised' => 'Der jrühzte zohjelasse Shpeisherbedarf vum PHP, et <code lang="en">memory_limit</code>, shtund op $1 un es op $2 erop jesaz woode.',
+ 'config-memory-bad' => "'''Opjepaß:''' Dem PHP singe Parameeter <code lang=\"en\">memory_limit</code> es \$1.
+Dat es wall ze winnisch.
+Et Enreeschte kunnt doh draan kappott jon!",
+ 'config-xcache' => 'Dä <code lang="en">[http://trac.lighttpd.net/xcache/ XCache]</code> es ennjeresht.',
+ 'config-apc' => 'Dä <code lang="en">[http://www.php.net/apc APC]</code> es ennjeresht.',
+ 'config-eaccel' => 'Dä <code lang="en">[http://eaccelerator.sourceforge.net/ eAccelerator]</code> es ennjeresht.',
+ 'config-wincache' => 'Dä <code lang="en">[http://www.iis.net/download/WinCacheForPhp WinCache]</code> es ennjeresht.',
+ 'config-no-cache' => '\'\'\'Opjepaß:\'\'\' Mer kunnte dä <code lang="en">[http://eaccelerator.sourceforge.net eAccelerator]</code>, dä <code lang="en">[http://www.php.net/apc APC]</code>, dä <code lang="en">[http://trac.lighttpd.net/xcache/ XCache]</code> un dä <code lang="en">[http://www.iis.net/download/WinCacheForPhp WinCache]</code> nit fenge.
+Et <i lang="en">object caching</i> es nit müjjelesh un ußjeschalldt.',
+ 'config-diff3-bad' => 'Mer han <i lang="en">GNU</i> <code lang="en">diff3</code> nit jefonge.',
+ 'config-imagemagick' => 'Mer han <i lang="en">ImageMagick</i> jefonge: <code>$1</code>.
+Et Ömrääschne en Minni-Beldsche weed müjjelesch sin, wann De et Belder Huhlaade zohlöhß.',
+ 'config-gd' => 'Mer han de ennjeboute GD-Jrafik-Projramm-Biblijotheek jefonge.
+Et Ömrääschne en Minni-Beldsche weed müjjelesch sin, wann De et Belder Huhlaade zohlöhß.',
+ 'config-no-scaling' => 'Mer han weeder de GD-Jrafik-Projramm-Biblijotheek, noch <i lang="en">ImageMagick</i> jefonge.
+Et Ömrääschne en Minni-Beldsche weed ußjeschalldt.',
+ 'config-no-uri' => "'''Fähler:''' Mer kunnte der aktoälle <i lang=\"en\">URI</i> nit erusfenge.
+Et Enreeschte es domet heh aam Engk.",
+ 'config-uploads-not-safe' => "'''Opjepaß:''' Uß däm jewöhnlijje Verzeichnes för de huhjelaade Datteie, dat es <code>$1</code>, künnte öhnzwällsche Skrepte un Projramme ußjeföhrt wääde. Och wann MediaWiki de huhjelaade Datteie prööf, dat kein bekannte Risike dren sin, sullt mer doch dat [http://www.mediawiki.org/wiki/Manual:Security#Upload_security Sesherheitsloch] zoh maache, ih dat mer et Dattei Huhlaade zohlöht.",
+ 'config-brokenlibxml' => 'Op Dingem Rääschner loufe Versione vun PHP un <code lang="en">libxml2</code> zosamme, di ävver nit zosamme paßße, un de Daate em MediaWiki un ander Web_Aanwändunge [http://bugs.php.net/bug.php?id=45996 bug kapott maache].
+Jangk op PHP 5.2.9 udder dohnoh un op <code lang="en">libxml2</code> 2.7.3 udder dohnoh.
+Heh jeihd et nit wigger.',
+ 'config-using531' => 'MediaWiki läuf nit met PHP $1 zosamme wääje enem [http://bugs.php.net/bug.php?id=50394 Fähler em Zosammehang met Parrameetere för <code lang="en">__call()</code>].
+Jangk op de Version 5.3.2 vum <i lang="en">PHP</i> ov dohnoh, udder op de Version 5.3.0 udder dovöör, öm dat Problem ze ömjonn.
+Heh jeiht et nit wigger.',
+ 'config-db-type' => 'De Zoot Daatebangk:',
+ 'config-db-host' => 'Dä Name vun däm Rääschner met dä Daatebangk:',
+ 'config-db-host-help' => 'Wann Dinge ẞööver för de Daatebangk ob enem andere Rääschner es, donn heh dämm singe Name udder <i lang="en">IP</i>-Addräß enjävve.
+
+Wann De ob enem Meetẞööver beß, weet Der Dinge Provaider odder däm sing Dokemäntazjuhn saare, wat De endraare moß.
+
+Wann De ob enem ẞööver onger <i lang="en">Windows</i> am enshtalleere bes un en <i lang="en">MySQL</i>-Daatebangk häs, künnd_et sin, dat „<code lang="en">localhost</code>“ nit douch för der Name vum ẞööver. Wann dad-esu es, versöhg et ens met „<code lang="en">127.0.0.1</code>“ als <i lang="en">IP</i>-Addräß vum eije Rääschner.',
+ 'config-db-host-oracle' => 'Dä Daatebangk ier <i lang="en" title="Transparent Network Substrate">TNS</i>:',
+ 'config-db-host-oracle-help' => 'Donn ene jöltije [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm „<i lang="en">Local Connect</i>“-Name] aanjävve. De Dattei „<code lang="en">tnsnames.ora</code>“ moß för heh dat Projamm seschbaa un ze Lässe sin.<br />Wann heh de Projamm_Biblijoteeke für de Aanwänderprojramme för de Version 10g udder neuer enjesaz wääde, kam_mer och et [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm „<i lang="en">Easy Connect</i>“] jenumme wääde för der Name ze verjävve.',
+ 'config-db-wiki-settings' => 'De Daate vum Wiki',
+ 'config-db-name' => 'Dä Name vun dä Daatebangk:',
+ 'config-db-name-help' => 'Jiff ene Name aan, dä för Ding Wiki passe deiht.
+Doh sullte kei Zweschrereum un kein Stresche dren sin.
+
+Wann De nit op Dingem eije Rääschner bes, künnt et sin, dat Dinge Provaider Der extra ene beshtemmpte Name för de Daatebangk jejovve hät, uffr dat de dä drom froore moß udder dat De de Daatebangke övver e Fommulaa selver enreeschte moß.',
+ 'config-db-name-oracle' => 'Schema för de Daatebangk:',
+ 'config-db-account-oracle-warn' => 'Mer han drei Aate, wi mer <i lang="en">Oracle</i> als Daatebangk aanbenge künne.
+
+Wann De ene neue Zohjang op de Daatenbangk met Naame un Paßwoot mem Projramm för et Opsäze aanlääje wells, dann jif ene Zohjang met däm Rääsch „<i lang="en">SYSDBA</i>“ aan, dä et alld jitt, un jif däm di Daate aan för dä neue Zohjang aanzelääje.
+Do kanns och dä neue Zohjang vun Hand aanlääje un heh beim Opsäze nur dää aanjävve — wann dä dat Rääsch hät, en de Daatebangk Schema_Objäkte aanzelääje.
+Udder De jiß zwei ongerscheidlijje Zohjäng op de Daatenbangk aan, woh eine vun dat Rääsch zom Aanlääje hät un dä andere moß dat nit un es för der nomaale Bedrief zohshtändesch.
+
+En Skrep, wat ene Zohjang op de Daatenbangk aanlääsch met all dä nüüdejje Rääschde, fengks De em Verzeishneß <code lang="en">maintenance/oracle/</code> vun Dingem MediaWiki. Donn draan dengke, dat ene Zohjang met beschrängkte Rääschde all di Müjjeleschkeite för et Waade un Repareere nit hät, di de jewöhnlejje Zoot Zohjang met sesh brängk.',
+ 'config-db-install-account' => 'Der Zohjang för en Enreeschte',
+ 'config-db-username' => 'Dä Name vun däm Aanwender för dä Zohjref op de Daatebangk:',
+ 'config-db-password' => 'Et Paßwoot vun däm Aanwender för dä Zohjref op de Daatebangk:',
+ 'config-db-password-empty' => 'Jiv e Paßwoot aan, för dä neue Aanwender för dä Zohjref op de Daatebangk, $1.
+Ed es zwa müjjelesch, Aanwender för dä Zohjref op de Daatebangk der ohne e Paßwoot aanzelääje,
+ävver dat wöhr en schwere Jevah för de Sescherheit vum Wiki.',
+ 'config-db-install-username' => 'Jiv ene Name aan för dä Aanwender för dä Zohjref op de Daatebangk beim Enshtalleere.
+Dat es keine Metmaacher_Name em Wiki — heh dä Name es alleins en der Daatebangk bikannt.',
+ 'config-db-install-password' => 'Jiv e Paßwoot aan för dä Aanwender för dä Zohjref op de Daatebangk beim Enshtalleere.
+Dat es kei Paßwoot för ene Metmaacher em Wiki — et es alleins en der Daatebangk bikannt.',
+ 'config-db-install-help' => 'Donn dä Name un et Paßwoot vun däm Aanwänder för der Zohjreff op de Daatebangk jäz för et Enreeshte aanjävve.',
+ 'config-db-account-lock' => 'Donn dersälve Name un et sälve Paßwoot för der nomaale Bedrief vum Wiki bruche',
+ 'config-db-wiki-account' => 'Dä Name vun däm Aanwender för dä Zohjref op de Daatebangk em nomaale Bedrief:',
+ 'config-db-wiki-help' => 'Jiv ene Name un e Paßwoot aan, för dä Aanwender för dä Zohjref op de Daatebangk, wann et wiki nommaal aam Loufe es.
+Wan et dä Name en der Daatebangk noch it jit, un dä Aanwender för dä Zohjref op de Daatebangk beim Enshtalleere
+jenooch Beräschtijunge hät, läät dä heh dä Aanwender en der Daatebangk aan un jidd_em di Rääschde, di dä nüüdesch hät, ävver nit mieh.',
+ 'config-db-prefix' => 'Vörsaz för de Name vun de Tabälle en de Daatebangk:',
+ 'config-db-prefix-help' => 'Wann ein Daatebangk för mieh wi ein Wiki udder e Wiki uns söns jät zosamme jebruch weed, dann kam_mer noch jet vör de Tabälle ier Name säze. Esu ene Vörsaz sull dubblte Tabällename vermeide hälfe.
+Donn kein Zwescheräum enjävve!
+
+Jewöhnlesch bliev dat Feld heh ävver läddesch.',
+ 'config-db-charset' => 'Dä Daatebangk iere Zeishesaz',
+ 'config-charset-mysql5-binary' => 'MySQL (4.1 udder 5.0) binär',
+ 'config-charset-mysql5' => 'MySQL (4.1 udder 5.0) UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 röckwääts kompatibel UTF-8',
+ 'config-charset-help' => "''' Opjepaß:'''
+Wann De et '''röckwääts kompatibel UTF-8 Fommaat''' nemmps, met dem <i lang=\"en\">MySQL</i> singe Version4.1 udder hüüter, dann künnt dat all di Zeishe kappott maache, die nit em <i lang=\"en\" title=\"American Standard Code for Information Interchange\">ASCII</i> sen, un domet all ding Sesherungskopieje kapott maache, wat mer nieh mieh retuur krijje kann.
+
+Beim Shpeishere em '''binäre Fomaat''' deiht MediaWiki de Täxte, di em UTF-8 Fommaat kumme, en dä Daatebangk en binär kodeerte Daatefälder faßhallde.
+Dat es flöcker un spaasaamer wi et UTF-8 Fommaat vum <i lang=\"en\">MySQL</i> un määd et müjjelesch, all un jeedes <i lang=\"en\">Unicode</i>-Zeishe met faßzehallde.
+
+Beim Shpeishere em '''UTF-8 Fomaat''' deiht et <i lang=\"en\">MySQL</i> der Zeishesaz un de Kodeerung vun dä Daate känne, un kann se akeraat aanzeije un ömwandelle,
+allerdengs künne kein Zeishe ußerhalv vum [http://de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke jrundlääje Knubbel för vill Shprooche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeshpeishert wääde.",
+ 'config-mysql-old' => 'Mer bruche <i lang="en">MySQL</i> $1 udder neuer. Em Momang es <i lang="en">MySQL</i> $2 aam Loufe.',
+ 'config-db-port' => 'De Pooz-Nommer (<i lang="en">port</i>) för de Daatebangk:',
+ 'config-db-schema' => 'Et Schema en de Datebangk för MediaWiki:',
+ 'config-db-schema-help' => 'För jewöhnlesch es dat Schema en Odenong.
+Donn bloß jät draan ändere, wann De sescher weiß, dat dat nüüdesch es.',
+ 'config-sqlite-dir' => 'Dem <i lang="en">SQLite</i> sing Daateverzeishnes:',
+ 'config-sqlite-dir-help' => '<i lang="en">SQLite</i> hät all sing Daate zosamme en en einzel Dattei.
+
+En dat Verzeishneß, wat De aanjiß, moß dat Web_ẞööver_Projramm beim Opsäze eren schriive dörrve.
+
+Dat Verzeishneß sullt \'\'\'nit\'\'\' övver et Web zohjänglesch sin, dröm dom_mer et nit dohen, woh de <i lang="en">PHP</i>-Datteije sin.
+
+Mer donn beim Opsäze zwa uß Vöörssh en <code lang="en">.htaccess</code> Dattei dobei, ävver wann di nit werrek, künnte Lück vun ußerhallef aan Ding Daatebangk_Dattei eraan kumme.
+Doh shtonn Saache dren, wi de Addräße för de Metmaacher ier <i lang="en">e-mail</i> un de verschlößelte Paßwööter un de vershtoche un de fottjeschmeße Sigge un ander Saache ussem Wiki, di mer nit öffentlesch maache darref.
+
+Donn Ding Daatebangk et beß janz woh anders hen, noh <code lang="en">/var/lib/mediawiki/\'\'wikiname\'\'</code> för e Beishpell.',
+ 'config-oracle-def-ts' => 'Tabälleroum för der Shtandattjebruch:',
+ 'config-oracle-temp-ts' => 'Tabälleroum för der Jebruch zweschedorsh:',
+ 'config-type-mysql' => '<i lang="en">MySQL</i>',
+ 'config-type-postgres' => '<i lang="en">PostgreSQL</i>',
+ 'config-type-sqlite' => '<i lang="en">SQLite</i>',
+ 'config-type-oracle' => '<i lang="en">Oracle</i>',
+ 'config-support-info' => 'MediaWiki kann met heh dä Daatebangk_Süßteeme zosamme jonn:
+
+$1
+
+Wann dat Daatebangk_Süßteem, wat De nämme wells, onge nit dobei es, dann donn desch aan di Aanleidonge hallde, di bovve verlengk sen, öm et op Dingem ẞööver singem Süßteem müjjelesh ze maache, se aan et Loufe ze krijje.',
+ 'config-support-mysql' => '* <i lang="en">$1</i> es dat vum MediaWiki et eets ongershtöz Daatebangksüßteem ([http://www.php.net/manual/de/mysql.installation.php Aanleidung för et Övversäze un Enreeschte von PHP met <i lang="en">MySQL</i> dobei, op Deutsch])',
+ 'config-support-postgres' => '* <i lang="en">$1</i> es e bikannt Daatebangksüßteem met offe Quälltäxde, un en och en Wahl nävve <i lang="en">MySQL</i> ([http://www.php.net/manual/de/pgsql.installation.php Aanleidung för et Övversäze un Enreeschte von PHP met <i lang="en">PostgreSQL</i> dobei, op Deutsch]) Et sinn_er ävver paa klein Fählershe bekannt, um kunne dat em Momang för et reschtijje Werke nit emfähle.',
+ 'config-support-sqlite' => '* <i lang="en">$1</i> es e eijfach Daatebangksüßteem, wat joot ongershtöz weed. ([http://www.php.net/manual/de/pdo.installation.php Aanleidong för et Övversäze un Enreeschte von PHP met <i lang="en">SQLite</i> dobei, op Deutsch])',
+ 'config-support-oracle' => '* <i lang="en">$1</i> es e jeschäfflesch Daatebangksüßteem för Ferme. ([http://www.php.net/manual/de/oci8.installation.php Aanleidong för et Övversäze un Enreeschte von PHP met <i lang="en">OCI8</i> dobei, op Deutsch])',
+ 'config-header-mysql' => 'De Enshtällunge för de <i lang="en">MySQL</i> Daatebangk',
+ 'config-header-postgres' => 'De Enshtällunge för de <i lang="en">PostgreSQL</i> Daatebangk',
+ 'config-header-sqlite' => 'De Enshtällunge för de <i lang="en">SQLite</i> Daatebangk',
+ 'config-header-oracle' => 'De Enshtällunge för de <i lang="en">Oracle</i> Daatebangk',
+ 'config-invalid-db-type' => 'Dat es en onjöltijje Zoot Daatebangk.',
+ 'config-missing-db-name' => 'Do moß jät enjävve för dä Name vun dä Daatebangk.',
+ 'config-missing-db-host' => 'Do moß jät enjävve för dä Name vun däm Rääschner met dä Daatebangk.',
+ 'config-missing-db-server-oracle' => 'Do moß jät enjävve för dä Daatebangk ier <i lang="en" title="Transparent Network Substrate">TNS</i>.',
+ 'config-invalid-db-server-oracle' => 'Dä Daatebangk ier <i lang="en" title="Transparent Network Substrate">TNS</i> kann nit „$1“ sin, dat es esu nit jöltesch.
+Döh dörve bloß <i lang="en" title="American Standard Code for Information Interchange">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), Ongerstreshe (_), un Punkte (.) dren vörkumme.',
+ 'config-invalid-db-name' => 'Dä Daatebangk iere Name kann nit „$1“ sin, dä es esu nit jöltesch.
+Döh dörve bloß <i lang="en" title="American Standard Code for Information Interchange">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), Ongerstresh (_), un Bendeshtresh (-) dren vörkumme.',
+ 'config-invalid-db-prefix' => 'Dä Vörsaz för de Name vun de Tabälle en de Daatebangk kann nit „$1“ sin, dä es esu nit jöltesch.
+Döh dörve bloß <i lang="en" title="American Standard Code for Information Interchange">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), Ongerstreshe (_), un Bendeshtreshe (-) dren vörkumme.',
+ 'config-connection-error' => '$1.
+
+Donn de Name för dä Rääschner, vun däm Aanwender för dä Zohjref op de Daatebangk, un et Paßwoot prööfe, repareere, un dann versöhg et norr_ens.',
+ 'config-invalid-schema' => 'Dat Schema för MediaWiki kann nit „$1“ sin, dä Name wöhr esu nit jöltesch.
+Döh dörve bloß <i lang="en" title="American Standard Code for Information Interchange">ASCII</i> Boochshtaabe (a-z, A-Z), Zahle (0-9), un Ongerstreshe (_) dren vörkumme.',
+ 'config-db-sys-create-oracle' => 'Dat Projramm för MediaWiki opzesäze kann bloß <i lang="en">SYSDBA</i> bruche för ene neue Zohjang zor Daatebangk enzereeschte!',
+ 'config-db-sys-user-exists-oracle' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk jidd_et ald. <i lang="en">SYSDBA</i> kam_mer bloß bruche, för ene neue Zohjang enzereeschte!',
+ 'config-postgres-old' => 'Mer bruche <i lang="en">PostgreSQL</i> $1 udder neuer. Em Momang es <i lang="en">PostgreSQL</i> $2 aam Loufe.',
+ 'config-sqlite-name-help' => 'Söhk enen Name uß, dä Ding Wiki beschrief.
+Donn kein Bendeschresch un Zweschräum en däm Name bruche.
+Dä Name weed för der Dateiname för de <i lang="en">SQLite</i> Daatebangk jenumme.',
+ 'config-sqlite-parent-unwritable-group' => 'Mer kunnte dat Verzeischneß för de Daate, <code lang="en"><nowiki>$1</nowiki></code>, nit enreeschte, weil dat Projramm fö dä Web_ẞööver en dat Verzeischneß doh drövver, <code><nowiki>$2</nowiki></code>, nix erin donn darref.
+
+Mer han dä Name vun däm Zohjang op et Süßteem eruß jefonge, onger dämm dat Web_ẞööver_Projramm läuf. Jez moß De bloß doför sorrje, dat dä en dat Verzeischneß <code><nowiki>$3</nowiki></code> schrieve kann, öm heh wigger maache ze künne.
+Ob enem Süßteem met <i lang="en">Unix</i>- oder <i lang="en">Linux</i> jeiht dat esu:
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Mer kunnte dat Verzeischneß för de Daate, <code lang="en"><nowiki>$1</nowiki></code>, nit enreeschte, weil dat Projramm fö dä Web_ẞööver en dat Verzeischneß doh drövver, <code><nowiki>$2</nowiki></code>, nix erin donn darref.
+
+Mer han dä Name vun däm Zohjang op et Süßteem nit eruß fenge künne, onger dämm dat Web_ẞööver_Projramm läuf. Jez moß De bloß doför sorrje, dat dä en dat Verzeischneß <code><nowiki>$3</nowiki></code> schrieve kann, öm heh wigger maache ze künne. Wann De dä Name och nit weiß, maach, dat jeeder_ein doh schrieve kann.
+Ob enem Süßteem met <i lang="en">Unix</i>- oder <i lang="en">Linux</i> jeiht dat esu:
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Ene Fähler es opjetrodde beim Aanlääje vum Daate_Verzeishneß „$1“.
+Don dä Plaz för et Shpeishere prööfe un Repareere, dann versöhg et norr_ens.',
+ 'config-sqlite-dir-unwritable' => 'Mer künne nit en dat Verzeishneß „$1“ schrieeve
+Donn dohvun de Zohjreffs_Rääschde esu verändere, dat der Webßööver doh dren schrieeve kann, un dann versöhg et norr_ens.',
+ 'config-sqlite-connection-error' => '$1.
+
+Donn onge dat Verzeishnes un der Name vun der Daatebangk prööfe un repareere, un dann versöhg_et norr-ens.',
+ 'config-sqlite-readonly' => 'En di Dattei <code lang="en">$1</code> künne mer nit schrieve.',
+ 'config-sqlite-cant-create-db' => 'Mer kunnte di Dattei <code lang="en">$1</code> för de Daatebangk nit aanlääje.',
+ 'config-sqlite-fts3-downgrade' => 'Dat PHP heh hät kein Ongershtözong för FTS3, dröm donn mer de Daatebangktabälle eronger shtoofe.',
+ 'config-can-upgrade' => 'Et sinn-er ald Daatebangktabelle vum MediaWiki en dä Daatebangk.
+Öm di op der Shtand vum MediaWiki $1 ze bränge, donn jäz op „{{int:config-continue}}“ klecke.',
+ 'config-upgrade-done' => "Alles es jäz om neue Shtand.
+
+Mer kann dat Wiki jäz [\$1 bruche].
+
+Wann De Ding Dattei <code lang=\"en\">LocalSettings.php</code> neu schrieve wells, donn onge op dä Knopp klicke.
+Dat dom_mer ävver '''nit vörschlonn'''em Jääjedeil, ußer, wann et Probleme mem Wiki jitt.",
+ 'config-upgrade-done-no-regenerate' => 'Alles es jäz om neue Shtand.
+
+Mer kann dat Wiki jäz [$1 bruche].',
+ 'config-regenerate' => 'Donn de Dattei <code lang="en">LocalSettings.php</code> neu opsäze →',
+ 'config-show-table-status' => 'Et Kommando <code lang="en">SHOW TABLE STATUS</code> aan de Daatebangk es donävve jejange!',
+ 'config-unknown-collation' => "'''Opjepaß:''' De Daatabangk deiht en onbikannte Reijefollsch bruche, för Booshtaabe un Zeishe ze verjliishe un ze zotteere.",
+ 'config-db-web-account' => 'Dä Zohjang zor Daatebangk fö et Wiki',
+ 'config-db-web-help' => 'Donn ene Name un e Paßwoot för der Zohjang zor Daatebangk för et Wiki em nomaale Bedrief aanjävve.',
+ 'config-db-web-account-same' => 'Donn dersällve Zohjang nämme, wi heh beim Opsäze.',
+ 'config-db-web-create' => 'Donn dä Zohjang aanlääje, wann dä noch nit doh es.',
+ 'config-db-web-no-create-privs' => 'Dä Zohjang för et Opsäze es nit berääschtesch, ene ander Zohjan enzereeschte.
+Dä aanjejovve Zohjang för der Nomaalbedrief moß dröm schunn enjersht sen!',
+ 'config-mysql-engine' => 'De Zoot udder et Fommaat vun de Tabälle:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' es fö jewöhnlesch et beß, weil vill Zohjreffe op eijmohl joot ongershtöz wääde.
+
+'''MyISAM''' es flöcker op Rääschnere met bloß einem Minsch draan, un bei Wikis, di mer bloß lässe un nit schrieeve kann.
+MyISAM-Daatebangke han em Schnett mieh Fähler un jon flöcker kappott, wi InnoDB-Daatebangke.",
+ 'config-mysql-charset' => 'Dä Daatebangk iere Zeishesaz:',
+ 'config-mysql-binary' => 'binär',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "Beim Shpeishere em '''binäre Fomaat''' deiht MediaWiki de Täxte, di em UTF-8 Fommaat kumme, en dä Daatebangk en binär kodeerte Daatefälder faßhallde.
+Dat es flöcker un spaasaamer wi et UTF-8 Fommaat vum <i lang=\"en\">MySQL</i> un määd et müjjelesch, all un jeedes <i lang=\"en\">Unicode</i>-Zeishe met faßzehallde.
+
+Beim Shpeishere em '''UTF-8 Fomaat''' deiht et <i lang=\"en\">MySQL</i> der Zeishesaz un de Kodeerung vun dä Daate känne, un kann se akeraat aanzeije un ömwandelle,
+allerdengs künne kein Zeishe ußerhalv vum [http://de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke jrundlääje Knubbel för vill Shprooche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeshpeishert wääde.",
+ 'config-site-name' => 'Däm Wiki singe Name:',
+ 'config-site-name-help' => 'Dä douch em Tittel vun de Brauserfinstere un aan ätlije andere Shtälle op.',
+ 'config-site-name-blank' => 'Donn ene Name för di Sait aanjävve.',
+ 'config-project-namespace' => 'Dä Name för et Appachtemang övver et Projäk:',
+ 'config-ns-generic' => 'Projäk',
+ 'config-ns-site-name' => 'Et sällve wi däm Wiki singe Name: $1',
+ 'config-ns-other' => 'Andere (jiff aan wälshe)',
+ 'config-ns-other-default' => 'MingWiki',
+ 'config-project-namespace-help' => "Noh dämm Vörbeld vun de Wikipeedija, donn vill Wikis dänne ier Sigge övver et Wiki un sing Rääjelle vun dä Sigge mem Enhald vum Wiki tränne, un en enem extra Appachtemang för et „'''Projäk'''“ afflääje.
+Sigge en däm Appachtemang fange all med enem beshtemmpte Vörsaz aan, däm Name vum Appachtemang, un dä moß De heh faßlääje.
+Dä Name kann beshtemmpte Zeiche nit enthallde, wi „#“ un „:“ un et es Tradizjuhn, dat hä vum Name vum Wiki her kütt.",
+ 'config-ns-invalid' => 'Dat aanjejovve Appachtemang „<nowiki>$1</nowiki>“ es nit jöltesch.
+Nemm ene andere Name för däm Wiki sing eije Appachtemang.',
+ 'config-ns-conflict' => 'Dat aanjejovve Appachtemang „<nowiki>$1</nowiki>“ kütt ald als Standatt-Appachtemang em MediaWiki vör.
+Nemm ene andere Name för däm Wiki sing eije Appachtemang.',
+ 'config-admin-box' => 'Der Zohjang för der eezte Wiki_Köbes',
+ 'config-admin-name' => 'Metmaacher_Name:',
+ 'config-admin-password' => 'Et Paßwoot:',
+ 'config-admin-password-confirm' => 'Norrens dat Paßwoot:',
+ 'config-admin-help' => 'Jif Dinge leevste Name als Metmaacher för Desch aan, för e Beishpell „Schmitzens Pitter“
+— Dat weed dä Name wääde, met dämm De Desch enlogge deihs.',
+ 'config-admin-name-blank' => 'Jiv ene Metmaacher_Name en för dä Wiki-Köbes.',
+ 'config-admin-name-invalid' => '„<nowiki>$1</nowiki>“ es keine jöltijje Metmaacher_Name.
+Jiv ene joode Name en!',
+ 'config-admin-password-blank' => 'Do mos_e Paßwoot för dä Wiki_Köbes aanjävve!',
+ 'config-admin-password-same' => 'Dat Paßwoot un dä Name dörve nit ejaal sin!',
+ 'config-admin-password-mismatch' => 'Di Paßwööter sin ongerscheidlesh!',
+ 'config-admin-email' => 'Addräß för de <i lang="en">e-mail</i>:',
+ 'config-admin-email-help' => 'Jiv heh di Adräß för de <i lang="en">e-mail</i> aan, woh De <i lang="en">e-mail</i> vun ander Metmaacher uss_em Wiki hen krijje wells, di et Der müjjelesh määt, Ding Paßwoot automatetsch truusche ze lohße, un woh Nohreeshte övver veränderte Sigge op Dinge Oppaßleß hen jescheck wääde sulle.
+De kanns dat Fäld ävver och läddesch lohße.',
+ 'config-admin-error-user' => 'Beim Enreeshte vum Zohjang för dä Wiki_Köbes „<nowiki>$1</nowiki>“ es ene Fähler em Wiki opjetrodde.',
+ 'config-admin-error-password' => 'Beim Paßwoot-Säze för dä Wiki_Köbes „<nowiki>$1</nowiki>“ es ene Fähler em Wiki opjetrodde.: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Do häs_en onjöltijje Addräß för de <i lang="en">e-mail</i> aanjejovve.',
+ 'config-subscribe' => 'Donn de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce <i lang="en">e-mail</i>-Leß met de Aanköndijunge vum MediaWiki] abonnere.',
+ 'config-subscribe-help' => 'Do kumme bloß winnish Meddeilunge un di jonn övver neu Versiohne vom MediaWiki un weeshtejje Saache vun däm sing Sesherheit.
+Do sullts se abbonneere, un Ding MediWiki_Projramme op der neue Shtand bränge, wann neu Version eruß kumme.',
+ 'config-almost-done' => 'Do bes beinah dorsh!
+Do künnts jez der Räß vun de einzel Enshtellunge övverjonn, un et Wiki tiräktemang fäädesch opsäze.',
+ 'config-optional-continue' => 'Wells De noch mieh Frore jeshtallt krijje un noch mieh Enshtällunge maache?',
+ 'config-optional-skip' => 'Nä, lohß dä Ömshtand, donn eifarr_et Wiki opsäze.',
+ 'config-profile' => 'Enshtällunge för de Metmaacher ier Rääschte:',
+ 'config-profile-wiki' => 'E tradizjonäll offe Wiki',
+ 'config-profile-no-anon' => 'Schriever möße enlogge',
+ 'config-profile-fishbowl' => 'Bloß ußdröcklesch zohjelohße Schriever',
+ 'config-profile-private' => 'E jeschloße Privat_Wiki',
+ 'config-profile-help' => "Wikis loufe et beß, wam_mer esu vill Lück wi müjjelesch draan metmaache un schrieve löht.
+Met MediaWiki es et ejfach, de neuste Änderunge ze beloore un wat ahnungslose udder fiese Lück kapott jemaat han wider retuur ze maache.
+
+Bloß, mänsh eine häd_eruß jefonge, dat mer MediaWiki jood en en jruuße Zahl ongerscheidlijje Rolle bruche kann, un nit emmer es et leish, ene vum onverfälschte Wiki_Wääsch ze övverzeuje.
+Esu häß De de Wahl:
+
+'''{{int:config-profile-wiki}}''' löht jeder_ein metschrieve, och ohne enzelogge.
+
+'''{{int:config-profile-no-anon}}''', dat sorsh för mieh seeshbaa Verantwootlishkeite, künnt ävver zohfällije Methellefer verschrecke.
+
+'''{{int:config-profile-fishbowl}}''' löht nor de ußjesöhk Metmaacher schrieve, ävver de janze Öffentleshkeit kann et lässe un süht och de ällder Versione, un wat wää wann draan jedonn hät.
+
+'''{{int:config-profile-private}}''' kann nur lässe, wäh en et Wiki zohjelohße es, un desellve Jropp kann uch schrieve.
+
+Noch ander un un opwändijere Enshtellunge för de Rääschte sin müjjelesch, wann et Wiki ens aam Loufe es. Loor Der doför de [http://www.mediawiki.org/wiki/Manual:User_rights zopaß Hölp em Handbooch] aan.",
+ 'config-license' => 'Urhävverrääsch un Lizänz:',
+ 'config-license-none' => 'Kein Fooßreih övver de Lizänz',
+ 'config-license-cc-by-sa' => '<i lang="en">Creative Commons</i> Der Name moß jenannt sin, et Wiggerjävve es zohjelohße onger dersellve Bedengunge',
+ 'config-license-cc-by-nc-sa' => '<i lang="en">Creative Commons</i> Nit för e Jeschäff ze maache, et Wiggerjävve es zohjelohße unger dersellve Bedengunge',
+ 'config-license-cc-0' => '<i lang="en">Creative Commons</i> „Noll“',
+ 'config-license-gfdl-old' => 'De <i lang="en">GNU</i>-Lizänz för frei Dokemäntazjuhne Version 1.2',
+ 'config-license-gfdl-current' => 'De <i lang="en">GNU</i>-Lizänz för frei Dokemäntazjuhne, Version 1.3 udder en späädere',
+ 'config-license-pd' => 'Allmende (jemeinfrei, <i lang="en">public domain</i>)',
+ 'config-license-cc-choose' => 'En <i lang="en">Creative Commons</i> Lizänz, sellver ußjesöhk:',
+ 'config-license-help' => "Ättlijje öffentleje Wikis donn iehr Beidrääsh onger en [http://freedomdefined.org/Definition frei Lizänz] shtelle.
+Dat hellef, e Jeföhl vun Jemeinsamkeid opzeboue, un op lange Seesh emmer wider Beidrääsch ze krijje.
+Dat es nit onbedengk nüüdesh för e Jeschäffs- udder Privaat_Wiki.
+
+Wä Stöcke uß de Wikipedia bruche well, un han well, dat de Wikipedia uss_em eije Wiki jät övvernämme kann, sullt mer „'''<i lang=\"en\">Creative Commons</i>, dem Schriever singe Name moß jenannt wääde, un Wiggerjävve zoh dersellve Bedengunge es zohjelohße'''“ ußwähle.
+
+De su jenannte '''<i lang=\"en\">GNU Free Documentation License</i>''' (de freije Lizänz för Dokemäntazjuhne vun dä GNU) sen de ahle Lizänzbedenonge vun de Wikipedia. Se es emmer noch in Odenong un jöltesch, ävver se hädd e paa Eijeschaffte, die et Wiggerjävve, et widder Verwände un et Ußlääje schwieeresch maache.",
+ 'config-email-settings' => 'Enschtellunge för de <i lang="en">e-mail</i>',
+ 'config-enable-email' => 'De <i lang="en">e-mail</i> noh druße zohlohße',
+ 'config-enable-email-help' => 'Sulle <i lang="en">e-mails</i> zohjelohße sin, moß mer, domet et noher flupp, de [http://www.php.net/manual/en/mail.configuration.php Enschtellunge em PHP för de <i lang="en">e-mails</i>] zopaß jemaat han.
+Wann kein <i lang="en">e-mails</i> nüüdesch sin, kam_mer se heh afschallde.',
+ 'config-email-user' => '<i lang="en">e-mails</i> zwesche de Metmaacher zohlohße',
+ 'config-email-user-help' => 'Määt et müjjelesch, dat sesch de Metmaacher jääjesiggesch <i lang="en">e-mails</i> schecke künne, wann se dat en iehre eije Enschtellunge och enjeschalldt han.',
+ 'config-email-usertalk' => '<i lang="en">e-mails</i> mem Bescheid zohlohße, dat einem sing Klaafsigg verändert woodt',
+ 'config-email-usertalk-help' => 'Maach et müjjelesch, dat Metmaaacher en iere Enstellunge <i lang="en">e-mails</i> mem Bescheid zohlohße, dat einem sing Klaafsigg verändert woodt.',
+ 'config-email-watchlist' => 'Nohreeschte övver Änderonge aan Sigg op de Opaßleßte zohlohße',
+ 'config-email-watchlist-help' => 'Lohß Metmaacher Nohreeshte övver de Sigge op dänne iehr Oppaßleß krijje, wann se et en iehre Enschtellonge ußjewählt han.',
+ 'config-email-auth' => 'Donn de Övverprööfung för Zohjangsberääschtejunge övver de <i lang="en">e-mail</i> zohlohße',
+ 'config-email-auth-help' => 'Wann dat aanjeschald es, möße Metmaacher, di iehr Adräß för de <i lang="en">e-mail</i> neu aanjävve udder ändere, di Addräß övver ene Lengk beschtäätejje, dä se met de <i lang="en">e-mail</i> jescheck krijje.
+Bloß aan esu beschtääteschte Adräße deiht et Wiki <i lang="en">e-mails</i> schecke, Di künne vun annder Metmaachere kumme, udder vum Wiki sellver, wann en Sigg en däm Metmaacher singe Oppaßleß verändert woode es.
+Mer \'\'\'schlonn vör, dat aanzeschallde\'\'\' för öffentlesch Wikis, weil sönß zoh leisch Driß mem Wiki singe <i lang="en">e-mail</i> jemaat wääde künnt.',
+ 'config-email-sender' => 'De Adräß för de Antwoote op <i lang="en">e-mails</i>:',
+ 'config-email-sender-help' => 'Jiff de Adräß för de <i lang="en">e-mail</i> en, woh Antwoote ob em Wiki singe <i lang="en">e-mails</i> hen jonn sulle.
+Dat es och de Adräß, woh de <i lang="en">e-mails</i> met Fählermäldonge hen jon.
+Vill ẞöövere för de <i lang="en">e-mail</i> welle winnischßdens ene jöltijje Domain en dä Adräß han.',
+ 'config-upload-settings' => 'Belder un Datteie huh laade',
+ 'config-upload-enable' => 'Belder un Datteie huh laade zohlohße',
+ 'config-upload-help' => 'Datteije huh ze laade künnt e Risiko för dem ẞööver singe Sescherheit sin.
+Mieh doh drövver kam_mer em [http://www.mediawiki.org/wiki/Manual:Security Kapitel övver de Sescherheit] em Handbooch lässe.
+
+Öm et Huhlaade zohzelohße donn de Rääschde för der Zohjreff op dat Ongerverzeischneß <code lang="en">images</code> em MediaWiki singem Houpverzeischneß esu enshtälle, dat et Webßööverprojramm doh Datteije un Verzeischneße eren schrieve kann.
+Donoh donn heh di Saach zohlohße.',
+ 'config-upload-deleted' => 'Dat Verzeishneß för fottjeschmeße Datteije:',
+ 'config-upload-deleted-help' => 'Söhk e Verzeijschneß uß för de fottjeschmeße Datteije vum Wiki dren afzelääje.
+Et bäß es, wam_mer vum <i lang="en">world wide web</i> doh nit drahn kumme kann.',
+ 'config-logo' => 'Dem Wiki singem Logo sing <i lang="en">URL</i>:',
+ 'config-logo-help' => 'De Schtandart_Bedeen_Bovverfläsch vum MediaWiki hät e Logo bovve en der Eck met 135x160 Pixele.
+Donn e zopaß Logo huh laade, un donn däm sing URL heh endraare.
+
+Wells De kei Logo han, draach heh nix en.',
+ 'config-instantcommons' => 'Donn <i lang="en">InstantCommons</i> zohlohße.',
+ 'config-instantcommons-help' => '<i lang="en">[http://www.mediawiki.org/wiki/InstantCommons InstantCommons]</i> es en Eijeschaff, di et för Wikis müjjelesch määt, Belder, Tondatteie un ander Meedijedatteie enzebenge, di op dä Webßait vun de <i lang="en">[http://commons.wikimedia.org/ Wikimedia Commons]</i> ongerjebraat sin. Öm dat noze ze künne, moß dä ẞööver vum MediaWiki en Verbendung nohm Internet opnämme künne.
+
+Mieh Aanjaabe doh drövver un en Aanleidung, wi mer och ander Wikis ußer de <i lang="en">Wikimedia Commons</i> doför enreeschte kann, fengk mer em [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos Handbooch].',
+ 'config-cc-error' => 'Et Ußsöhke övver de <i lang="en">Creative Commons</i> iehr Projramm zum Lizänzbeshtemme hät nix jebraat.
+Donn de Lizänz sellver beshtemme.',
+ 'config-cc-again' => 'Noch ens neu ußsöhke&nbsp;…',
+ 'config-cc-not-chosen' => 'Söhk uß, wat för en Lizänz vun de <i lang="en">Creative Commons</i> De han wells, un donn dann op „<i lang="en">proceed</i>“ klecke.',
+ 'config-advanced-settings' => 'Fottjeschredde Enshtellunge',
+ 'config-cache-options' => 'Enshtällunge för et Faßhallde vun Objäkte em Zweschsheisher:',
+ 'config-cache-help' => 'Objäkte em Zwescheshpeisher faßhallde, dat heiß öff jebruchte Daate en der <i lang="en">cache</i> donn, bruche mer, öm MediaWiki flöcker ze maache,
+Meddlere un jruuße Wiki-ẞaits sullte dat onbedengk ußnoze, un och bei klein Wikis weed mer et jood merke.',
+ 'config-cache-none' => 'Keine Zweschshpeijsher (Et jeid_em Wiki nix verloore, ußer velleish Schnälleshkeid wann vill loss es)',
+ 'config-cache-accel' => 'Ene Objäk<i lang="en">cache</i> vum PHP (<i lang="en">APC</i>, <i lang="en">eAccelerator</i>, <i lang="en">XCache</i>, udder <i lang="en">WinCache</i>)',
+ 'config-cache-memcached' => 'Donn der <code lang="en">memcached</code> ẞööver nämme (Määt extra Enshtellunge un Opsäze nüüdesch)',
+ 'config-memcached-servers' => 'De <code lang="en">memcached</code> ßöövere:',
+ 'config-memcached-help' => 'Donn de Leß aanhjävve, met de <i lang="en">IP</i>-Addräße för der <code lang="en">memcached</code> ẞööver ze bruche.
+Se sullte ein pro Reih opjeschrevve sin, un en Pooz (<i lang="en">port</i>) ier Nommer han, För e Beishpell, esu:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Do häss der <code lang="en">memcached</code> als Dinge Zoot vun Zwescheshpeijscher aanjejovve, ävver nit eine ẞööver doför.',
+ 'config-memcache-badip' => 'Do häss en onjöltijje <i lang="en">IP</i>-Addräß för der <code lang="en">memcached</code> ẞööver aanjejovve: $1.',
+ 'config-memcache-noport' => 'Do has kein Pooz (<code lang="en">port</code>) Nommer aanjejovve för mem <code lang="en">memcached</code> ẞööver ze bruche: $1.
+Wann De di Nommer nit weiß, der Shtandatt es 11211.',
+ 'config-memcache-badport' => 'Dem <code lang="en">memcached</code> ẞööver singe Pooz (<code lang="en">port</code>) Nommere sullte zwesche $1 un $2 sin.',
+ 'config-extensions' => 'Projramm-Zosätz (<i lang="en">extensions</i>)',
+ 'config-extensions-help' => 'Di bovve opjeleß Zohsazprojramme för et MediaWiki sin em Verzeischneß <code lang="en">./extensions</code> ald ze fenge.
+
+Do kann se heh un jez aanschallde, ävver se künnte noch zohsäzlesch Enshtellunge bruche.',
+ 'config-install-alreadydone' => "'''Opjepaß:'''
+Et sühd esu uß, wi wann De MediaWiki ald enshtalleet hätß, un wöhrs aam Versöhke, dat norr_ens ze donn.
+Jang wigger op de näähßte Sigg.",
+ 'config-install-begin' => 'Wann De op „{{int:config-continue}}“ klecks, jeiht de Enshtallazjuhn vum MediaWiki loßß.
+Wann De noch Änderonge maache wells, dann kleck op „{{int:config-back}}“.',
+ 'config-install-step-done' => 'jedonn',
+ 'config-install-step-failed' => 'donävve jejange',
+ 'config-install-extensions' => 'Zohsazprojramme enjeschloße',
+ 'config-install-database' => 'Ben de Daatebangk aam ennreeschte.',
+ 'config-install-pg-schema-not-exist' => 'Dat Scheema för <i lang="en">PostgreSQL</i> es nit doh.',
+ 'config-install-pg-schema-failed' => 'Et Tabälle-Opsäze es donävve jejange.
+Donn doför sorrje, dat dä Daatebangk-Aanwänder „$1“ en dämm Daatebangkscheema „$2“ schrieve kann.',
+ 'config-install-pg-commit' => 'Ben de Änderonge aam ennbränge.',
+ 'config-install-pg-plpgsql' => 'Ben noh dä Daatebangkshprooch <code lang="en">PL/pgSQL</code> aam söhke.',
+ 'config-pg-no-plpgsql' => 'Do moß de Daatebangkshprooch <code lang="en">PL/pgSQL</code> en dä Daatebangk $1 enreeschte.',
+ 'config-pg-no-create-privs' => 'Dä Daatebangk-Aanwänder för et Enreeschte hät nit jenooch Rääschde, öm ene andere Daatebangk-Aanwänder en dä Daatebangk aanzelääje.',
+ 'config-install-user' => 'Ben unse Daatebangk-Aanwänder en de Daatebangk am aanlääje.',
+ 'config-install-user-alreadyexists' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kann nit aanjelaat wääde, et jidd_en alld.',
+ 'config-install-user-create-failed' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kunnt nit aanjelaat wääde, wäje: <code lang="en">$2</code>',
+ 'config-install-user-grant-failed' => 'Däm Daatebangk-Aanwänder sing Beräschtijunge ze säze däät nit fluppe wääje: $2',
+ 'config-install-tables' => 'Ben de Daatebangk-Tabälle aam aanlääje.',
+ 'config-install-tables-exist' => "'''Opjepaß''': Et schingk, dem MediaWiki sing Tabälle sin alt doh.
+Doh dom_mer nix aanlääje.",
+ 'config-install-tables-failed' => "'''Fähler''': De Tabälle kunnte nit aanjelaat wääde, wääje: $1",
+ 'config-install-interwiki' => 'Ben de Engerwiki-Tabäll met de shtandattmääßejje Daate aam fölle.',
+ 'config-install-interwiki-list' => 'Mer kunnte de Dattei <code lang="en">interwiki.list</code> nit fenge.',
+ 'config-install-interwiki-exists' => "'''Opjepaß''': En der Engewiki-Tabäll schingk alt jät dren ze shtonn.
+Doh dom_mer nix dobei.",
+ 'config-install-stats' => 'De Shtatestek-Zahle wääde op Aanfang jeshtallt.',
+ 'config-install-keys' => 'Jeheime Schlößel wääde opjebout.',
+ 'config-insecure-keys' => "'''Opjepaß:''' {{PLURAL:$2|Ene jeheime Schlößel|Jeheim Schlößele|Keine jeheime Schlößel}} ($1) {{PLURAL:$2|es|sin|es}} automattesch aanjelaat woode. {{PLURAL:$2|Dä es|Di sin|Hä es}} ävver nit onbedengk janz sescher. Övverlääsch Der, {{PLURAL:$2|dä|di|en}} norr_ens vun Hand ze ändere.",
+ 'config-install-sysop' => 'Dä Zohjang för der Wiki-Köbes weed aanjelaat.',
+ 'config-install-subscribe-fail' => 'Mer künne de <i lang="en">e-mail</i>-Leß <code lang="en">mediawiki-announce</code> nit abonneere.',
+ 'config-install-mainpage' => 'Ben de Houpsigg med enem shtandatmääßeje Enhald aam aanlääje',
+ 'config-install-extension-tables' => 'Ben Datebangk-Tabälle för de Zohsazprojramme aam ennreschte',
+ 'config-install-mainpage-failed' => 'Kunnt de Houpsigg nit afshpeishere: $1',
+ 'config-install-done' => "'''Jlöckwonsch!'''
+MediaWiki es jetz enstalleet.
+
+Et Projramm zom Enreeschte hät en Dattei <code lang=\"en\">LocalSettings.php</code> aanjelaat.
+Doh sin de Enstellunge vum Wiki dren.
+
+Do weeß se eronge laade möße un dann en dem Wiki sing Aanfangsverzeishnes donn möße, et sellve Verzeisneß, woh di Dattei <code lang=\"en\">index.php</code> dren litt. Dat Erongerlaade sullt automattesch aanjefange han.
+
+Wann domet jet nit jeflupp hät, udder De di Dattei norr_ens han wells, donn op dä Lengk heh dronger klecke:
+
+\$3
+
+'''Opjepaß''': Wann De dat jez nit deihß es Alles verschött wat De jemaat häs, weil di Dattei fott es en däm Momang, woh heh dat Projamm aam Engk es.
+
+Wann De mem Ronger- un widder Huhlaade fäädesh bes, kanns De '''[\$2 en Ding Wiki jonn]'''.",
+ 'config-download-localsettings' => 'Donn de Dattei <code lang="en">LocalSettings.php</code> eronger laade',
+ 'config-help' => 'Hölp',
+);
+
+/** Kurdish (Latin) (Kurdî (Latin))
+ * @author George Animal
+ */
+$messages['ku-latn'] = array(
+ 'config-page-language' => 'Ziman',
+ 'config-page-name' => 'Nav',
+);
+
+/** Luxembourgish (Lëtzebuergesch)
+ * @author Robby
+ */
+$messages['lb'] = array(
+ 'config-desc' => 'Den Installatiounsprogramm vu MediaWiki',
+ 'config-title' => 'MediaWiki $1 Installatioun',
+ 'config-information' => 'Informatioun',
+ 'config-localsettings-upgrade' => "'''Opgepasst''': E Fichier <code>LocalSettings.php</code> gouf fonnt.
+Är Software kann aktualiséiert ginn, setzt w.e.g. de Wäert vum <code>\$wgUpgradeKey</code> an d'Këscht.
+Dir fannt en am LocalSettings.php.",
+ 'config-localsettings-key' => 'Aktualisatiounsschlëssel:',
+ 'config-localsettings-badkey' => 'De Schlëssel deen Dir aginn hutt ass net korrekt',
+ 'config-session-error' => 'Feeler beim Starte vun der Sessioun: $1',
+ 'config-your-language' => 'Är Sprooch',
+ 'config-your-language-help' => 'Sicht déi Sprooch eraus déi Dir während der Installatioun benotze wëllt',
+ 'config-wiki-language' => 'Sprooch vun der Wiki:',
+ 'config-wiki-language-help' => "Sicht d'Sprooch eraus an där d'Wiki haaptsächlech geschriwwe gëtt.",
+ 'config-back' => '← Zréck',
+ 'config-continue' => 'Weider →',
+ 'config-page-language' => 'Sprooch',
+ 'config-page-welcome' => 'Wëllkomm bäi MediaWiki!',
+ 'config-page-dbconnect' => 'Mat der Datebank verbannen',
+ 'config-page-upgrade' => 'Eng Installatioun déi besteet aktualiséieren',
+ 'config-page-dbsettings' => 'Astellunge vun der Datebank',
+ 'config-page-name' => 'Numm',
+ 'config-page-options' => 'Optiounen',
+ 'config-page-install' => 'Installéieren',
+ 'config-page-complete' => 'Fäerdeg!',
+ 'config-page-restart' => 'Installatioun neistarten',
+ 'config-page-readme' => 'Liest dëst',
+ 'config-page-releasenotes' => 'Informatiounen zur Versioun',
+ 'config-page-copying' => 'Kopéieren',
+ 'config-page-upgradedoc' => 'Aktualiséieren',
+ 'config-page-existingwiki' => 'Wiki déi et gëtt',
+ 'config-help-restart' => 'Wëllt dir all gespäichert Donnéeë läschen déi dir bis elo aginn hutt an den Installatiounsprozess nei starten?',
+ 'config-restart' => 'Jo, neistarten',
+ 'config-welcome' => "=== Iwwerpréifung vum Installatiounsenvironnement ===
+Et gi grondsätzlech Iwwerpréifunge gemaach fir ze kucken ob den Environnment gëeegent ass fir MediaWiki z'installéieren.
+Dir sollt d'Resultater vun dëser Iwwerpréifung ugi wann Dir während der Installatioun Hëllef braucht.",
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki Haaptsäit]
+* [http://www.mediawiki.org/wiki/Help:Contents Benotzerguide]
+* [http://www.mediawiki.org/wiki/Manual:Contents Guide fir Administrateuren]
+* [http://www.mediawiki.org/wiki/Manual:FAQ FAQ]
+----
+* <doclink href=Readme>Liest dëst</doclink>
+* <doclink href=ReleaseNotes>Informatioune vun der aktueller Versioun</doclink>
+* <doclink href=Copying>Lizenzbedingungen</doclink>
+* <doclink href=UpgradeDoc>Aktualisatioun</doclink>',
+ 'config-env-good' => 'Den Environement gouf nogekuckt.
+Dir kënnt MediaWiki installéieren.',
+ 'config-env-bad' => 'Den Environnement gouf iwwerpréift.
+Dir kënnt MediWiki net installéieren.',
+ 'config-env-php' => 'PHP $1 ass installéiert.',
+ 'config-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!',
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] ass installéiert',
+ 'config-apc' => '[http://www.php.net/apc APC] ass installéiert',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] ass installéiert',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] ass installéiert',
+ 'config-diff3-bad' => 'GNU diff3 gouf net fonnt.',
+ 'config-no-uri' => "'''Feeler:''' Déi aktuell URI konnt net festgestallt ginn.
+Installatioun ofgebrach.",
+ 'config-db-type' => 'Datebanktyp:',
+ 'config-db-host-oracle' => 'Datebank-TNS:',
+ 'config-db-wiki-settings' => 'Dës Wiki identifizéieren',
+ 'config-db-name' => 'Numm vun der Datebank:',
+ 'config-db-name-oracle' => 'Datebankschema:',
+ 'config-db-install-account' => "Benotzerkont fir d'Installatioun",
+ 'config-db-username' => 'Datebank-Benotzernumm:',
+ 'config-db-password' => 'Passwuert vun der Datebank:',
+ 'config-db-install-help' => 'Gitt de Benotzernumm an Passwuert an dat wàhrend der Installatioun benotzt gëtt fir sech mat der Datebank ze verbannen.',
+ 'config-db-account-lock' => 'De selwechte Benotzernumm a Passwuert fir déi normal Operatioune benotzen',
+ 'config-db-wiki-account' => 'Benotzerkont fir normal Operatiounen',
+ 'config-db-wiki-help' => "Gitt de Benotzernumm an d'Passwuert an dat benotzt wäert gi fir sech bei den normale Wiki-Operatiounen mat der Datebank ze connectéieren.
+Wann et de Kont net gëtt, a wann den Installatiouns-Kont genuch Rechter huet, gëtt dëse Benotzerkont opgemaach mat dem Minimum vu Rechter déi gebraucht gi fir dës Wiki bedreiwen ze kënnen.",
+ 'config-db-charset' => 'Zeechesaz (character set) vun der Datebank',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binair',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-mysql-old' => 'MySQL $1 oder eng méi nei Versioun gëtt gebraucht, Dir hutt $2.',
+ 'config-db-port' => 'Port vun der Datebank:',
+ 'config-db-schema' => 'Schema fir MediaWiki',
+ 'config-db-schema-help' => "D'Schemaen hei driwwer si gewéinlech korrekt.
+Ännert se nëmme wann Dir wësst datt et néideg ass.",
+ 'config-sqlite-dir' => 'Repertoire vun den SQLite-Donnéeën',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-header-mysql' => 'MySQL-Astellungen',
+ 'config-header-postgres' => 'PostgreSQL-Astellungen',
+ 'config-header-sqlite' => 'SQLite-Astellungen',
+ 'config-header-oracle' => 'Oracle-Astellungen',
+ 'config-invalid-db-type' => 'Net valabelen Datebank-Typ',
+ 'config-missing-db-name' => 'Dir musst en Numm fir de Wäert "Numm vun der Datebank" uginn',
+ 'config-missing-db-server-oracle' => 'Dir musst e Wäert fir "Datebank-TNS" uginn',
+ 'config-db-sys-user-exists-oracle' => 'De Benotzerkont "$1" gëtt et schonn. SYSDBA kann nëmme benotzt gi fir en neie Benotzerkont opzemaachen.',
+ 'config-postgres-old' => 'PostgreSQL $1 oder eng méi nei Versioun gëtt gebraucht, Dir hutt $2.',
+ 'config-sqlite-name-help' => 'Sicht en Numm deen Är wiki identifizéiert.
+Benotzt keng Espacen a Bindestrécher.
+E gëtt fir den Numm vum SQLite Date-Fichier benotzt.',
+ 'config-sqlite-readonly' => 'An de Fichier <code>$1</code> Kann net geschriwwe ginn.',
+ 'config-sqlite-cant-create-db' => 'Den Datebank-Fichier <code>$1</code> konnt net ugeluecht ginn.',
+ 'config-db-web-account' => 'Datebankkont fir den Accès iwwer de Web',
+ 'config-db-web-account-same' => 'Dee selwechte Kont wéi bei der Installatioun benotzen',
+ 'config-db-web-create' => 'De Kont uleeë wann et e net scho gëtt',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-binary' => 'binär',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Numm vun der Wiki:',
+ 'config-site-name-help' => 'Dësen daucht an der Titelleescht vum Browser an op verschiddenen anere Plazen op.',
+ 'config-site-name-blank' => 'Gitt den Numm vum Site un.',
+ 'config-project-namespace' => 'Projet Nummraum:',
+ 'config-ns-generic' => 'Projet',
+ 'config-ns-site-name' => 'Deeselwechte wéi den Numm vun der Wiki: $1',
+ 'config-ns-other' => 'Anerer (spezifizéieren)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-admin-box' => 'Administrateurs-Kont',
+ 'config-admin-name' => 'Ären Numm:',
+ 'config-admin-password' => 'Passwuert:',
+ 'config-admin-password-confirm' => 'Passwuert confirméieren:',
+ 'config-admin-name-blank' => 'Gitt e Benotzernumm fir den Administrateur an.',
+ 'config-admin-name-invalid' => 'De spezifizéierte Benotzernumm "<nowiki>$1</nowiki>" ass net valabel.
+Spezifizéiert en anere Benotzernumm.',
+ 'config-admin-password-blank' => 'Gitt e Passwuert fir den Adminstateur-Kont an.',
+ 'config-admin-password-same' => "D'Passwuert däerf net dat selwecht si wéi de Benotzernumm.",
+ 'config-admin-password-mismatch' => 'Déi zwee Passwierder Déi dir aginn stëmmen net iwwerteneen.',
+ 'config-admin-email' => 'E-Mailadress:',
+ 'config-admin-error-user' => 'Interne Feeler beim uleeë vun engem Administrateur mam Numm "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Interne Feeler beim Setze vum Passwuert fir den Admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Dir hutt eng E-Mailadress aginn déi net valabel ass',
+ 'config-subscribe' => "Sech op d'[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ukënnegunge vun neie Versiounen] abonnéieren.",
+ 'config-almost-done' => "Dir sidd bal fäerdeg!
+Dir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki elo direkt installéieren.",
+ 'config-optional-continue' => 'Stellt mir méi Froen.',
+ 'config-optional-skip' => "Ech hunn es genuch, installéier just d'Wiki.",
+ 'config-profile' => 'Profil vun de Benotzerrechter:',
+ 'config-profile-wiki' => 'Traditionell Wiki',
+ 'config-profile-no-anon' => 'Uleeë vun engem Benotzerkont verlaangt',
+ 'config-profile-fishbowl' => 'Nëmmen autoriséiert Editeuren',
+ 'config-profile-private' => 'Privat Wiki',
+ 'config-license' => 'Copyright a Lizenz:',
+ 'config-license-none' => 'Keng Lizenz ënnen op der Säit',
+ 'config-license-pd' => 'Ëffentlechen Domaine',
+ 'config-email-settings' => 'E-Mail-Astellungen',
+ 'config-enable-email' => 'E-Mailen déi no bausse ginn aschalten',
+ 'config-email-user' => 'Benotzer-op-Benotzer E-Mail aschalten',
+ 'config-email-usertalk' => 'Benoriichtege bäi Ännerung vun der Benotzerdiskussiounssäit aschalten',
+ 'config-email-watchlist' => 'Benoriichtigung vun der Iwwerwaachungslëscht aschalten',
+ 'config-email-auth' => 'E-Mail-Authentifizéierung aschalten',
+ 'config-email-sender' => 'E-Mailadress fir Äntwerten:',
+ 'config-upload-settings' => 'Eropgeluede Biller a Fichieren',
+ 'config-upload-enable' => 'Eropluede vu Fichieren aschalten',
+ 'config-upload-deleted' => 'Repertoire fir geläschte Fichieren:',
+ 'config-logo' => 'URL vum Logo:',
+ 'config-cc-again' => 'Nach eng kéier eraussichen...',
+ 'config-advanced-settings' => 'Erweidert Astellungen',
+ 'config-extensions' => 'Erweiderungen',
+ 'config-install-step-done' => 'fäerdeg',
+ 'config-install-step-failed' => 'huet net fonctionnéiert',
+ 'config-install-extensions' => 'Mat den Ereiderungen',
+ 'config-install-database' => 'Datebank gëtt installéiert',
+ 'config-pg-no-plpgsql' => "Fir d'Datebank $1 muss d'Datebanksprooch PL/pgSQL installéiert ginn",
+ 'config-install-user' => 'Datebank Benotzer uleeën',
+ 'config-install-user-alreadyexists' => 'De Benotzer "$1" gëtt et schonn!',
+ 'config-install-user-create-failed' => 'D\'Opmaache vum Benotzer "$1" huet net fonctionnéiert: $2',
+ 'config-install-tables' => 'Tabelle ginn ugeluecht',
+ 'config-install-interwiki' => 'Standard Interwiki-Tabell gëtt ausgefëllt',
+ 'config-install-interwiki-list' => 'De Fichier <code>interwiki.list</code> gouf net fonnt.',
+ 'config-install-stats' => 'Initialisatioun vun de Statistiken',
+ 'config-install-keys' => 'Generéiere vum Geheimschlëssel',
+ 'config-install-sysop' => 'Administrateur Benotzerkont gëtt ugeluecht',
+);
+
+/** Malagasy (Malagasy)
+ * @author Jagwar
+ */
+$messages['mg'] = array(
+ 'config-session-error' => 'Hadisoana teo am-panombohana ny fidirana : $1',
+ 'config-your-language' => 'Ny fiteninao :',
+ 'config-wiki-language' => "Fiteny ho ampiasain'ny wiki :",
+ 'config-back' => '← Miverina',
+ 'config-continue' => 'Manohy →',
+ 'config-page-language' => 'Fiteny',
+ 'config-page-welcome' => "Tonga soa eto amin'i MediaWiki !",
+ 'config-page-dbconnect' => "Hiditra eo amin'i banky angona",
+ 'config-page-name' => 'Anarana',
+ 'config-page-readme' => 'Vakio aho',
+ 'config-page-copying' => 'Hala-tahaka',
+ 'config-page-upgradedoc' => 'Fanavaozina',
+ 'config-page-existingwiki' => 'Wiki efa misy',
+ 'config-help-restart' => "Tianao hofafana avokoa ve ny data voaangona natsofokao ary hamerina ny fizotran'ny fametrahana ?",
+ 'config-restart' => 'Eny, avereno atao',
+ 'config-db-username' => "Anaram-pikamban'ny banky angona :",
+ 'config-db-password' => "Tenimiafin'ny banky angona :",
+ 'config-header-mysql' => "Parametatr'i MySQL",
+ 'config-header-sqlite' => "Parametatr'i SQLite",
+ 'config-header-oracle' => "Parametatr'i Oracle",
+ 'config-mysql-innodb' => 'innoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-ns-generic' => 'Tetikasa',
+ 'config-ns-other' => 'Hafa (lazao)',
+ 'config-admin-name' => 'Ny anaranao :',
+ 'config-admin-password' => 'Tenimiafina :',
+ 'config-admin-email' => 'Adiresy imailaka :',
+ 'config-profile-wiki' => 'Wiki tsotra',
+ 'config-profile-no-anon' => 'Mila mamorona kaonty',
+ 'config-profile-fishbowl' => 'Mpanova mahazo alalana ihany',
+ 'config-profile-private' => 'Wiki tsy sarababem-bahoaka',
+ 'config-license' => 'Zom-pamorona ary lisansa :',
+ 'config-license-none' => 'Tsy misy lisansa any an-tongom-pejy',
+ 'config-email-user' => 'Avela mifandefa imailaka ny mpikambana',
+ 'config-email-user-help' => "Hahafahan'ny mpikambana mifandefa imailaka raha omen'ny mpikambana alalana ao amin'ny safidiny.",
+ 'config-upload-deleted' => "Petra-drakitra ho an'ny rakitra voafafa :",
+ 'config-extensions' => 'Fanitarana',
+ 'config-install-step-done' => 'vita',
+ 'config-install-step-failed' => 'hadisoana',
+ 'config-install-user' => "Famoronana mpapiasan'ny banky angona",
+ 'config-install-tables' => 'Famoronana tabilao',
+ 'config-install-stats' => 'Fanombohana ny statistika',
+ 'config-install-keys' => 'Fanamboarana lakile miafina$',
+ 'config-help' => 'fanoroana',
+);
+
+/** Macedonian (Македонски)
+ * @author Bjankuloski06
+ */
+$messages['mk'] = array(
+ 'config-desc' => 'Инсталатор на МедијаВики',
+ 'config-title' => 'Инсталатор на МедијаВики $1',
+ 'config-information' => 'Информации',
+ 'config-localsettings-upgrade' => 'Востановена е податотека <code>LocalSettings.php</code>.
+За да ја надградите инсталцијава, внесете ја вредноста на <code>$wgUpgradeKey</code> во полето подолу.
+Тоа е го најдете во LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Утврдено е присуството на податотеката „LocalSettings.php“.
+За да ја надградите инсталацијата, пуштете ја „update.php“ наместо горенаведената.',
+ 'config-localsettings-key' => 'Надградбен клуч:',
+ 'config-localsettings-badkey' => 'Клучот што го наведовте е погрешен',
+ 'config-upgrade-key-missing' => 'Востановена е постоечка инсталација на МедијаВики.
+За да ја надградите, вметнете го следниов ред на дното од вашата страница LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'Постоечката страница LocalSettings.php е нецелосна.
+Не е поставена променливата $1.
+Изменете ја страницата LocalSettings.php така што ќе ѝ зададете вредност на променливата, па стиснете на „Продолжи“.',
+ 'config-localsettings-connection-error' => 'Се појави грешка при поврзувањето со базата користејќи ги поставките назначени во LocalSettings.php или AdminSettings.php. Исправете ги овие поставки и обидете се повторно.
+
+$1',
+ 'config-session-error' => 'Грешка при започнување на сесијата: $1',
+ 'config-session-expired' => 'Вашите сесиски податоци истекоа.
+Поставките на сесиите траат $1.
+Нивниот рок можете да го зголемите со задавање на <code>session.gc_maxlifetime</code> во php.ini.
+Почнете ја инсталацијата одново.',
+ 'config-no-session' => 'Вашите сесиски податоци се изгубени!
+Погледајте во php.ini дали <code>session.save_path</code> е поставен во правилна папка.',
+ 'config-your-language' => 'Вашиот јазик:',
+ 'config-your-language-help' => 'Одберете на кој јазик да се одвива инсталацијата.',
+ 'config-wiki-language' => 'Јазик на викито:',
+ 'config-wiki-language-help' => 'Одберете на кој јазик ќе бидат содржините на викито.',
+ 'config-back' => '← Назад',
+ 'config-continue' => 'Продолжи →',
+ 'config-page-language' => 'Јазик',
+ 'config-page-welcome' => 'Добредојдовте на МедијаВики!',
+ 'config-page-dbconnect' => 'Поврзување со базата',
+ 'config-page-upgrade' => 'Надградба на постоечката инсталација',
+ 'config-page-dbsettings' => 'Нагодувања на базата',
+ 'config-page-name' => 'Назив',
+ 'config-page-options' => 'Поставки',
+ 'config-page-install' => 'Инсталирај',
+ 'config-page-complete' => 'Готово!',
+ 'config-page-restart' => 'Пушти ја инсталацијата одново',
+ 'config-page-readme' => 'Прочитај ме',
+ 'config-page-releasenotes' => 'Белешки за изданието',
+ 'config-page-copying' => 'Копирање',
+ 'config-page-upgradedoc' => 'Надградба',
+ 'config-page-existingwiki' => 'Постоечко вики',
+ 'config-help-restart' => 'Дали сакате да ги исчистите сите зачувани податоци што ги внесовте и да ја започнете инсталацијата одново?',
+ 'config-restart' => 'Да, почни одново',
+ 'config-welcome' => '=== Environmental checks ===
+Се вршат основни проверки за да се востанови дали околината е погодна за инсталирање на МедијаВики.
+Ако ви затреба помош при инсталацијата, ќе треба да ги наведете резултатите од овие проверки.',
+ 'config-copyright' => "=== Авторски права и услови ===
+
+$1
+
+Ова е слободна програмска опрема (free software); можете да го редистрибуирате и/или менувате согласно условите на ГНУ-овата општа јавна лиценца (GNU General Public License) на Фондацијата за слободна програмска опрема (Free Software Foundation); верзија 2 или било која понова верзија на лиценцата (по ваш избор).
+
+Овој програм се нуди со надеж дека ќе биде корисен, но '''без никаква гаранција'''; дури ни подразбраната гаранција за '''продажна способност''' или '''погодност за определена цел'''.
+Повеќе информации ќе најдете во текстот на ГНУ-овата општа јавна лиценца.
+
+Би требало да имате добиено <doclink href=Copying>примерок од ГНУ-овата општа јавна лиценца</doclink> заедно со програмов; ако немате добиено, тогаш пишете ни на Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. или [http://www.gnu.org/copyleft/gpl.html прочитајте ја тука].",
+ 'config-sidebar' => '* [http://www.mediawiki.org Домашна страница на МедијаВики]
+* [http://www.mediawiki.org/wiki/Help:Contents Водич за корисници]
+* [http://www.mediawiki.org/wiki/Manual:Contents Водич за администратори]
+* [http://www.mediawiki.org/wiki/Manual:FAQ ЧПП]
+----
+* <doclink href=Readme>Прочитај ме</doclink>
+* <doclink href=ReleaseNotes>Белешки за изданието</doclink>
+* <doclink href=Copying>Копирање</doclink>
+* <doclink href=UpgradeDoc>Надградување</doclink>',
+ 'config-env-good' => 'Околината е проверена.
+Можете да го инсталирате МедијаВики.',
+ 'config-env-bad' => 'Околината е проверена.
+Не можете да го инсталирате МедијаВики.',
+ 'config-env-php' => 'PHP $1 е инсталиран.',
+ 'config-env-php-toolow' => 'PHP $1 е инсталиран.
+Меѓутоа, МедијаВики бара PHP $2 или поново.',
+ 'config-unicode-using-utf8' => 'Со utf8_normalize.so за уникодна нормализација од Брајон Вибер (Brion Vibber).',
+ 'config-unicode-using-intl' => 'Со додатокот [http://pecl.php.net/intl intl PECL] за уникодна нормализација.',
+ 'config-unicode-pure-php-warning' => "'''Предупредување''': Додатокот [http://pecl.php.net/intl intl PECL] не е достапен за врши уникодна нормализација, враќајќи се на бавна примена на чист PHP.
+
+Ако имате високопрометно мрежно место, тогаш ќе треба да прочитате повеќе за [http://www.mediawiki.org/wiki/Unicode_normalization_considerations уникодната нормализација].",
+ 'config-unicode-update-warning' => "'''Предупредување''': Инсталираната верзија на обвивката за уникодна нормализација користи постара верзија на библиотеката на [http://site.icu-project.org/ проектот ICU].
+За да користите Уникод, ќе треба да направите [http://www.mediawiki.org/wiki/Unicode_normalization_considerations надградба].",
+ 'config-no-db' => 'Не можев да пронајдам соодветен двигател за базата на податоци!',
+ 'config-no-db-help' => 'Ќе треба да инсталирате двигател за базата на податоци за PHP.
+Поддржани се следниве типови на бази: $1.
+
+Ако сте на заедничко (споделено) вдомување, побарајте му на вдомителот да инсталира соодветен двигател за базата.
+Ако вие самите го составивте ова PHP, сменете ги поставките така што ќе овозможите клиент на базата - на пр. со кодот <code>./configure --with-mysql</code>.
+Ако инсталиравте PHP од пакет на Debian или Ubuntu, тогаш ќе треба да го инсталирате и модулот php5-mysql.',
+ 'config-no-fts3' => "'''Предупредување''': SQLite iе составен без модулот [http://sqlite.org/fts3.html FTS3] - за оваа база нема да има можност за пребарување.",
+ 'config-register-globals' => "'''Предупредување: Можноста <code>[http://php.net/register_globals register_globals]</code> за PHP е овозможена.'''
+'''Оневозможете ја ако е можно.'''
+МедијаВики ќе работи, но опслужувачот ви е изложен на безбедносни ризици.",
+ 'config-magic-quotes-runtime' => "'''Кобно: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] е активно!'''
+Оваа можност непредвидливо го расипува вносот на податоци.
+Оваа можност мора да е исклучена. Во спротивно нема да можете да го инсталирате и користите МедијаВики.",
+ 'config-magic-quotes-sybase' => "'''Кобно: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] е активно!'''
+Оваа можност непредвидливо го расипува вносот на податоци.
+Оваа можност мора да е исклучена. Во спротивно нема да можете да го инсталирате и користите МедијаВики.",
+ 'config-mbstring' => "'''Кобно: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] е активно!'''
+Оваа можност предизвикува грешки и може непредвидиво да го расипува вносот на податоци.
+Оваа можност мора да е исклучена. Во спротивно нема да можете да го инсталирате и користите МедијаВики.",
+ 'config-ze1' => "'''Кобно: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] е активно!'''
+Оваа можност предизвикува ужасни грешки во МедијаВики.
+Оваа можност мора да е исклучена. Во спротивно нема да можете да го инсталирате и користите МедијаВики.",
+ 'config-safe-mode' => "'''Предупредување:''' [http://www.php.net/features.safe-mode безбедниот режим] на PHP е активен.
+Ова може да предизвика проблеми, особено ако користите подигања и поддршка за <code>math</code>.",
+ 'config-xml-bad' => 'XML-модулот за PHP недостасува.
+МедијаВики има потреба од функции во овој модул и нема да работи со овие поставки.
+Ако работите со Mandrake, инсталирајте го php-xml пакетот.',
+ 'config-pcre' => 'Недостасува модулот за поддршка на PCRE.
+МедијаВики не може да работи без функции за регуларни изрази соодветни на Perl.',
+ 'config-pcre-no-utf8' => "'''Фатално''': PCRE-модулот на PHP е составен без поддршка за PCRE_UTF8.
+МедијаВики бара поддршка за UTF-8 за да може да работи правилно.",
+ 'config-memory-raised' => '<code>memory_limit</code> за PHP изнесува $1, зголемен на $2.',
+ 'config-memory-bad' => "'''Предупредување:''' <code>memory_limit</code> за PHP изнесува $1.
+Ова е веројатно премалку.
+Инсталацијата може да не успее!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] е инсталиран',
+ 'config-apc' => '[http://www.php.net/apc APC] е инсталиран',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] е инсталиран',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] е инсталиран',
+ 'config-no-cache' => "'''Предупредување:''' Не можев да го најдам [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
+Кеширањето на објекти не е овозможено.",
+ 'config-diff3-bad' => 'GNU diff3 не е пронајден.',
+ 'config-imagemagick' => 'Пронајден е ImageMagick: <code>$1</code>.
+Ако овозможите подигање, тогаш ќе биде овозможена минијатуризација на сликите.',
+ 'config-gd' => 'Утврдив дека има вградена GD графичка библиотека.
+Ако овозможите подигање, тогаш ќе биде овозможена минијатураизација на сликите.',
+ 'config-no-scaling' => 'Не можев да пронајдам GD-библиотека или ImageMagick.
+Минијатуризацијата на сликите ќе биде оневозможена.',
+ 'config-no-uri' => "'''Грешка:''' Не можев да го утврдам тековниот URI.
+Инсталацијата е откажана.",
+ 'config-uploads-not-safe' => "'''Предупредување:''' Вашата матична папка за подигање <code>$1</code> е подложна на извршување (пуштање) на произволни скрипти.
+Иако МедијаВики врши безбедносни проверки на сите подигнати податотеки, ве советуваме [http://www.mediawiki.org/wiki/Manual:Security#Upload_security да ја затворите оваа безбедносна дупка] пред да овозможите подигање.",
+ 'config-brokenlibxml' => 'Вашиот систем има комбинација од PHP и libxml2 верзии и затоа има грешки и може да предизвика скриено расипување на податоците кај МедијаВики и други мрежни програми.
+Надградете го на PHP 5.2.9 и libxml2 2.7.3 или нивни понови верзии! ПРЕКИНУВАМ ([http://bugs.php.net/bug.php?id=45996 грешката е заведена во PHP]).',
+ 'config-using531' => 'МедијаВики не може да се користи со PHP $1 поради грешка кај упатните параметри за <code>__call()</code>.
+За да го решите проблемот, надградете го на PHP 5.3.2 или понова верзија, или пак користете го постариот PHP 5.3.0.',
+ 'config-db-type' => 'Тип на база:',
+ 'config-db-host' => 'Домаќин на базата:',
+ 'config-db-host-help' => 'Ако вашата база е на друг опслужувач, тогаш тука внесете го името на домаќинот илиу IP-адресата.
+
+Ако користите заедничко (споделено) вдомување, тогаш вашиот вдомител треба да го доде точното име на домаќинот и неговата документација.
+
+Ако инсталирате на опслужувач на Windows и користите MySQL, можноста „localhost“ може да не функционира за опслужувачкото име. Во тој случај, обидете се со внесување на „127.0.0.1“ како локална IP-адреса',
+ 'config-db-host-oracle' => 'TNS на базата:',
+ 'config-db-host-oracle-help' => 'Внесете важечко [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm локално име за поврзување]. На оваа инсталација мора да ѝ биде видлива податотеката tnsnames.ora.<br />Ако користите клиентски библиотеки 10g или понови, тогаш можете да го користите и методот на иметнување на [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Идентификувај го викиво',
+ 'config-db-name' => 'Име на базата:',
+ 'config-db-name-help' => 'Одберете име што ќе го претставува вашето вики.
+Името не смее да содржи празни места.
+
+Ако користите заедничко (споделено) вдомување, тогаш вашиот вдомител ќе ви даде конкретно име на база за користење, или пак ќе ви даде да создавате бази преку контролната табла.',
+ 'config-db-name-oracle' => 'Шема на базата:',
+ 'config-db-account-oracle-warn' => 'Постојат три поддржани сценарија за инсталирање на Oracle како базен услужник:
+
+Ако сакате да создадете сметка на базата како дел од постапката за инсталација, наведете сметка со SYSDBA-улога како сметка за базата што ќе се инсталира и наведете ги саканите податоци за сметката за мрежен пристап. Во друг случај, можете да создадете сметка за мрежен пристап рачно и да ја наведете само таа сметка (ако има дозволи за создавање на шематски објекти) или пак да наведете две различни сметки, една со привилегии за создавање, а друга (ограничена) за мрежен пристап.
+
+Скриптата за создавање сметка со задолжителни привилегии ќе ја најдете во папката „maintenance/oracle/“ од оваа инсталација. Имајте на ум дека ако користите ограничена сметка ќе ги оневозможите сите функции за одржување со основната сметка.',
+ 'config-db-install-account' => 'Корисничка смета за инсталација',
+ 'config-db-username' => 'Корисничко име за базата:',
+ 'config-db-password' => 'Лозинка за базата:',
+ 'config-db-password-empty' => 'Внесете лозинка за новиот корисник на базата: $1.
+Иако може да се создаваат корисници без лозинка, тоа не е безбедно.',
+ 'config-db-install-username' => 'Внесете корисничко име што ќе се користи за поврзување со базата во текот на инсталацијата. Ова не е корисничкото име од сметката на МедијаВики, туку посебно корисничко име за вашата база на податоци.',
+ 'config-db-install-password' => 'Внесете клозинка што ќе се користи за поврзување со базата во текот на инсталацијата. Ова не е лозинката од сметката на МедијаВики, туку посебна лозинка за вашата база на податоци.',
+ 'config-db-install-help' => 'Внесете го корисничкото име и лозинката што ќе се користи за поврзување со базата на податоци во текот на инсталацијата.',
+ 'config-db-account-lock' => 'Користи го истото корисничко име и лозинка за редовна работа',
+ 'config-db-wiki-account' => 'Корисничко име за редовна работа',
+ 'config-db-wiki-help' => 'Внесете корисничко име и лозинка што ќе се користат за поврзување со базата на податоци во текот на редовната работа со викито.
+Ако сметката не постои, а инсталационата сметка има доволно привилегии, тогаш оваа корисничка сметка ќе биде создадена со минималните привилегии потребни за работа со викито.',
+ 'config-db-prefix' => 'Префикс на табелата на базата:',
+ 'config-db-prefix-help' => 'Ако треба да делите една база на податоци со повеќе викија, или со МедијаВики и друг мрежен програм, тогаш можете да додадете префикс на сите називи на табелите за да спречите проблематични ситуации.
+Не користете празни простори.
+
+Ова поле обично се остава празно.',
+ 'config-db-charset' => 'Збир знаци за базата',
+ 'config-charset-mysql5-binary' => 'Бинарен за MySQL 4.1/5.0',
+ 'config-charset-mysql5' => 'UTF-8 за MySQL 4.1/5.0',
+ 'config-charset-mysql4' => 'Назадно-соодветен UTF-8 за MySQL 4.0',
+ 'config-charset-help' => "'''ПРЕДУПРЕДУВАЊЕ:''' Ако користите '''назадно-соодветен UTF-8''' во MySQL 4.1+, а потоа направите резервен примерок на базата со <code>mysqldump</code>, ова може да ги опустоши сите не-ASCII знаци, и со тоа неповратно да ја расипе целата зачувана резерва!
+
+Во '''бинарен режим''', во базата МедијаВики го складира UTF-8 текстот во бинарни полиња.
+Ова е поефикансно отколку UTF-8 режимот на MySQL бидејќи ви овозможува да го користите целиот спектар на уникодни знаци.
+Во '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може соодветно да ги претстави и претвори,
+но нема да ви дозволи да складирате знаци над [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
+ 'config-mysql-old' => 'Се бара MySQL $1 или поново, а вие имате $2.',
+ 'config-db-port' => 'Порта на базата:',
+ 'config-db-schema' => 'Шема за МедијаВики',
+ 'config-db-schema-help' => 'Оваа шема обично по правило ќе работи нормално.
+Сменете ја само ако знаете дека треба да се смени.',
+ 'config-sqlite-dir' => 'Папка на SQLite-податоци:',
+ 'config-sqlite-dir-help' => "SQLite ги складира сите податоци во една податотека.
+
+Папката што ќе ја наведете мора да е запислива од мрежниот опслужувач во текот на инсталацијата.
+
+Таа '''не''' смее да биде достапна преку интернет, и затоа не ја ставаме кајшто ви се наоѓаат PHP-податотеките.
+
+Инсталаторот воедно ќе создаде податотека <code>.htaccess</code>, но ако таа не функционира како што треба, тогаш некој ќе може да ви влезе во вашата необработена (сирова) база на податоци.
+Тука спаѓаат необработени кориснички податоци (е-поштенски адреси, хеширани лозинки) како и избришани ревизии и други податоци за викито до кои се има ограничен пристап.
+
+Се препорачува целата база да ја сместите некаде, како на пр. <code>/var/lib/mediawiki/вашетовики</code>.",
+ 'config-oracle-def-ts' => 'Стандарден таблеарен простор:',
+ 'config-oracle-temp-ts' => 'Привремен табеларен простор:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'МедијаВики ги поддржува следниве системи на бази на податоци:
+
+$1
+
+Ако системот што сакате да го користите не е наведен подолу, тогаш проследете ја горенаведената врска со инструкции за да овозможите поддршка за тој систем.',
+ 'config-support-mysql' => '* $1 е главната цел на МедијаВики и најдобро се поддржува ([http://www.php.net/manual/en/mysql.installation.php како се составува PHP со поддршка за MySQL])',
+ 'config-support-postgres' => '* $1 е популарен систем на бази на податоци со отворен код кој претставува алтернатива на MySQL ([http://www.php.net/manual/en/pgsql.installation.php како да составите PHP со поддршка за PostgreSQL]). Може сè уште да има некои грешки. па затоа не се препорачува за употреба во производна средина.',
+ 'config-support-sqlite' => '* $1 е лесен систем за бази на податоци кој е многу добро поддржан. ([http://www.php.net/manual/en/pdo.installation.php Како да составите PHP со поддршка за SQLite], користи PDO)',
+ 'config-support-oracle' => '* $1 е база на податоци на комерцијално претпријатие. ([http://www.php.net/manual/en/oci8.installation.php Како да составите PHP со поддршка за OCI8])',
+ 'config-header-mysql' => 'Нагодувања на MySQL',
+ 'config-header-postgres' => 'Нагодувања на PostgreSQL',
+ 'config-header-sqlite' => 'Нагодувања на SQLite',
+ 'config-header-oracle' => 'Нагодувања на Oracle',
+ 'config-invalid-db-type' => 'Неважечки тип на база',
+ 'config-missing-db-name' => 'Мора да внесете значење за параметарот „Име на базата“',
+ 'config-missing-db-host' => 'Мора да внесете вредност за „Домаќин на базата на податоци“',
+ 'config-missing-db-server-oracle' => 'Мора да внесете вредност за „TNS на базата“',
+ 'config-invalid-db-server-oracle' => 'Неважечки TNS „$1“ за базата.
+Користете само знаци по ASCII - букви (a-z, A-Z), бројки (0-9), долни црти (_) и точки (.).',
+ 'config-invalid-db-name' => 'Неважечко име на базата „$1“.
+Користете само ASCII-букви (a-z, A-Z), бројки (0-9), долни црти (_) и цртички (-).',
+ 'config-invalid-db-prefix' => 'Неважечки префикс за базата „$1“.
+Користете само ASCII-букви (a-z, A-Z), бројки (0-9), долни црти (_) и цртички (-).',
+ 'config-connection-error' => '$1.
+
+Проверете го долунаведениот домаќин, корисничко име и лозинка и обидете се повторно.',
+ 'config-invalid-schema' => 'Неважечка шема за МедијаВики „$1“.
+Користете само букви, бројки и долни црти.',
+ 'config-db-sys-create-oracle' => 'Инсталаторот поддржува само употреба на SYSDBA-сметка за создавање на нова сметка.',
+ 'config-db-sys-user-exists-oracle' => 'Корисничката сметка „$1“ веќе постои. SYSDBA служи само за создавање на нова сметка!',
+ 'config-postgres-old' => 'Се бара PostgreSQL $1 или поново, а вие имате $2.',
+ 'config-sqlite-name-help' => 'Одберете име кое ќе го претставува вашето вики.
+Не користете празни простори и црти.
+Ова ќе се користи за податотечното име на SQLite-податоците.',
+ 'config-sqlite-parent-unwritable-group' => 'Не можам да ја создадам папката <code><nowiki>$1</nowiki></code> бидејќи мрежниот опслужувач не може да запише во матичната папка <code><nowiki>$2</nowiki></code>.
+
+Инсталаторот го утврди корисникот под кој работи вашиот мрежен опслужувач.
+За да продолжите, наместете да може да запишува во папката <code><nowiki>$3</nowiki></code>.
+На Unix/Linux систем направете го следново:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Не можам да ја создадам папката <code><nowiki>$1</nowiki></code> бидејќи мрежниот опслужувач не може да запише во матичната папка <code><nowiki>$2</nowiki></code>.
+
+Инсталаторот не можеше го утврди корисникот под кој работи вашиот мрежен опслужувач.
+За да продолжите, наместете тој (и други!) да може глобално да запишува во папката <code><nowiki>$3</nowiki></code>
+На Unix/Linux систем направете го следново:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Грешка при создавањето на податочната папка „$1“.
+Проверете каде се наоѓа и обидете се повторно.',
+ 'config-sqlite-dir-unwritable' => 'Не можам да запишам во папката „$1“.
+Во дозволите за неа, овозможете му на мрежниот опслужувач да запишува во неа и обидете се повторно.',
+ 'config-sqlite-connection-error' => '$1.
+
+Проверете ја податочната папка и името на базата, и обидете се повторно.',
+ 'config-sqlite-readonly' => 'Податотеката <code>$1</code> е незапислива.',
+ 'config-sqlite-cant-create-db' => 'Не можев да ја создадам податотеката <code>$1</code> за базата.',
+ 'config-sqlite-fts3-downgrade' => 'PHP нема поддршка за FTS3 — ја поништувам надградбата за табелите',
+ 'config-can-upgrade' => "Во оваа база има табели на МедијаВики.
+За да ги надградите на МедијаВики $1, кликнете на '''Продолжи'''.",
+ 'config-upgrade-done' => "Надградбата заврши.
+
+Сега можете да [$1 почнете да го користите вашето вики].
+
+Ако сакате да ја пресоздадете вашата податотека <code>LocalSettings.php</code>, тогаш кликнете на копчето подолу.
+Ова '''не се препорачува''' освен во случај на проблеми со викито.",
+ 'config-upgrade-done-no-regenerate' => 'Надградбата заврши.
+
+Сега можете да [$1 почнете да го користите викито].',
+ 'config-regenerate' => 'Пресоздај LocalSettings.php →',
+ 'config-show-table-status' => 'Барањето SHOW TABLE STATUS не успеа!',
+ 'config-unknown-collation' => "'''Предупредување:''' Базата корисни непрепознаена упатна споредба.",
+ 'config-db-web-account' => 'Сметка на базата за мрежен пристап',
+ 'config-db-web-help' => 'Одберете корисничко име и лозинка што ќе ги користи мрежниот опслужувач за поврзување со опслужувачот на базта на податоци во текот на редовната работа со викито.',
+ 'config-db-web-account-same' => 'Користи ја истата сметка од инсталацијата',
+ 'config-db-web-create' => 'Создај ја сметката ако веќе не постои',
+ 'config-db-web-no-create-privs' => 'Сметката што ја назначивте за инсталација нема доволно привилегии за да може да создаде сметка.
+Тука мора да назначите постоечка сметка.',
+ 'config-mysql-engine' => 'Складишен погон:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' речиси секогаш е најдобар избор, бидејќи има добра поддршка за едновременост.
+
+'''MyISAM''' може да е побрз кај инсталациите наменети за само еден корисник или незаписни инсталации (само читање).
+Базите на податоци од MyISAM почесто се расипуваат од базите на InnoDB.",
+ 'config-mysql-charset' => 'Збир знаци за базата:',
+ 'config-mysql-binary' => 'Бинарен',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "Во '''бинарен режим''', во базата на податоци МедијаВики складира UTF-8 текст во бинарни полиња.
+Ова е поефикасно отколку TF-8 режимот на MySQL, и ви овозможува да ја користите целата палета на уникодни знаци.
+
+Во '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може соодветно да ги претстави и претвори, но нема да ви дозволи да складиратезнаци над [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
+ 'config-site-name' => 'Име на викито:',
+ 'config-site-name-help' => 'Ова ќе се појавува во заглавната лента на прелистувачот и на разни други места.',
+ 'config-site-name-blank' => 'Внесете име на мрежното место.',
+ 'config-project-namespace' => 'Проектен именски простор:',
+ 'config-ns-generic' => 'Проект',
+ 'config-ns-site-name' => 'Исто име како викито: $1',
+ 'config-ns-other' => 'Друго (наведете)',
+ 'config-ns-other-default' => 'МоеВики',
+ 'config-project-namespace-help' => "По примерот на Википедија, многу викија ги чуваат страниците со правила на посебно место од самите содржини, т.е. во „'''проектен именски простор'''“.
+Сите наслови на страниците во овој именски простор почнуваат со извесен префикс, којшто можете да го укажете тука.
+По традиција префиксот произлегува од името на викито, но не смее да содржи интерпункциски знаци како „#“ или „:“.",
+ 'config-ns-invalid' => 'Назначениот именски простор „<nowiki>$1</nowiki>“ е неважечки.
+Назначете друг проектен именски простор.',
+ 'config-ns-conflict' => 'Наведениот именски простор „<nowiki>$1</nowiki>“ се коси со основниот именски простор на МедијаВики.
+Наведете друг именски простор за проектот.',
+ 'config-admin-box' => 'Администратоска сметка',
+ 'config-admin-name' => 'Вашето име:',
+ 'config-admin-password' => 'Лозинка:',
+ 'config-admin-password-confirm' => 'Пак лозинката:',
+ 'config-admin-help' => 'Тука внесете го вашето корисничко име, на пр. „Петар Петровски“.
+Ова име ќесе користи за најава во викито.',
+ 'config-admin-name-blank' => 'Внесете администраторско корисничко име.',
+ 'config-admin-name-invalid' => 'Назначенотго корисничко име „<nowiki>$1</nowiki>“ е неважечко.
+Назначете друго.',
+ 'config-admin-password-blank' => 'Внесете лозинка за администраторската сметка',
+ 'config-admin-password-same' => 'Лозинката не може да биде иста со корисничкото име.',
+ 'config-admin-password-mismatch' => 'Лозинките што ги внесовте не се совпаѓаат.',
+ 'config-admin-email' => 'Е-поштенска адреса:',
+ 'config-admin-email-help' => 'Тука внесете е-поштенска адреса за да можете да добивате е-пошта од други корисници на викито, да ја менувате лозинката, и да бидете известувани за промени во страниците на вашиот список на набљудувања. Можете и да го оставите празно.',
+ 'config-admin-error-user' => 'Се појави внатрешна грешка при создавањето на администраторот со име „<nowiki>$1</nowiki>“.',
+ 'config-admin-error-password' => 'Се појави внатрешна грешка при задавање на лозинката за администраторот „<nowiki>$1</nowiki>“: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Внесовте неважечка е-поштенска адреса',
+ 'config-subscribe' => 'Претплатете се на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release поштенскиот список за известувања].',
+ 'config-subscribe-help' => 'Ова е нископрометен поштенски список кој се користи за соопштувања во врска со изданија, вклучувајќи важни безбедносни соопштенија.
+Треба да се претплатите и да ја надградувате вашата инсталација на МедијаВики кога излегуваат нови верзии.',
+ 'config-almost-done' => 'Уште малку сте готови!
+Сега можете да ги прескокнете преостанатите поставувања и веднаш да го инсталирате викито.',
+ 'config-optional-continue' => 'Постави ми повеќе прашања.',
+ 'config-optional-skip' => 'Веќе ми здосади, дај само инсталирај го викито.',
+ 'config-profile' => 'Профил на кориснички права:',
+ 'config-profile-wiki' => 'Традиционално вики',
+ 'config-profile-no-anon' => 'Задолжително отворање сметка',
+ 'config-profile-fishbowl' => 'Само овластени уредници',
+ 'config-profile-private' => 'Приватно вики',
+ 'config-profile-help' => "Викијата функционираат најдобро кога имаат што повеќе уредници.
+Во МедијаВики лесно се проверуваат скорешните промени, и лесно се исправа (технички: „враќа“) штетата направена од неупатени или злонамерни корисници.
+
+Многумина имаат најдено најразлични полезни примени за МедијаВики, но понекогаш не е лесно да убедите некого во предностите на вики-концептот.
+Значи имате избор.
+
+'''{{int:config-profile-wiki}}''' — секој може да го уредува, дури и без најавување.
+Ако имате вики со '''задолжително отворање на сметка''', тогаш добивате повеќе контрола, но ова може даги одврати спонтаните учесници.
+
+'''{{int:config-profile-fishbowl}}''' — може да уредуваат само уредници што имаат добиено дозвола за тоа, но јавноста може да ги гледа страниците, вклучувајќи ја нивната историја.
+'''{{int:config-profile-private}}''' — страниците се видливи и уредливи само за овластени корисници.
+
+По инсталацијата имате на избор и посложени кориснички права и поставки. Погледајте во [http://www.mediawiki.org/wiki/Manual:User_rights прирачникот].",
+ 'config-license' => 'Авторски права и лиценца:',
+ 'config-license-none' => 'Без подножје за лиценца',
+ 'config-license-cc-by-sa' => 'Creative Commons НаведиИзвор СподелиПодИстиУслови',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons НаведиИзвор-Некомерцијално-СподелиПодИстиУслови',
+ 'config-license-cc-0' => 'Криејтив комонс Нула',
+ 'config-license-gfdl-old' => 'ГНУ-ова лиценца за слободна документација 1.2',
+ 'config-license-gfdl-current' => 'ГНУ-ова лиценца за слободна документација 1.3 или понова',
+ 'config-license-pd' => 'Јавен домен',
+ 'config-license-cc-choose' => 'Одберете друга Creative Commons лиценца по ваш избор',
+ 'config-license-help' => "Многу јавни викија ги ставаат сите придонеси под [http://freedomdefined.org/Definition слободна лиценца].
+Со ова се создава атмосфера на општа сопственост и поттикнува долгорочно учество.
+Ова не е неопходно за викија на поединечни физички или правни лица.
+
+Ако сакате да користите текст од Википедија, и сакате Википедија да прифаќа текст прекопиран од вашето вики, тогаш треба да ја одберете лиценцата '''Creative Commons НаведиИзвор СподелиПодИстиУслови'''.
+
+ГНУ-овата лиценца за слободна документација е старата лиценца на Википедија.
+Оваа лиценца сè уште важи, но има некои особености што значително го отежнуваат толкувањето на искористувањето на содржините вон Викимедија.",
+ 'config-email-settings' => 'Нагодувања за е-пошта',
+ 'config-enable-email' => 'Овозможи излезна е-пошта',
+ 'config-enable-email-help' => 'Ако сакате да работи е-поштата, [http://www.php.net/manual/en/mail.configuration.php поштенските нагодувања на PHP] треба да се правилно наместени.
+Ако воопшто не сакате никакви функции за е-пошта, тогаш можете да ги оневозможите тука.',
+ 'config-email-user' => 'Овозможи е-пошта од корисник до корисник',
+ 'config-email-user-help' => 'Дозволи сите корисници да можат да си праќаат е-пошта ако ја имаат овозможено во нагодувањата.',
+ 'config-email-usertalk' => 'Овозможи известувања за промени во кориснички страници за разговор',
+ 'config-email-usertalk-help' => 'Овозможи корисниците да добиваат известувања за промени во нивните кориснички страници за разговор ако ги имаат овозможено во нагодувањата.',
+ 'config-email-watchlist' => 'Овозможи известувања за список на набљудувања',
+ 'config-email-watchlist-help' => 'Овозможи корисниците да добиваат известувања за нивните набљудувани страници ако ги имаат овозможено во нагодувањата.',
+ 'config-email-auth' => 'Овозможи потврдување на е-пошта',
+ 'config-email-auth-help' => "Ако оваа можност е вклучена, тогаш корисниците ќе мора да ја потврдат нивната е-поштенска адреса преку врска испратена до нив кога ја укажуваат или менуваат е-поштенската адреса.
+Само корисници со потврдена е-пошта можат да добиваат е-пошта од други корисници или да ги менуваат писмата за известување.
+Оваа можност е '''препорачана''' за јавни викија поради можни злоупотреби на е-поштенската функција.",
+ 'config-email-sender' => 'Повратна е-поштенска адреса:',
+ 'config-email-sender-help' => 'Внесете ја е-поштенската адреса што ќе се користи како повратна адреса за излезна е-пошта.
+Таму ќе се испраќаат вратените (непримени) писма.
+Многу поштенски опслужувачи бараат барем делот за доменско име да биде важечки.',
+ 'config-upload-settings' => 'Подигање на слики и податотеки',
+ 'config-upload-enable' => 'Овозможи подигање на податотеки',
+ 'config-upload-help' => 'Подигањето на податотеки потенцијално го изложуваат вашиот опслужувач на безбедносни ризици.
+За повеќе информации, прочитајте го [http://www.mediawiki.org/wiki/Manual:Security поглавието за безбедност] во прирачникот.
+
+За да овозможите подигање на податотеки, сменете го режимот на потпапката <code>images</code> во основната папка на МедијаВики, за да му овозможите на мрежниот опслужувач да запишува во неа.
+Потоа овозможете ја оваа функција.',
+ 'config-upload-deleted' => 'Папка за избришаните податотеки:',
+ 'config-upload-deleted-help' => 'Одберете во која папка да се архивираат избришаните податотеки.
+Најдобро би било ако таа не е достапна преку интернет.',
+ 'config-logo' => 'URL за логото:',
+ 'config-logo-help' => 'Матичното руво на МедијаВики има простор за лого од 135x160 пиксели во горниот лев агол.
+Подигнете слика со соодветна големина, и тука внесете ја URL-адресата.
+
+Ако не сакате да имате лого, тогаш оставете го ова поле празно.',
+ 'config-instantcommons' => 'Овозможи Instant Commons',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] е функција која им овозможува на викијата да користат слики, звучни записи и други мултимедијални содржини од [http://commons.wikimedia.org/ Заедничката Ризница].
+За да може ова да работи, МедијаВики бара пристап до интернет.
+
+За повеќе информации за оваа функција и напатствија за нејзино поставување на вики (сите други освен Ризницата), коносултирајте го [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos прирачникот].',
+ 'config-cc-error' => 'Изборникот на Creative Commons лиценца не даде резултати.
+Внесете го името на лиценцата рачно.',
+ 'config-cc-again' => 'Одберете повторно...',
+ 'config-cc-not-chosen' => 'Одберете ја саканата Creative Commons лиценца и кликнете на „продолжи“.',
+ 'config-advanced-settings' => 'Напредни нагодувања',
+ 'config-cache-options' => 'Нагодувања за кеширање на објекти:',
+ 'config-cache-help' => 'Кеширањето на објекти се користи за зголемување на брзината на МедијаВики со кеширање на често употребуваните податоци.
+Ова многу се препорачува на средни до големи викија, но од тоа ќе имаат полза и малите викија.',
+ 'config-cache-none' => 'Без кеширање (не се остранува ниедна функција, но може да влијае на брзината кај поголеми викија)',
+ 'config-cache-accel' => 'Кеширање на PHP-објекти (APC, eAccelerator, XCache или WinCache)',
+ 'config-cache-memcached' => 'Користи Memcached (бара дополнително поставување и нагодување)',
+ 'config-memcached-servers' => 'Memcached-опслужувачи:',
+ 'config-memcached-help' => 'Список на IP-адреси за употреба во Memcached.
+Треба да се наведе по една во секој ред, како и портата што ќе се користи. На пример:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Го одбравте Memcached како ваш ваш тип на скришно памтење (кеш), но не наведовте опслужувач(и)',
+ 'config-memcache-badip' => 'Внесовте неважечка IP-адреса за Memcached: $1',
+ 'config-memcache-noport' => 'Не ја наведовте портата за опслужувачот на Memcached: $1.
+Ако не знаете која порта треба да се користи, основната е 11211',
+ 'config-memcache-badport' => 'Бројките за портата на Memcached треба да бидат помеѓу $1 и $2',
+ 'config-extensions' => 'Додатоци',
+ 'config-extensions-help' => 'Во вашата папка <code>./extensions</code> беа востановени горенаведените додатоци.
+
+За ова може да треба дополнително нагодување, но можете да ги овозможите сега',
+ 'config-install-alreadydone' => "'''Предупредување:''' Изгледа дека веќе го имате инсталирано МедијаВики и сега сакате да го инсталирате повторно.
+Продолжете на следната страница.",
+ 'config-install-begin' => 'Стискајќи на „{{int:config-continue}}“ ќе ја започнете инсталацијата на МедијаВики.
+Ако сакате да направите измени во досегашното, стиснете на „Назад“.',
+ 'config-install-step-done' => 'готово',
+ 'config-install-step-failed' => 'не успеа',
+ 'config-install-extensions' => 'Вклучувам додатоци',
+ 'config-install-database' => 'Ја поставувам базата на податоци',
+ 'config-install-pg-schema-not-exist' => 'PostgreSQL-шемата не постои',
+ 'config-install-pg-schema-failed' => 'Создавањето натабелите не успеа.
+Проверете дали корисникот „$1“ може да запишува во шемата „$2“.',
+ 'config-install-pg-commit' => 'Спроведување на промени',
+ 'config-install-pg-plpgsql' => 'Проверувам јазик PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Ќе треба да го инсталирате јазикот PL/pgSQL во базата $1',
+ 'config-pg-no-create-privs' => 'Сметката што ја наведовте за инсталацијата нема доволно привилегии за да создаде друга сметка.',
+ 'config-install-user' => 'Создавам корисник за базата',
+ 'config-install-user-alreadyexists' => 'Корисникот „$1“ веќе постои',
+ 'config-install-user-create-failed' => 'Создавањето на корисникот „$1“ не успеа: $2',
+ 'config-install-user-grant-failed' => 'Доделувањето на дозвола на корисникот „$1“ не успеа: $2',
+ 'config-install-tables' => 'Создавам табели',
+ 'config-install-tables-exist' => "'''Предупредување''': Изгледа дека табелите за МедијаВики веќе постојат.
+Го прескокнувам создавањето.",
+ 'config-install-tables-failed' => "'''Грешка''': Создавањето на табелата не успеа поради следнава грешка: $1",
+ 'config-install-interwiki' => 'Ги пополнувам основно-зададените интервики-табели',
+ 'config-install-interwiki-list' => 'Не можев да ја пронајдам податотеката <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Предупредување''': Табелата со интервикија веќе содржи ставки.
+Го прескокнувам основно-зададениот список.",
+ 'config-install-stats' => 'Ги подготвувам статистиките',
+ 'config-install-keys' => 'Создавам таен клуч',
+ 'config-insecure-keys' => "'''Предупредување:''' {{PLURAL:$2|Безбедносниот клуч $1 создаден во текот на инсталацијата не е сосем безбеден|Безбедносните клучеви $1 создадени во текот на инсталацијата не се сосем безбедни}}. Ви препорачуваме да {{PLURAL:$2|го|ги}} смените рачно.",
+ 'config-install-sysop' => 'Создавање на администраторска корисничка сметка',
+ 'config-install-subscribe-fail' => 'Не можам да ве претплатам на објавите на МедијаВики',
+ 'config-install-mainpage' => 'Создавам главна страница со стандардна содржина',
+ 'config-install-extension-tables' => 'Изработка на табели за овозможени додатоци',
+ 'config-install-mainpage-failed' => 'Не можев да вметнам главна страница: $1',
+ 'config-install-done' => "'''Честитаме!'''
+Успешно го инсталиравте МедијаВики.
+
+Инсталаторот создаде податотека <code>LocalSettings.php</code>.
+Таму се содржат сите ваши нагодувања.
+
+Ќе треба да ја преземете и да ја ставите во основата на инсталацијата (истата папка во која се наоѓа index.php). Преземањето треба да е започнато автоматски.
+
+Ако не ви е понудено преземање, или пак ако сте го откажале, можете да го почнете одново стискајќи на следнава врска:
+
+$3
+
+'''Напомена''': Ако ова не го направите сега, податотеката со поставки повеќе нема да биде на достапна.
+
+Откога ќе завршите со тоа, можете да '''[$2 влезете на вашето вики]'''.",
+ 'config-download-localsettings' => 'Преземи го LocalSettings.php',
+ 'config-help' => 'помош',
+);
+
+/** Malayalam (മലയാളം)
+ * @author Praveenp
+ */
+$messages['ml'] = array(
+ 'config-desc' => 'മീഡിയവിക്കി ഇൻസ്റ്റോളർ',
+ 'config-title' => 'മീഡിയവിക്കി $1 ഇൻസ്റ്റലേഷൻ',
+ 'config-information' => 'വിവരങ്ങൾ',
+ 'config-localsettings-upgrade' => "'''അറിയിപ്പ്''': ഒരു <code>LocalSettings.php</code> ഫയൽ കാണുന്നു.
+സോഫ്റ്റ്‌വേർ അപ്‌ഗ്രേഡ് ചെയ്യുക സാദ്ധ്യമാണ്.
+ദയവായി പെട്ടിയിൽ <code>\$wgUpgradeKey</code> എന്നതിന്റെ വില നൽകുക.",
+ 'config-localsettings-key' => 'അപ്‌ഗ്രേഡ് ചാവി:',
+ 'config-localsettings-badkey' => 'താങ്കൾ നൽകിയ ചാവി തെറ്റാണ്',
+ 'config-session-error' => 'സെഷൻ തുടങ്ങുന്നതിൽ പിഴവ്: $1',
+ 'config-your-language' => 'താങ്കളുടെ ഭാഷ:',
+ 'config-your-language-help' => 'ഇൻസ്റ്റലേഷൻ പ്രക്രിയയിൽ ഉപയോഗിക്കേണ്ട ഭാഷ തിരഞ്ഞെടുക്കുക.',
+ 'config-wiki-language' => 'വിക്കി ഭാഷ:',
+ 'config-wiki-language-help' => 'വിക്കിയിൽ പ്രധാനമായി ഉപയോഗിക്കേണ്ട ഭാഷ തിരഞ്ഞെടുക്കുക.',
+ 'config-back' => '← പിന്നിലേയ്ക്ക്',
+ 'config-continue' => 'തുടരുക →',
+ 'config-page-language' => 'ഭാഷ',
+ 'config-page-welcome' => 'മീഡിയവിക്കിയിലേയ്ക്ക് സ്വാഗതം!',
+ 'config-page-dbconnect' => 'ഡേറ്റാബേസുമായി ബന്ധപ്പെടുക',
+ 'config-page-upgrade' => 'നിലവിലുള്ള ഇൻസ്റ്റലേഷൻ അപ്‌ഗ്രേഡ് ചെയ്യുക',
+ 'config-page-dbsettings' => 'ഡേറ്റാബേസ് സജ്ജീകരണങ്ങൾ',
+ 'config-page-name' => 'പേര്',
+ 'config-page-options' => 'ഐച്ഛികങ്ങൾ',
+ 'config-page-install' => 'ഇൻസ്റ്റോൾ',
+ 'config-page-complete' => 'സമ്പൂർണ്ണം!',
+ 'config-page-restart' => 'ഇൻസ്റ്റലേഷൻ അടച്ച ശേഷം പുനർപ്രവർത്തിപ്പിക്കുക',
+ 'config-page-readme' => 'ഇത് വായിക്കൂ',
+ 'config-page-releasenotes' => 'പ്രകാശന കുറിപ്പുകൾ',
+ 'config-page-copying' => 'പകർത്തൽ',
+ 'config-page-upgradedoc' => 'അപ്‌ഗ്രേഡിങ്',
+ 'config-help-restart' => 'ഇതുവരെ ഉൾപ്പെടുത്തിയ എല്ലാവിവരങ്ങളും ഒഴിവാക്കാനും ഇൻസ്റ്റലേഷൻ പ്രക്രിയ നിർത്തി-വീണ്ടുമാരംഭിക്കാനും താങ്കളാഗ്രഹിക്കുന്നുണ്ടോ?',
+ 'config-restart' => 'അതെ, പുനർപ്രവർത്തിപ്പിക്കുക',
+ 'config-sidebar' => '* [http://www.mediawiki.org മീഡിയവിക്കി പ്രധാനതാൾ]
+* [http://www.mediawiki.org/wiki/Help:Contents ഉപയോക്തൃസഹായി]
+* [http://www.mediawiki.org/wiki/Manual:Contents കാര്യനിർവഹണസഹായി]
+* [http://www.mediawiki.org/wiki/Manual:FAQ പതിവുചോദ്യങ്ങൾ]',
+ 'config-env-php' => 'പി.എച്ച്.പി. $1 ഇൻസ്റ്റോൾ ചെയ്തിട്ടുണ്ട്.',
+ 'config-no-db' => 'അനുയോജ്യമായ ഡേറ്റാബേസ് ഡ്രൈവർ കണ്ടെത്താനായില്ല!',
+ 'config-memory-raised' => 'പി.എച്ച്.പി.യുടെ <code>memory_limit</code> $1 ആണ്, $2 ആയി ഉയർത്തിയിരിക്കുന്നു.',
+ 'config-memory-bad' => "'''മുന്നറിയിപ്പ്:''' പി.എച്ച്.പി.യുടെ <code>memory_limit</code> $1 ആണ്.
+ഇത് മിക്കവാറും വളരെ കുറവാണ്.
+ഇൻസ്റ്റലേഷൻ പരാജയപ്പെട്ടേക്കാം!",
+ 'config-db-type' => 'ഡേറ്റാബേസ് തരം:',
+ 'config-db-host' => 'ഡേറ്റാബേസ് ഹോസ്റ്റ്:',
+ 'config-db-name' => 'ഡേറ്റാബേസിന്റെ പേര്:',
+ 'config-db-name-oracle' => 'ഡേറ്റാബേസ് സ്കീമ:',
+ 'config-db-install-account' => 'ഇൻസ്റ്റലേഷനുള്ള ഉപയോക്തൃ അംഗത്വം',
+ 'config-db-username' => 'ഡേറ്റാബേസ് ഉപയോക്തൃനാമം:',
+ 'config-db-password' => 'ഡേറ്റാബേസ് രഹസ്യവാക്ക്:',
+ 'config-mysql-old' => 'മൈഎസ്‌ക്യൂഎൽ $1 അഥവാ അതിലും പുതിയത് ആവശ്യമാണ്, താങ്കളുടെ പക്കൽ ഉള്ളത് $2 ആണ്.',
+ 'config-db-port' => 'ഡേറ്റാബേസ് പോർട്ട്:',
+ 'config-db-schema' => 'മീഡിയവിക്കിയ്ക്കായുള്ള സ്കീമ',
+ 'config-support-info' => 'മീഡിയവിക്കി താഴെ പറയുന്ന ഡേറ്റാബേസ് സിസ്റ്റംസ് പിന്തുണയ്ക്കുന്നു:
+
+$1
+
+താങ്കൾ ഉപയോഗിക്കാനാഗ്രഹിക്കുന്ന ഡേറ്റാബേസ് സിസ്റ്റം പട്ടികയിലില്ലെങ്കിൽ, ദയവായി പിന്തുണ സജ്ജമാക്കാനായി മുകളിൽ നൽകിയിട്ടുള്ള ലിങ്കിലെ നിർദ്ദേശങ്ങൾ ചെയ്യുക.',
+ 'config-header-mysql' => 'മൈഎസ്‌ക്യൂഎൽ സജ്ജീകരണങ്ങൾ',
+ 'config-invalid-db-type' => 'അസാധുവായ ഡേറ്റാബേസ് തരം',
+ 'config-missing-db-name' => '"ഡേറ്റാബേസിന്റെ പേരി"ന് ഒരു വില നിർബന്ധമായും നൽകിയിരിക്കണം',
+ 'config-connection-error' => '$1.
+
+താഴെ നൽകിയിരിക്കുന്ന ഹോസ്റ്റ്, ഉപയോക്തൃനാമം, രഹസ്യവാക്ക് എന്നിവ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.',
+ 'config-regenerate' => 'LocalSettings.php പുനഃസൃഷ്ടിക്കുക →',
+ 'config-mysql-engine' => 'സ്റ്റോറേജ് എൻജിൻ:',
+ 'config-site-name' => 'വിക്കിയുടെ പേര്:',
+ 'config-site-name-help' => 'ഇത് ബ്രൗസറിന്റെ ടൈറ്റിൽ ബാറിലും മറ്റനേകം ഇടങ്ങളിലും പ്രദർശിപ്പിക്കപ്പെടും.',
+ 'config-site-name-blank' => 'സൈറ്റിന്റെ പേര് നൽകുക.',
+ 'config-project-namespace' => 'പദ്ധതി നാമമേഖല:',
+ 'config-ns-generic' => 'പദ്ധതി',
+ 'config-ns-site-name' => 'വിക്കിയുടെ പേര് തന്നെ: $1',
+ 'config-ns-other' => 'ഇതരം (വ്യക്തമാക്കുക)',
+ 'config-ns-other-default' => 'എന്റെ‌വിക്കി',
+ 'config-admin-box' => 'കാര്യനിർവാഹക അംഗത്വം',
+ 'config-admin-name' => 'താങ്കളുടെ പേര്:',
+ 'config-admin-password' => 'രഹസ്യവാക്ക്:',
+ 'config-admin-password-confirm' => 'രഹസ്യവാക്ക് ഒരിക്കൽക്കൂടി:',
+ 'config-admin-help' => 'ഇവിടെ താങ്കളുടെ ഇച്ഛാനുസരണമുള്ള ഉപയോക്തൃനാമം നൽകുക, ഉദാഹരണം "ശശി കൊട്ടാരത്തിൽ".
+ഈ പേരായിരിക്കണം വിക്കിയിൽ പ്രവേശിക്കാൻ താങ്കൾ ഉപയോഗിക്കേണ്ടത്.',
+ 'config-admin-name-blank' => 'ഒരു കാര്യനിർവാഹക ഉപയോക്തൃനാമം നൽകുക.',
+ 'config-admin-name-invalid' => 'നൽകിയിട്ടുള്ള ഉപയോക്തൃനാമം "<nowiki>$1</nowiki>" അസാധുവാണ്.
+മറ്റൊരു ഉപയോക്തൃനാമം നൽകുക.',
+ 'config-admin-password-blank' => 'കാര്യനിർവാഹക അംഗത്വത്തിനുള്ള രഹസ്യവാക്ക് നൽകുക.',
+ 'config-admin-password-same' => 'രഹസ്യവാക്കും ഉപയോക്തൃനാമവും ഒന്നാകരുത്.',
+ 'config-admin-password-mismatch' => 'താങ്കൾ നൽകിയ രഹസ്യവാക്കുകൾ രണ്ടും തമ്മിൽ യോജിക്കുന്നില്ല.',
+ 'config-admin-email' => 'ഇമെയിൽ വിലാസം:',
+ 'config-admin-error-user' => '"<nowiki>$1</nowiki>" എന്ന പേരിലുള്ള കാര്യനിർവഹണ അംഗത്വ നിർമ്മിതിയ്ക്കിടെ ആന്തരികമായ പിഴവുണ്ടായി.',
+ 'config-admin-error-password' => '"<nowiki>$1</nowiki>" എന്ന പേരിലുള്ള കാര്യനിർവാഹക അംഗത്വത്തിനു രഹസ്യവാക്ക് സജ്ജീകരിച്ചപ്പോൾ ആന്തരികമായ പിഴവുണ്ടായി: <pre>$2</pre>',
+ 'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce പ്രകാശന അറിയിപ്പ് മെയിലിങ് ലിസ്റ്റിൽ] വരിക്കാരാകുക.',
+ 'config-subscribe-help' => 'പുറത്തിറക്കൽ അറിയിപ്പുകളും, പ്രധാന സുരക്ഷാ അറിയിപ്പുകളും പ്രസിദ്ധീകരിക്കുന്ന വളരെ എഴുത്തുകളൊന്നും ഉണ്ടാകാറില്ലാത്ത മെയിലിങ് ലിസ്റ്റ് ആണിത്.
+പുതിയ പതിപ്പുകൾ പുറത്ത് വരുന്നതനുസരിച്ച് അവയെക്കുറിച്ചറിയാനും മീഡിയവിക്കി ഇൻസ്റ്റലേഷൻ പുതുക്കാനും ഇതിന്റെ വരിക്കാരൻ/വരിക്കാരി ആവുക.',
+ 'config-almost-done' => 'മിക്കവാറും പൂർത്തിയായിരിക്കുന്നു!
+ബാക്കിയുള്ളവ അവഗണിച്ച് വിക്കി ഇൻസ്റ്റോൾ ചെയ്യാവുന്നതാണ്.',
+ 'config-optional-continue' => 'കൂടുതൽ ചോദ്യങ്ങൾ ചോദിക്കൂ.',
+ 'config-optional-skip' => 'ഞാൻ മടുത്തു, ഇൻസ്റ്റോൾ ചെയ്ത് തീർക്ക്.',
+ 'config-profile-wiki' => 'പരമ്പരാഗത വിക്കി',
+ 'config-profile-no-anon' => 'അംഗത്വ സൃഷ്ടി ചെയ്യേണ്ടതുണ്ട്',
+ 'config-profile-fishbowl' => 'അനുവാദമുള്ളവർ മാത്രം തിരുത്തുക',
+ 'config-profile-private' => 'സ്വകാര്യ വിക്കി',
+ 'config-license' => 'പകർപ്പവകാശവും അനുമതിയും:',
+ 'config-license-cc-by-sa' => 'ക്രിയേറ്റീവ് കോമൺസ് ആട്രിബ്യൂഷൻ ഷെയർ എലൈക്',
+ 'config-license-cc-by-nc-sa' => 'ക്രിയേറ്റീവ് കോമൺസ് ആട്രിബ്യൂഷൻ നോൺ-കൊമേഴ്സ്യൽ ഷെയർ എലൈക്',
+ 'config-license-gfdl-old' => 'ഗ്നൂ സ്വതന്ത്ര പ്രസിദ്ധീകരണാനുമതി 1.2',
+ 'config-license-gfdl-current' => 'ഗ്നൂ സ്വതന്ത്ര പ്രസിദ്ധീകരണാനുമതി 1.3 അഥവാ പുതിയത്',
+ 'config-license-pd' => 'പൊതു സഞ്ചയം',
+ 'config-email-settings' => 'ഇമെയിൽ സജ്ജീകരണങ്ങൾ',
+ 'config-enable-email-help' => "ഇമെയിൽ പ്രവർത്തിക്കണമെങ്കിൽ, [http://www.php.net/manual/en/mail.configuration.php PHP's മെയിൽ സജ്ജീകരണങ്ങൾ] ശരിയായി ക്രമീകരിക്കേണ്ടതുണ്ട്.
+ഇമെയിൽ സൗകര്യം ആവശ്യമില്ലെങ്കിൽ, ഇവിടെത്തന്നെ അത് നിർജ്ജീവമാക്കാം.",
+ 'config-email-user' => 'ഉപയോക്താക്കൾ തമ്മിലുള്ള ഇമെയിൽ പ്രവർത്തനസജ്ജമാക്കുക',
+ 'config-email-user-help' => 'സ്വന്തം ക്രമീകരണങ്ങളിൽ ഇമെയിൽ സജ്ജമാക്കിയിട്ടുണ്ടെങ്കിൽ ഉപയോക്താക്കളെ മറ്റുള്ളവർക്ക് ഇമെയിൽ അയയ്ക്കാൻ അനുവദിക്കുക.',
+ 'config-email-usertalk' => 'ഉപയോക്തൃസംവാദം താളിൽ മാറ്റങ്ങളുണ്ടായാൽ അറിയിക്കുക',
+ 'config-email-watchlist' => 'ശ്രദ്ധിക്കുന്നവയിൽ മാറ്റം വന്നാൽ അറിയിക്കുക',
+ 'config-email-auth' => 'ഇമെയിലിന്റെ സാധുതാപരിശോധന സജ്ജമാക്കുക',
+ 'config-email-sender' => 'മറുപടിയ്ക്കുള്ള ഇമെയിൽ വിലാസം:',
+ 'config-upload-settings' => 'ചിത്രങ്ങളും പ്രമാണങ്ങളും അപ്‌ലോഡ് ചെയ്യൽ',
+ 'config-upload-enable' => 'പ്രമാണ അപ്‌ലോഡുകൾ സജ്ജമാക്കുക',
+ 'config-upload-deleted' => 'മായ്ക്കപ്പെട്ട ഫയലുകൾക്കുള്ള ഡയറക്റ്ററി:',
+ 'config-logo' => 'ലോഗോയുടെ യൂ.ആർ.എൽ.:',
+ 'config-logo-help' => 'മീഡിയവിക്കിയിൽ സ്വതേയുള്ള ദൃശ്യരൂപത്തിൽ 135x160 പിക്സലുള്ള ലോഗോ മുകളിൽ ഇടത് മൂലയിൽ കാണാം.
+അനുയോജ്യമായ വലിപ്പമുള്ള ഒരു ചിത്രം അപ്‌ലോഡ് ചെയ്തിട്ട്, അതിന്റെ യൂ.ആർ.എൽ. ഇവിടെ നൽകുക.
+
+താങ്കൾക്ക് ലോഗോ ആവശ്യമില്ലെങ്കിൽ, ഈ പെട്ടി ശൂന്യമായിടുക.',
+ 'config-cc-again' => 'ഒന്നുകൂടി എടുക്കൂ...',
+ 'config-advanced-settings' => 'വിപുലീകൃത ക്രമീകരണങ്ങൾ',
+ 'config-extensions' => 'അനുബന്ധങ്ങൾ',
+ 'config-install-step-done' => 'ചെയ്തു കഴിഞ്ഞു',
+ 'config-install-step-failed' => 'പരാജയപ്പെട്ടു',
+ 'config-install-extensions' => 'അനുബന്ധങ്ങൾ ഉൾപ്പെടുത്തുന്നു',
+ 'config-install-database' => 'ഡേറ്റാബേസ് സജ്ജമാക്കുന്നു',
+ 'config-install-pg-commit' => 'മാറ്റങ്ങൾ സ്വീകരിക്കുന്നു',
+ 'config-install-user' => 'ഡേറ്റാബേസ് ഉപയോക്താവിനെ സൃഷ്ടിക്കുന്നു',
+ 'config-install-sysop' => 'കാര്യനിർവാഹക അംഗത്വം സൃഷ്ടിക്കുന്നു',
+ 'config-install-mainpage' => 'സ്വാഭാവിക ഉള്ളടക്കത്തോടുകൂടി പ്രധാനതാൾ സൃഷ്ടിക്കുന്നു',
+ 'config-install-mainpage-failed' => 'പ്രധാന താൾ ഉൾപ്പെടുത്താൻ കഴിഞ്ഞില്ല: $1',
+ 'config-install-done' => "'''അഭിനന്ദനങ്ങൾ!'''
+താങ്കൾ വിജയകരമായി മീഡിയവിക്കി ഇൻസ്റ്റോൾ ചെയ്തിരിക്കുന്നു.
+
+ഇൻസ്റ്റോളർ ഒരു <code>LocalSettings.php</code> ഫയൽ സൃഷ്ടിച്ചിട്ടുണ്ട്.
+അതിൽ താങ്കളുടെ എല്ലാ ക്രമീകരണങ്ങളുമുണ്ട്.
+
+താങ്കൾ അത് [$1 എടുത്ത്] താങ്കളുടെ വിക്കി ഇൻസ്റ്റലേഷന്റെ അടിസ്ഥാന ഡയറക്റ്ററിയിൽ ഇടുക (index.php കിടക്കുന്ന അതേ ഡയറക്റ്ററി).
+'''ശ്രദ്ധിക്കുക''': ഇത് ഇപ്പോൾ ചെയ്തില്ലെങ്കിൽ, സൃഷ്ടിക്കപ്പെട്ട കോൺഫിഗറേഷൻ ഫയൽ എടുക്കാതെ ഇൻസ്റ്റലേഷൻ പ്രക്രിയയിൽ നിന്ന് പുറത്തിറങ്ങിയാൽ പിന്നീട് ലഭ്യമായിരിക്കില്ല.
+
+ചെയ്തശേഷം, താങ്കൾക്ക് '''[$2 വിക്കിയിൽ പ്രവേശിക്കാം]'''.",
+);
+
+/** Mongolian (Монгол)
+ * @author Chinneeb
+ */
+$messages['mn'] = array(
+ 'config-page-language' => 'Хэл',
+);
+
+/** Erzya (Эрзянь)
+ * @author Botuzhaleny-sodamo
+ */
+$messages['myv'] = array(
+ 'config-page-language' => 'Кель',
+ 'config-page-name' => 'Лемезэ',
+ 'config-page-readme' => 'Ловномак',
+ 'config-admin-name' => 'Леметь:',
+ 'config-admin-password' => 'Совамо валот:',
+ 'config-admin-password-confirm' => 'Совамо валот одов:',
+ 'config-admin-email' => 'Е-сёрма паргот:',
+ 'config-install-step-done' => 'теезь',
+);
+
+/** Dutch (Nederlands)
+ * @author Catrope
+ * @author McDutchie
+ * @author Purodha
+ * @author SPQRobin
+ * @author Siebrand
+ */
+$messages['nl'] = array(
+ 'config-desc' => 'Het installatieprogramma voor MediaWiki',
+ 'config-title' => 'Installatie MediaWiki $1',
+ 'config-information' => 'Informatie',
+ 'config-localsettings-upgrade' => 'Er is een bestaand instellingenbestand <code>LocalSettings.php</code> gevonden.
+Voer de waarde van <code>$wgUpgradeKey</code> in in onderstaande invoerveld om deze installatie bij te werken.
+De instelling is terug te vinden in LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Het bestand LocalSettings.php is al aanwezig.
+Voer update.php uit om deze installatie bij te werken.',
+ 'config-localsettings-key' => 'Upgradesleutel:',
+ 'config-localsettings-badkey' => 'De sleutel die u hebt opgegeven is onjuist',
+ 'config-upgrade-key-missing' => 'Er is een bestaande installatie van MediaWiki aangetroffen.
+Plaats de volgende regel onderaan uw LocalSettings.php om deze installatie bij te werken:
+
+$1',
+ 'config-localsettings-incomplete' => 'De bestaande inhoud van LocalSettings.php lijkt incompleet.
+De variabele $1 is niet ingesteld.
+Wijzig LocalSettings.php zodat deze variabele is ingesteld en klik op "Doorgaan".',
+ 'config-localsettings-connection-error' => 'Er is een fout opgetreden tijdens het verbinden van de database met de instellingen uit LocalSettings.php of AdminSettings.php. Los het probleem met de instellingen op en probeer het daarna opnieuw.
+
+$1',
+ 'config-session-error' => 'Fout bij het begin van de sessie: $1',
+ 'config-session-expired' => 'Uw sessiegegevens zijn verlopen.
+Sessies zijn ingesteld om een levensduur van $1 te hebben.
+U kunt deze wijzigen via de instelling <code>session.gc_maxlifetime</code> in php.ini.
+Begin het installatieproces opnieuw.',
+ 'config-no-session' => 'Uw sessiegegevens zijn verloren gegaan.
+Controleer uw php.ini en zorg dat er een juiste map is ingesteld voor <code>session.save_path</code>.',
+ 'config-your-language' => 'Uw taal:',
+ 'config-your-language-help' => 'Selecteer een taal om tijdens het installatieproces te gebruiken.',
+ 'config-wiki-language' => 'Wikitaal:',
+ 'config-wiki-language-help' => 'Selecteer de taal waar de wiki voornamelijk in wordt geschreven.',
+ 'config-back' => '← Terug',
+ 'config-continue' => 'Doorgaan →',
+ 'config-page-language' => 'Taal',
+ 'config-page-welcome' => 'Welkom bij MediaWiki!',
+ 'config-page-dbconnect' => 'Verbinding maken met database',
+ 'config-page-upgrade' => 'Bestaande installatie bijwerken',
+ 'config-page-dbsettings' => 'Databaseinstellingen',
+ 'config-page-name' => 'Naam',
+ 'config-page-options' => 'Opties',
+ 'config-page-install' => 'Installeren',
+ 'config-page-complete' => 'Afgerond!',
+ 'config-page-restart' => 'Installatie herstarten',
+ 'config-page-readme' => 'Lees mij',
+ 'config-page-releasenotes' => 'Release notes',
+ 'config-page-copying' => 'Kopiëren',
+ 'config-page-upgradedoc' => 'Bijwerken',
+ 'config-page-existingwiki' => 'Bestaande wiki',
+ 'config-help-restart' => 'Wilt u alle opgeslagen gegevens die u hebt ingevoerd wissen en het installatieproces opnieuw starten?',
+ 'config-restart' => 'Ja, opnieuw starten',
+ 'config-welcome' => '=== Controle ongeving ===
+Er worden een aantal basale controles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.
+Als u hulp nodig hebt bij de installatie, lever deze gegevens dan ook aan.',
+ 'config-copyright' => "=== Auteursrechten en voorwaarden ===
+
+$1
+
+Dit programma is vrije software. U mag het verder verspreiden en/of aanpassen in overeenstemming met de voorwaarden van de GNU General Public License zoals uitgegeven door de Free Software Foundation; ofwel versie 2 van de Licentie of - naar uw keuze - enige latere versie.
+
+Dit programma wordt verspreid in de hoop dat het nuttig is, maar '''zonder enige garantie''', zelfs zonder de impliciete garantie van '''verkoopbaarheid''' of '''geschiktheid voor een bepaald doel'''.
+Zie de GNU General Public License voor meer informatie.
+
+Samen met dit programma hoort u een <doclink href=Copying>exemplaar van de GNU General Public License</doclink> ontvangen te hebben; zo niet, schrijf dan aan de Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Verenigde Staten. Of [http://www.gnu.org/copyleft/gpl.html lees de licentie online].",
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki thuispagina]
+* [http://www.mediawiki.org/wiki/Help:Contents Gebruikershandleiding] (Engelstalig)
+* [http://www.mediawiki.org/wiki/Manual:Contents Beheerdershandleiding] (Engelstalig)
+* [http://www.mediawiki.org/wiki/Manual:FAQ Veel gestelde vragen] (Engelstalig)
+----
+* <doclink href=Readme>Leesmij</doclink> (Engelstalig)
+* <doclink href=ReleaseNotes>Release notes</doclink> (Engelstalig)
+* <doclink href=Copying>Kopiëren</doclink> (Engelstalig)
+* <doclink href=UpgradeDoc>Versie bijwerken</doclink> (Engelstalig)',
+ 'config-env-good' => 'De omgeving is gecontroleerd.
+U kunt MediaWiki installeren.',
+ 'config-env-bad' => 'De omgeving is gecontroleerd.
+U kunt MediaWiki niet installeren.',
+ 'config-env-php' => 'PHP $1 is op dit moment geïnstalleerd.',
+ 'config-env-php-toolow' => 'PHP $1 is geïnstalleerd.
+MediaWiki heeft PHP $2 of hoger nodig om correct te kunnen werken.',
+ 'config-unicode-using-utf8' => 'Voor Unicode-normalisatie wordt utf8_normalize.so van Brion Vibber gebruikt.',
+ 'config-unicode-using-intl' => 'Voor Unicode-normalisatie wordt de [http://pecl.php.net/intl PECL-extensie intl] gebruikt.',
+ 'config-unicode-pure-php-warning' => "'''Waarschuwing''': De [http://pecl.php.net/intl PECL-extensie intl] is niet beschikbaar om de Unicode-normalisatie af te handelen en daarom wordt de langzame PHP-implementatie gebruikt.
+Als u MediaWiki voor een website met veel verkeer installeert, lees u dan in over [http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-normalisatie].",
+ 'config-unicode-update-warning' => "'''Waarschuwing''': De geïnstalleerde versie van de Unicode-normalisatiewrapper maakt gebruik van een oudere versie van [http://site.icu-project.org/ de bibliotheek van het ICU-project].
+U moet [http://www.mediawiki.org/wiki/Unicode_normalization_considerations bijwerken] als Unicode voor u van belang is.",
+ 'config-no-db' => 'Er kon geen geschikte databasedriver geladen worden!',
+ 'config-no-db-help' => 'U moet een databasedriver installeren voor PHP.
+De volgende databases worden ondersteund: $1.
+
+Als u op een gedeelde omgeving zit, vraag dan aan uw hostingprovider een geschikte databasedriver te installeren.
+Als u PHP zelf hebt gecompileerd, wijzig dan uw instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysql</code>.
+Als u PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook de module php5-mysql.',
+ 'config-no-fts3' => "'''Waarschuwing''': SQLite is gecompileerd zonder de module [http://sqlite.org/fts3.html FTS3]; er zijn geen zoekfuncties niet beschikbaar.",
+ 'config-register-globals' => "'''Waarschuwing: De PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''
+'''Schakel deze uit als dat mogelijk is.'''
+MediaWiki kan ermee werken, maar uw server is dan meer kwetsbaar voor beveiligingslekken.",
+ 'config-magic-quotes-runtime' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is actief!'''
+Deze instelling zorgt voor gegevenscorruptie.
+U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ 'config-magic-quotes-sybase' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] is actief!'''
+Deze instelling zorgt voor gegevenscorruptie.
+U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ 'config-mbstring' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is actief!'''
+Deze instelling zorgt voor gegevenscorruptie.
+U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ 'config-ze1' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] is actief!'''
+Deze instelling zorgt voor grote problemen in MediaWiki.
+U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+ 'config-safe-mode' => "'''Waarschuwing:'''
+'''PHP's [http://www.php.net/features.safe-mode veilige modus] is actief.'''
+Dit kan problemen veroorzaken, vooral bij het uploaden van bestanden en ondersteuning van <code>math</code>.",
+ 'config-xml-bad' => 'De XML-module van PHP ontbreekt.
+MediaWiki heeft de functies van deze module nodig en werkt niet zonder deze module.
+Als u gebruik maakt van Mandrake, installeer dan het package php-xml.',
+ 'config-pcre' => 'De ondersteuningsmodule PCRE lijkt te missen.
+MediaWiki vereist dat de met Perl compatibele reguliere expressies werken.',
+ 'config-pcre-no-utf8' => "'''Fataal:''' de module PRCE van PHP lijkt te zijn gecompileerd zonder ondersteuning voor PCRE_UTF8.
+MediaWiki heeft ondersteuning voor UTF-8 nodig om correct te kunnen werken.",
+ 'config-memory-raised' => "PHP's <code>memory_limit</code> is $1 en is verhoogd tot $2.",
+ 'config-memory-bad' => "'''Waarschuwing:''' PHP's <code>memory_limit</code> is $1.
+Dit is waarschijnlijk te laag.
+De installatie kan mislukken!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] is op dit moment geïnstalleerd',
+ 'config-apc' => '[http://www.php.net/apc APC] is op dit moment geïnstalleerd',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] is op dit moment geïnstalleerd',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] is op dit moment geïnstalleerd',
+ 'config-no-cache' => "'''Waarschuwing:''' [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC] of [http://trac.lighttpd.net/ xcache / XCache] is niet aangetroffen.
+Het cachen van objecten is niet ingeschakeld.",
+ 'config-diff3-bad' => 'GNU diff3 niet aangetroffen.',
+ 'config-imagemagick' => 'ImageMagick aangetroffen: <code>$1</code>.
+Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.',
+ 'config-gd' => 'Ingebouwde GD grafische bibliotheek aangetroffen.
+Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.',
+ 'config-no-scaling' => 'De GD-bibliotheek en ImageMagick zijn niet aangetroffen.
+Het maken van miniaturen van afbeeldingen wordt uitgeschakeld.',
+ 'config-no-uri' => "'''Fout:''' de huidige URI kon niet vastgesteld worden.
+De installatie is afgebroken.",
+ 'config-uploads-not-safe' => "'''Waarschuwing:''' uw uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.
+Hoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [http://www.mediawiki.org/wiki/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
+ 'config-brokenlibxml' => 'Uw systeem heeft een combinatie van PHP- en libxml2-versies geïnstalleerd die is foutgevoelig is en kan leiden tot onzichtbare beschadiging van gegevens in MediaWiki en andere webapplicaties.
+Upgrade naar PHP 5.2.9 of hoger en libxml2 2.7.3 of hoger! De installatie wordt afgebroken ([http://bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).',
+ 'config-using531' => 'PHP $1 is niet compatibel met MediaWiki vanwege een fout met betrekking tot referentieparameters met <code>__call()</code>.
+Werk uw PHP bij naar PHP 5.3.2 of hoger of werk bij naar de lagere versie PHP 5.3.0 om dit op te lossen.
+De installatie wordt afgebroken.',
+ 'config-db-type' => 'Databasetype:',
+ 'config-db-host' => 'Databasehost:',
+ 'config-db-host-help' => 'Als uw databaseserver een andere server is, voer dan de hostnaam of het IP-adres hier in.
+
+Als u gebruik maakt van gedeelde webhosting, hoort uw provider u de juiste hostnaam te hebben verstrekt.
+
+Als u MediaWiki op een Windowsserver installeert en MySQL gebruikt, dan werkt "localhost" mogelijk niet als servernaam.
+Als het inderdaad niet werkt, probeer dan "127.0.0.1" te gebruiken als lokaal IP-adres.',
+ 'config-db-host-oracle' => 'Database-TNS:',
+ 'config-db-host-oracle-help' => 'Voer een geldige [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] in; een tnsnames.ora-bestand moet zichtbaar zijn voor deze installatie.<br />Als u gebruik maakt van clientlibraries 10g of een latere versie, kunt u ook gebruik maken van de naamgevingsmethode [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Identificeer deze wiki',
+ 'config-db-name' => 'Databasenaam:',
+ 'config-db-name-help' => 'Kies een naam die uw wiki identificeert.
+Er mogen geen spaties gebruikt worden.
+Als u gebruik maakt van gedeelde webhosting, dan hoort uw provider ofwel u een te gebruiken databasenaam gegeven te hebben, of u aangegeven te hebben hoe u databases kunt aanmaken.',
+ 'config-db-name-oracle' => 'Databaseschema:',
+ 'config-db-account-oracle-warn' => 'Er zijn drie ondersteunde scenario\'s voor het installeren van Oracle als databasebackend:
+
+Als u een databasegebruiker wilt aanmaken als onderdeel van het installatieproces, geef dan de gegevens op van een databasegebruiker in met de rol SYSDBA voor de installatie en voer de gewenste aanmeldgegevens in voor de gebruiker met webtoegang. U kunt ook de gebruiker met webtoegang handmatig aanmaken en alleen van die gebruiker de aanmeldgegevens opgeven als deze de vereiste rechten heeft om schemaobjecten aan te maken. Als laatste is het mogelijk om aanmeldgegevens van twee verschillende gebruikers op te geven; een met de rechten om schemaobjecten aan te maken, en een met alleen webtoegang.
+
+Een script voor het aanmaken van een gebruiker met de vereiste rechten is te vinden in de map "maintenance/oracle/" van deze installatie. Onthoud dat het gebruiken van een gebruiker met beperkte rechten alle mogelijkheden om beheerscripts uit te voeren met de standaard gebruiker onmogelijk maakt.',
+ 'config-db-install-account' => 'Gebruiker voor installatie',
+ 'config-db-username' => 'Gebruikersnaam voor database:',
+ 'config-db-password' => 'Wachtwoord voor database:',
+ 'config-db-password-empty' => 'Voer een wachtwoord in voor de nieuwe databasegebruiker: $1.
+Hoewel het wellicht mogelijk is gebruikers aan te maken zonder wachtwoord, is dit niet veilig.',
+ 'config-db-install-username' => 'Voer de gebruikersnaam in die gebruikt moet worden om te verbinden met de database tijdens het installatieproces. Dit is niet de gebruikersnaam van de MediaWikigebruiker. Dit is de gebruikersnaam voor de database.',
+ 'config-db-install-password' => 'Voer het wachtwoord in dat gebruikt moet worden om te verbinden met de database tijdens het installatieproces. Dit is niet het wachtwoord van de MediaWikigebruiker. Dit is het wachtwoord voor de database.',
+ 'config-db-install-help' => 'Voer de gebruikersnaam en het wachtwoord in die worden gebruikt voor de databaseverbinding tijdens het installatieproces.',
+ 'config-db-account-lock' => 'Dezelfde gebruiker en wachwoord gebruiken na de installatie',
+ 'config-db-wiki-account' => 'Gebruiker voor na de installatie',
+ 'config-db-wiki-help' => 'Selecteer de gebruikersnaam en het wachtwoord die gebruikt worden om verbinding te maken met de database na de installatie.
+Als de gebruiker niet bestaat en de gebruiker die tijdens de installatie gebruikt wordt voldoende rechten heeft, wordt deze gebruiker aangemaakt met de minimaal benodigde rechten voor het laten werken van de wiki.',
+ 'config-db-prefix' => 'Databasetabelvoorvoegsel:',
+ 'config-db-prefix-help' => "Als u een database moet gebruiken voor meerdere wiki's, of voor MediaWiki en een andere applicatie, dan kunt u ervoor kiezen om een voorvoegsel toe te voegen aan de tabelnamen om conflicten te voorkomen.
+Gebruik geen spaties.
+
+Dit veld wordt meestal leeg gelaten.",
+ 'config-db-charset' => 'Tekenset voor de database',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binair',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 UTF-8-compatibel',
+ 'config-charset-help' => "'''Waarschuwing:''' als u '''achterwaarts compatibel met UTF-8''' gebruikt met MySQL 4.1+ en een back-up van de database maakt met <code>mysqldump</code>, dan kunnen alle niet-ASCII-tekens in uw back-ups onherstelbaar beschadigd raken.
+
+In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.
+Dit is efficiënter dan de UTF-8-modus van MySQL en stelt u in staat de volledige reeks Unicode-tekens te gebruiken.
+In '''UTF-8-modus''' kent MySQL de tekenset van uw gegevens en kan de databaseserver ze juist weergeven en converteren.
+Het is dat niet mogelijk tekens op te slaan die de \"[http://nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
+ 'config-mysql-old' => 'U moet MySQL $1 of later gebruiken.
+U gebruikt $2.',
+ 'config-db-port' => 'Databasepoort:',
+ 'config-db-schema' => 'Schema voor MediaWiki',
+ 'config-db-schema-help' => 'Dit schema klopt meestal.
+Wijzig het alleen als u weet dat dit nodig is.',
+ 'config-sqlite-dir' => 'Gegevensmap voor SQLite:',
+ 'config-sqlite-dir-help' => "SQLite slaat alle gegevens op in een enkel bestand.
+
+De map die u opgeeft moet schrijfbaar zijn voor de webserver tijdens de installatie.
+
+Deze mag '''niet toegankelijk''' zijn via het web en het bestand mag dus niet tussen de PHP-bestanden staan.
+
+Het installatieprogramma schrijft het bestand <code>.htaccess</code> weg met het databasebestand, maar als dat niet werkt kan iemand zich toegang tot het ruwe databasebestand verschaffen.
+Ook de gebruikersgegevens (e-mailsadressen, wachtwoordhashes) en verwijderde versies en overige gegevens met beperkte toegang via MediaWiki zijn dan onbeschermd.
+
+Overweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld in <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Standaard tablespace:',
+ 'config-oracle-temp-ts' => 'Tijdelijke tablespace:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki ondersteunt de volgende databasesystemen:
+
+$1
+
+Als u het databasesysteem dat u wilt gebruiken niet in de lijst terugvindt, volg dan de handleiding waarnaar hierboven wordt verwezen om ondersteuning toe te voegen.',
+ 'config-support-mysql' => '* $1 is het primaire databasesysteem voor voor MediaWiki en wordt het best ondersteund ([http://www.php.net/manual/en/mysql.installation.php hoe PHP gecompileerd moet zijn met ondersteuning voor MySQL])',
+ 'config-support-postgres' => '* $1 is een populair open source databasesysteem als alternatief voor MySQL ([http://www.php.net/manual/en/pgsql.installation.php hoe PHP gecompileerd moet zijn met ondersteuning voor PostgreSQL])',
+ 'config-support-sqlite' => '* $1 is een zeer goed ondersteund lichtgewicht databasesysteem ([http://www.php.net/manual/en/pdo.installation.php hoe PHP gecompileerd zijn met ondersteuning voor SQLite]; gebruikt PDO)',
+ 'config-support-oracle' => '* $1 is een commerciële data voor grote bedrijven ([http://www.php.net/manual/en/oci8.installation.php PHP compileren met ondersteuning voor OCI8]).',
+ 'config-header-mysql' => 'MySQL-instellingen',
+ 'config-header-postgres' => 'PostgreSQL-instellingen',
+ 'config-header-sqlite' => 'SQLite-instellingen',
+ 'config-header-oracle' => 'Oracle-instellingen',
+ 'config-invalid-db-type' => 'Ongeldig databasetype',
+ 'config-missing-db-name' => 'U moet een waarde ingeven voor "Databasenaam"',
+ 'config-missing-db-host' => 'U moet een waarde invoeren voor "Databaseserver"',
+ 'config-missing-db-server-oracle' => 'U moet een waarde voor "Database-TNS" ingeven',
+ 'config-invalid-db-server-oracle' => 'Ongeldige database-TMS "$1".
+Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_).',
+ 'config-invalid-db-name' => 'Ongeldige databasenaam "$1".
+Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_) en streepjes (-).',
+ 'config-invalid-db-prefix' => 'Ongeldig databasevoorvoegsel "$1".
+Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_) en streepjes (-).',
+ 'config-connection-error' => '$1.
+
+Controleer de host, gebruikersnaam en wachtwoord hieronder in en probeer het opnieuw.',
+ 'config-invalid-schema' => 'Ongeldig schema voor MediaWiki "$1".
+Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_).',
+ 'config-db-sys-create-oracle' => 'Het installatieprogramma biedt alleen de mogelijkheid een nieuwe gebruiker aan te maken met de SYSDBA-gebruiker.',
+ 'config-db-sys-user-exists-oracle' => 'De gebruiker "$1" bestaat al. SYSDBA kan alleen gebruikt worden voor het aanmaken van een nieuwe gebruiker!',
+ 'config-postgres-old' => 'PostgreSQL $1 of hoger is vereist.
+U gebruikt $2.',
+ 'config-sqlite-name-help' => 'Kies een naam die uw wiki identificeert.
+Gebruik geen spaties of koppeltekens.
+Deze naam wordt gebruikt voor het gegevensbestands van SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Het was niet mogelijk de gegevensmap <code><nowiki>$1</nowiki></code> te maken omdat in de bovenliggende map <code><nowiki>$2</nowiki></code> niet geschreven mag worden door de webserver.
+
+Het installatieprogramma heeft vast kunnen stellen onder welke gebruiker de webserver draait.
+Maak de map <code><nowiki>$3</nowiki></code> beschrijfbaar om door te kunnen gaan.
+Voer op een Linux-systeem de volgende opdrachten uit:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Het was niet mogelijk de gegevensmap <code><nowiki>$1</nowiki></code> te maken omdat in de bovenliggende map <code><nowiki>$2</nowiki></code> niet geschreven mag worden door de webserver.
+
+Het installatieprogramma heeft niet vast kunnen stellen onder welke gebruiker de webserver draait.
+Maak de map <code><nowiki>$3</nowiki></code> beschrijfbaar voor de webserver (en anderen!) om door te kunnen gaan.
+Voer op een Linux-systeem de volgende opdrachten uit:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Er is een fout opgetreden bij het aanmaken van de gegevensmap "$1".
+Controleer de locatie en probeer het opnieuw.',
+ 'config-sqlite-dir-unwritable' => 'Het was niet mogelijk in de map "$1" te schrijven.
+Wijzig de rechten zodat de webserver erin kan schrijven en probeer het opnieuw.',
+ 'config-sqlite-connection-error' => '$1.
+
+Controleer de map voor gegevens en de databasenaam hieronder en probeer het opnieuw.',
+ 'config-sqlite-readonly' => 'Het bestand <code>$1</code> kan niet geschreven worden.',
+ 'config-sqlite-cant-create-db' => 'Het was niet mogelijk het databasebestand <code>$1</code> aan te maken.',
+ 'config-sqlite-fts3-downgrade' => 'PHP heeft geen ondersteuning voor FTS3.
+De tabellen worden gedowngrade.',
+ 'config-can-upgrade' => "Er staan al tabellen voor MediaWiki in deze database.
+Klik op '''Doorgaan''' om ze bij te werken naar MediaWiki $1.",
+ 'config-upgrade-done' => "Het bijwerken is afgerond.
+
+Uw kunt [$1 uw wiki nu gebruiken].
+
+Als u uw <code>LocalSettings.php</code> opnieuw wilt aanmaken, klik dan op de knop hieronder.
+Dit is '''niet aan te raden''' tenzij u problemen hebt met uw wiki.",
+ 'config-upgrade-done-no-regenerate' => 'Het bijwerken is afgerond.
+
+U kunt u [$1 uw wiki gebruiken].',
+ 'config-regenerate' => 'LocalSettings.php opnieuw aanmaken →',
+ 'config-show-table-status' => 'Het uitvoeren van SHOW TABLE STATUS is mislukt!',
+ 'config-unknown-collation' => "'''Waarschuwing:''' de database gebruikt een collatie die niet wordt herkend.",
+ 'config-db-web-account' => 'Databasegebruiker voor webtoegang',
+ 'config-db-web-help' => 'Selecteer de gebruikersnaam en het wachtwoord die de webserver gebruikt om verbinding te maken met de databaseserver na de installatie.',
+ 'config-db-web-account-same' => 'Dezelfde gebruiker gebruiken als voor de installatie',
+ 'config-db-web-create' => 'Maak de gebruiker aan als deze nog niet bestaat',
+ 'config-db-web-no-create-privs' => 'De gebruiker die u hebt opgegeven voor de installatie heeft niet voldoende rechten om een gebruiker aan te maken.
+De gebruiker die u hier opgeeft moet al bestaan.',
+ 'config-mysql-engine' => 'Opslagmethode:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' is vrijwel altijd de beste instelling, omdat deze goed omgaat met meerdere verzoeken tegelijkertijd.
+
+'''MyISAM''' is bij een zeer beperkt aantal gebruikers mogelijk sneller, of als de wiki alleen-lezen is.
+MyISAM-databases raken vaker corrupt dan InnoDB-databases.",
+ 'config-mysql-charset' => 'Tekenset voor de database:',
+ 'config-mysql-binary' => 'Binair',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "In '''binaire modus''' slaat MediaWiki tekst in UTF-8 op in binaire databasevelden.
+Dit is efficiënter dan de UTF-8-modus van MySQL en stelt u in staat de volledige reeks Unicode-tekens te gebruiken.
+
+In '''UTF-8-modus''' kent MySQL de tekenset van uw gegevens en kan de databaseserver ze juist weergeven en converteren.
+Het is dat niet mogelijk tekens op te slaan die de \"[http://nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
+ 'config-site-name' => 'Naam van de wiki:',
+ 'config-site-name-help' => 'Deze naam verschijnt in de titelbalk van browsers en op andere plaatsen.',
+ 'config-site-name-blank' => 'Geef een naam op voor de site.',
+ 'config-project-namespace' => 'Projectnaamruimte:',
+ 'config-ns-generic' => 'Project',
+ 'config-ns-site-name' => 'Zelfde als de wiki: $1',
+ 'config-ns-other' => 'Andere (geen aan welke)',
+ 'config-ns-other-default' => 'MijnWiki',
+ 'config-project-namespace-help' => "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een \"'''projectnaamruimte'''\".
+Alle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat u hier kunt aangeven.
+Dit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
+ 'config-ns-invalid' => 'De aangegeven naamruimte "<nowiki>$1</nowiki>" is ongeldig.
+Geef een andere naamruimte op.',
+ 'config-ns-conflict' => 'De aangegeven naamruimte "<nowiki>$1</nowiki>" conflicteert met een standaard naamruimte in MediaWiki.
+Geef een andere naam op voor de projectnaamruimte.',
+ 'config-admin-box' => 'Beheerdersgebruiker',
+ 'config-admin-name' => 'Uw naam:',
+ 'config-admin-password' => 'Wachtwoord:',
+ 'config-admin-password-confirm' => 'Wachtwoord opnieuw:',
+ 'config-admin-help' => 'Voer de gebruikersnaam hier in, bijvoorbeeld "Jan Jansen".
+Dit is de naam die wordt gebruikt om aan de melden bij de wiki.',
+ 'config-admin-name-blank' => 'Geef een gebruikersnaam op voor de beheerder.',
+ 'config-admin-name-invalid' => 'De opgegeven gebruikersnaam "<nowiki>$1</nowiki>" is ongeldig.
+Kies een andere gebruikersnaam.',
+ 'config-admin-password-blank' => 'Voer een wachtwoord voor de beheerder in.',
+ 'config-admin-password-same' => 'Het wachtwoord mag niet hetzelfde zijn als de gebruikersnaam.',
+ 'config-admin-password-mismatch' => 'De twee door u ingevoerde wachtwoorden komen niet overeen.',
+ 'config-admin-email' => 'E-mailadres:',
+ 'config-admin-email-help' => "Voer hier een e-mailadres in om e-mail te kunnen ontvangen van andere gebruikers op de wiki, uw wachtwoord opnieuw in te kunnen stellen en op de hoogte te worden gehouden van wijzigingen van pagina's op uw volglijst.",
+ 'config-admin-error-user' => 'Interne fout bij het aanmaken van een beheerder met de naam "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Interne fout bij het instellen van een wachtwoord voor de bejeerder "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'U hebt een ongeldig e-mailadres opgegeven',
+ 'config-subscribe' => 'Abonneren op de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailinglijst releaseaankondigen].',
+ 'config-subscribe-help' => 'Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.
+Abonneer uzelf erop en werk uw MediaWiki-installatie bij als er nieuwe versies uitkomen.',
+ 'config-almost-done' => 'U bent bijna klaar!
+Als u wilt kunt u de overige instellingen overslaan en de wiki nu installeren.',
+ 'config-optional-continue' => 'Stel me meer vragen.',
+ 'config-optional-skip' => 'Laat dat maar, installeer gewoon de wiki.',
+ 'config-profile' => 'Gebruikersrechtenprofiel:',
+ 'config-profile-wiki' => 'Traditionele wiki',
+ 'config-profile-no-anon' => 'Gebruiker aanmaken verplicht',
+ 'config-profile-fishbowl' => 'Alleen voor geautoriseerde bewerkers',
+ 'config-profile-private' => 'Privéwiki',
+ 'config-profile-help' => "Wiki's werken het beste als ze door zoveel mogelijk gebruikers worden bewerkt.
+In MediaWiki is het eenvoudig om de recente wijzigingen te controleren en eventuele foutieve of kwaadwillende bewerkingen terug te draaien.
+
+Daarnaast vinden velen MediaWiki goed inzetbaar in vele andere rollen, en soms is het niet handig om helemaal \"op de wikimanier\" te werken.
+Daarom biedt dit installatieprogramma u de volgende keuzes voor de basisinstelling van gebruikersvrijheden:
+
+Een '''{{int:config-profile-wiki}}''' staat iedereen toe te bewerken, zonder zelfs aan te melden.
+Een wiki met '''{{int:config-profile-no-anon}}\" biedt extra verantwoordelijkheid, maar kan afschrikken toevallige gebruikers afschrikken.
+
+Het scenario '''{{int:config-profile-fishbowl}}''' laat gebruikers waarvoor dat is ingesteld bewerkt, maar andere gebruikers kunnen alleen pagina's bekijken, inclusief de bewerkingsgeschiedenis.
+In een '''{{int:config-profile-private}}''' kunnen alleen goedgekeurde gebruikers pagina's bekijken en bewerken.
+
+Meer complexe instellingen voor gebruikersrechten zijn te maken na de installatie; hierover is meer te lezen in de [http://www.mediawiki.org/wiki/Manual:User_rights handleiding].",
+ 'config-license' => 'Auteursrechten en licentie:',
+ 'config-license-none' => 'Geen licentie in de voettekst',
+ 'config-license-cc-by-sa' => 'Creative Commons Naamsvermelding-Gelijk delen',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Naamsvermelding-Niet Commercieel-Gelijk delen',
+ 'config-license-cc-0' => 'Creative Commons Zero',
+ 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
+ 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 of hoger',
+ 'config-license-pd' => 'Publiek domein',
+ 'config-license-cc-choose' => 'Een Creative Commons-licentie selecteren',
+ 'config-license-help' => "In veel openbare wiki's zijn alle bijdragen beschikbaar onder een [http://freedomdefined.org/Definition vrije licentie].
+Dit helpt bij het creëren van een gevoel van gemeenschappelijk eigendom en stimuleert bijdragen op lange termijn.
+Dit is over het algemeen niet nodig is voor een particuliere of zakelijke wiki.
+
+Als u teksten uit Wikipedia wilt kunnen gebruiken en u wilt het mogelijk maken teksten uit uw wiki naar Wikipedia te kopiëren, kies dan de licentie '''Creative Commons Naamsvermelding-Gelijk delen'''.
+
+De GNU Free Documentation License was de oude licentie voor inhoud uit Wikipedia.
+Dit is nog steeds een geldige licentie, maar deze licentie heeft een aantal eigenschappen die hergebruik en interpretatie lastig kunnen maken.",
+ '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 [http://www.php.net/manual/en/mail.configuration.php PHP's e-mailinstellingen] correct zijn.
+Als u niet wilt dat e-mailen mogelijk is, dan kunt u de instellingen hier uitschakelen.",
+ 'config-email-user' => 'E-mail tussen gebruikers inschakelen',
+ 'config-email-user-help' => 'Gebruikers toestaan e-mail aan elkaar te verzenden als dit in de voorkeuren is ingesteld.',
+ 'config-email-usertalk' => 'Gebruikersoverlegnotificatie inschakelen',
+ 'config-email-usertalk-help' => 'Gebruikers toestaan notificaties te ontvangen bij wijzigingen op de eigen overlegpagina als dit in de voorkeuren is ingesteld',
+ 'config-email-watchlist' => 'Volglijstnotificatie inschakelen',
+ 'config-email-watchlist-help' => "Gebruikers toestaan notificaties te ontvangen bij wijzigingen van pagina's op hun volglijst als dit in de voorkeuren is ingesteld",
+ 'config-email-auth' => 'E-mailbevestiging inschakelen',
+ 'config-email-auth-help' => "Als deze instelling actief is, moeten gebruikers hun e-mailadres bevestigen via een verwijziging die ze per e-mail wordt toegezonden.
+Alleen bevestigde e-mailadressen kunnen e-mail ontvangen van andere gebruikers of wijzigingsnotificaties ontvangen.
+Het inschakelen van deze instelling is '''aan te raden''' voor openbare wiki's vanwege de mogelijkheden voor misbruik van e-mailmogelijkheden.",
+ 'config-email-sender' => 'E-mailadres voor antwoorden:',
+ 'config-email-sender-help' => 'Voer het e-mailadres in dat u wilt gebruiken als antwoordadres voor uitgaande e-mail.
+Als een e-mail niet bezorgd kan worden, wordt dat op dit e-mailadres gemeld.
+Veel mailservers vereisen dat tenminste het domein bestaat.',
+ 'config-upload-settings' => 'Afbeeldingen en bestanden uploaden',
+ 'config-upload-enable' => 'Uploaden van bestanden inschakelen',
+ 'config-upload-help' => "Het uploaden van bestanden stelt uw server mogelijk bloot aan beveiligingsrisico's.
+Er is meer [http://www.mediawiki.org/wiki/Manual:Security informatie over beveiliging] beschikbaar in de handleiding.
+
+Om het bestandsuploads mogelijk te maken kunt u de rechten op de submap <code>images</code> onder de hoofdmap van MediaWiki aanpassen, zodat de webserver erin kan schrijven.
+Daarmee wordt deze functie ingeschakeld.",
+ 'config-upload-deleted' => 'Map voor verwijderde bestanden:',
+ 'config-upload-deleted-help' => 'Kies een map waarin verwijderde bestanden gearchiveerd kunnen worden.
+Idealiter is deze map niet via het web te benaderen.',
+ 'config-logo' => 'URL voor logo:',
+ 'config-logo-help' => 'Het standaarduiterlijk van MediaWiki bevat ruimte voor een logo van 135x160 pixels in de linker bovenhoek.
+Upload een afbeelding met de juiste afmetingen en voer de URL hier in.
+
+Als u geen logo wilt gebruiken, kunt u dit veld leeg laten.',
+ 'config-instantcommons' => 'Instant Commons inschakelen',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] is functie die het mogelijk maakt om afbeeldingen, geluidsbestanden en andere mediabestanden te gebruiken van de website [http://commons.wikimedia.org/ Wikimedia Commons].
+Hiervoor heeft MediaWiki toegang nodig tot Internet.
+
+Meer informatie over deze functie en hoe deze in te stellen voor andere wiki\'s dan Wikimedia Commons is te vinden in de [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos handleiding].',
+ 'config-cc-error' => 'De licentiekiezer van Creative Commons heeft geen resultaat opgeleverd.
+Voer de licentie handmatig in.',
+ 'config-cc-again' => 'Opnieuw kiezen...',
+ 'config-cc-not-chosen' => 'Kies alstublieft de Creative Commons-licentie die u wilt gebruiken en klik op "doorgaan".',
+ 'config-advanced-settings' => 'Gevorderde instellingen',
+ 'config-cache-options' => 'Instellingen voor het cachen van objecten:',
+ 'config-cache-help' => 'Het cachen van objecten wordt gebruikt om de snelheid van MediaWiki te verbeteren door vaak gebruikte gegevens te bewaren.
+Middelgrote tot grote websites wordt geadviseerd dit in te schakelen en ook kleine sites merken de voordelen.',
+ 'config-cache-none' => 'Niets cachen.
+Er gaat geen functionaliteit verloren, maar dit kan invloed hebben op de snelheid.',
+ 'config-cache-accel' => 'Cachen van objecten via PHP (APC, eAccelerator, XCache of WinCache)',
+ 'config-cache-memcached' => 'Memcached gebruiken (dit vereist aanvullende instellingen)',
+ 'config-memcached-servers' => 'Memcachedservers:',
+ 'config-memcached-help' => 'Lijst met IP-adressen te gebruiken voor Memcached.
+Eén IP-adres per regel met een poortnummer.
+Bijvoorbeeld:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'U hebt Memcached geselecteerd als uw cache, maar u hebt geen servers opgegeven.',
+ 'config-memcache-badip' => 'U hebt een ongeldig IP-adres ingevoerd voor Memcached: $1.',
+ 'config-memcache-noport' => 'U hebt geen poort opgegeven voor de Memcachedserver: $1.
+De standaardpoort is 11211.',
+ 'config-memcache-badport' => 'Poortnummers voor Memcached moeten tussen $1 en $2 liggen.',
+ 'config-extensions' => 'Uitbreidingen',
+ 'config-extensions-help' => 'De bovenstaande uitbreidingen zijn aangetroffen in de map <code>./extensions</code>.
+
+Mogelijk moet u aanvullende instellingen maken, maar u kunt deze uitbreidingen nu inschakelen.',
+ 'config-install-alreadydone' => "'''Waarschuwing:''' het lijkt alsof u MediaWiki al hebt geïnstalleerd en probeert het programma opnieuw te installeren.
+Ga alstublieft door naar de volgende pagina.",
+ 'config-install-begin' => 'Als u nu op "{{int:config-continue}}" klikt, begint de installatie van MediaWiki.
+Als u nog wijzigingen wilt maken, klik dan op "Terug".',
+ 'config-install-step-done' => 'Afgerond',
+ 'config-install-step-failed' => 'Mislukt',
+ 'config-install-extensions' => 'Inclusief uitbreidingen',
+ 'config-install-database' => 'Database inrichten',
+ 'config-install-pg-schema-not-exist' => 'Het schema voor PostgreSQL bestaat niet',
+ 'config-install-pg-schema-failed' => 'Het aanmaken van de tabellen is mislukt.
+Zorg dat de gebruiker "$1" in het schema "$2" mag schrijven.',
+ 'config-install-pg-commit' => 'Wijzigingen worden doorgevoerd',
+ 'config-install-pg-plpgsql' => 'Controle op de taal PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'U moet de taal PL/pgSQL installeren in de database $1',
+ 'config-pg-no-create-privs' => 'De gebruiker die u hebt opgegeven door de installatie heeft niet voldoende rechten om een gebruiker aan te maken.',
+ 'config-install-user' => 'Databasegebruiker aan het aanmaken',
+ 'config-install-user-alreadyexists' => 'Gebruiker "$1" bestaat al',
+ 'config-install-user-create-failed' => 'Het aanmaken van de gebruiker "$1" is mislukt: $2',
+ 'config-install-user-grant-failed' => 'Het geven van rechten aan gebruiker "$1" is mislukt: $2',
+ 'config-install-tables' => 'Tabellen aanmaken',
+ 'config-install-tables-exist' => "'''Waarschuwing''': de MediaWiki-tabellen lijken al te bestaan.
+Het aanmaken wordt overgeslagen.",
+ 'config-install-tables-failed' => "'''Fout''': het aanmaken van een tabel is mislukt met de volgende foutmelding: $1",
+ 'config-install-interwiki' => 'Bezig met het vullen van de interwikitabel',
+ 'config-install-interwiki-list' => 'Het bestand <code>interwiki.list</code> is niet aangetroffen',
+ 'config-install-interwiki-exists' => "'''Waarschuwing''': de interwikitabel heeft al inhoud.
+De standaardlijst wordt overgeslagen.",
+ 'config-install-stats' => 'Statistieken initialiseren',
+ 'config-install-keys' => 'Geheime sleutel aanmaken',
+ 'config-install-sysop' => 'Gebruiker voor beheerder aanmaken',
+ 'config-install-subscribe-fail' => 'Het is niet mogelijk te abonneren op mediawiki-announce',
+ 'config-install-mainpage' => 'Hoofdpagina aanmaken met standaard inhoud',
+ 'config-install-extension-tables' => 'Tabellen voor ingeschakelde uitbreidingen worden aangemaakt',
+ 'config-install-mainpage-failed' => 'Het was niet mogelijk de hoofdpagina in te voegen: $1',
+ 'config-install-done' => "'''Gefeliciteerd!'''
+U hebt MediaWiki met geïnstalleerd.
+
+Het installatieprogramma heeft het bestand <code>LocalSettings.php</code> aangemaakt.
+Dit bevat al uw instellingen.
+
+U moet het bestand downloaden en in de hoofdmap van uw wiki-installatie plaatsten; in dezelfde map als index.php.
+De download moet u automatisch zijn aangeboden.
+
+Als de download niet is aangeboden of als u de download hebt geannuleerd, dan kunt u de download opnieuw starten door op de onderstaande verwijzing te klikken:
+
+$3
+
+'''Let op''': als u dit niet nu doet, dan het is bestand als u later de installatieprocedure afsluit zonder het bestand te downloaden niet meer beschikbaar.
+
+Na het plaatsen van het bestand met instellingen kunt u '''[$2 uw wiki betreden]'''.",
+ 'config-download-localsettings' => 'LocalSettings.php downloaden',
+ 'config-help' => 'hulp',
+);
+
+/** Norwegian Nynorsk (‪Norsk (nynorsk)‬)
+ * @author Nghtwlkr
+ */
+$messages['nn'] = array(
+ 'config-your-language' => 'Språket ditt:',
+ 'config-wiki-language' => 'Wikispråk:',
+ 'config-back' => '← Attende',
+ 'config-continue' => 'Hald fram →',
+ 'config-page-language' => 'Språk',
+ 'config-memory-raised' => 'PHPs <code>memory_limit</code> er $1, auka til $2.',
+ 'config-memory-bad' => "'''Advarsel:''' PHPs <code>memory_limit</code> er $1.
+Dette er sannsynlegvis for lågt.
+Installasjonen kan mislukkast!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] er innstallert',
+ 'config-apc' => '[http://www.php.net/apc APC] er innstallert',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] er innstallert',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] er installert',
+ 'config-db-name' => 'Databasenamn:',
+ 'config-db-username' => 'Databasebrukarnamn:',
+ 'config-db-password' => 'Databasepassord:',
+ 'config-db-charset' => 'Databaseteiknsett',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binær',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 bakoverkompatibel UTF-8',
+ 'config-mysql-old' => 'MySQL $1 eller seinare krevst, du har $2.',
+ 'config-db-port' => 'Databaseport:',
+ 'config-db-schema' => 'Skjema for MediaWiki',
+ 'config-header-mysql' => 'MySQL-innstillingar',
+ 'config-header-postgres' => 'PostgreSQL-innstillingar',
+ 'config-header-sqlite' => 'SQLite-innstillingar',
+ 'config-header-oracle' => 'Oracle-innstillingar',
+ 'config-invalid-db-type' => 'Ugyldig databasetype',
+ 'config-invalid-db-name' => 'Ugyldig databasenamn «$1».
+Berre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).',
+ 'config-invalid-db-prefix' => 'Ugyldig databaseprefiks «$1».
+Berre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).',
+ 'config-invalid-schema' => 'Ugyldig skjema for MediaWiki «$1».
+Berre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).',
+ 'config-postgres-old' => 'PostgreSQL $1 eller seinare krevst, du har $2.',
+ 'config-email-settings' => 'E-postinnstillingar',
+ 'config-logo' => 'Logo-URL:',
+);
+
+/** Norwegian (bokmål)‬ (‪Norsk (bokmål)‬)
+ * @author Jon Harald Søby
+ * @author Nghtwlkr
+ */
+$messages['no'] = array(
+ 'config-desc' => 'Installasjonsprogrammet for MediaWiki',
+ 'config-title' => 'Installasjon av MediaWiki $1',
+ 'config-information' => 'Informasjon',
+ 'config-localsettings-upgrade' => "'''Advarsel''': En <code>LocalSettings.php</code>-fil har blitt oppdaget.
+Programvaren kan oppgraderes.
+Flytt <code>LocalSettings.php</code> til et trygt sted og kjør installasjonsprogrammet på nytt.",
+ 'config-session-error' => 'Feil under oppstart av økt: $1',
+ 'config-session-expired' => 'Dine øktdata ser ut til å ha utløpt.
+Økter er konfigurert for en levetid på $1.
+Du kan øke dette ved å sette <code>session.gc_maxlifetime</code> i php.ini.
+Start installasjonsprosessen på nytt.',
+ 'config-no-session' => 'Dine øktdata ble tapt!
+Sjekk din php.ini og sørg for at <code>session.save_path</code> er satt til en passende mappe.',
+ 'config-your-language' => 'Ditt språk:',
+ 'config-your-language-help' => 'Velg et språk å bruke under installasjonsprosessen.',
+ 'config-wiki-language' => 'Wikispråk:',
+ 'config-wiki-language-help' => 'Velg språket som wikien hovedsakelig vil bli skrevet i.',
+ 'config-back' => '← Tilbake',
+ 'config-continue' => 'Fortsett →',
+ 'config-page-language' => 'Språk',
+ 'config-page-welcome' => 'Velkommen til MediaWiki!',
+ 'config-page-dbconnect' => 'Koble til database',
+ 'config-page-upgrade' => 'Oppgrader eksisterende innstallasjon',
+ 'config-page-dbsettings' => 'Databaseinnstillinger',
+ 'config-page-name' => 'Navn',
+ 'config-page-options' => 'Valg',
+ 'config-page-install' => 'Innstaller',
+ 'config-page-complete' => 'Ferdig!',
+ 'config-page-restart' => 'Start installasjonen på nytt',
+ 'config-page-readme' => 'Les meg',
+ 'config-page-releasenotes' => 'Utgivelsesnotat',
+ 'config-page-copying' => 'Kopiering',
+ 'config-page-upgradedoc' => 'Oppgradering',
+ 'config-help-restart' => 'Ønsker du å fjerne alle lagrede data som du har skrevet inn og starte installasjonsprosessen på nytt?',
+ 'config-restart' => 'Ja, start på nytt',
+ 'config-welcome' => '=== Miljøsjekker ===
+Grunnleggende sjekker utføres for å se om dette miljøet er egnet for en MediaWiki-installasjon.
+Du bør oppgi resultatene fra disse sjekkene om du trenger hjelp under installasjonen.',
+ 'config-copyright' => "=== Opphavsrett og vilkår ===
+
+$1
+
+MediaWiki er fri programvare; du kan redistribuere det og/eller modifisere det under betingelsene i GNU General Public License som publisert av Free Software Foundation; enten versjon 2 av lisensen, eller (etter eget valg) enhver senere versjon.
+
+Dette programmet er distribuert i håp om at det vil være nyttig, men '''uten noen garanti'''; ikke engang implisitt garanti av '''salgbarhet''' eller '''egnethet for et bestemt formål'''.
+Se GNU General Public License for flere detaljer.
+
+Du skal ha mottatt <doclink href=Copying>en kopi av GNU General Public License</doclink> sammen med dette programmet; hvis ikke, skriv til Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA eller [http://www.gnu.org/copyleft/gpl.html les det på nettet].",
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWiki hjem]
+* [http://www.mediawiki.org/wiki/Help:Contents Brukerguide]
+* [http://www.mediawiki.org/wiki/Manual:Contents Administratorguide]
+* [http://www.mediawiki.org/wiki/Manual:FAQ OSS]',
+ 'config-env-good' => 'Miljøet har blitt sjekket.
+Du kan installere MediaWiki.',
+ 'config-env-bad' => 'Miljøet har blitt sjekket.
+Du kan installere MediaWiki.',
+ 'config-env-php' => 'PHP $1 er innstallert.',
+ 'config-unicode-using-utf8' => 'Bruker Brion Vibbers utf8_normalize.so for Unicode-normalisering.',
+ 'config-unicode-using-intl' => 'Bruker [http://pecl.php.net/intl intl PECL-utvidelsen] for Unicode-normalisering.',
+ 'config-unicode-pure-php-warning' => "'''Advarsel''': [http://pecl.php.net/intl intl PECL-utvidelsen] er ikke tilgjengelig for å håndtere Unicode-normaliseringen.
+Om du kjører et høy trafikksnettsted bør du lese litt om [http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-normalisering].",
+ 'config-unicode-update-warning' => "'''Advarsel''': Den installerte versjonen av Unicode-normalisereren bruker en eldre versjon av [http://site.icu-project.org/ ICU-prosjektets] bibliotek.
+Du bør [http://www.mediawiki.org/wiki/Unicode_normalization_considerations oppgradere] om du er bekymret for å bruke Unicode.",
+ 'config-no-db' => 'Fant ikke en passende databasedriver!',
+ 'config-no-db-help' => 'Du må installere en databasedriver for PHP.
+Følgende databasetyper er støttet: $1.
+
+Om du er på delt tjener, spør din tjenerleverandør om å installere en passende databasedriver.
+Om du kompilerte PHP selv, rekonfigirer den med en aktivert databaseklient, for eksempel ved å bruke <code>./configure --with-mysql</code>.
+Om du installerte PHP fra en Debian eller Ubuntu-pakke må du også installere modulen php5-mysql.',
+ 'config-no-fts3' => "'''Advarsel''': SQLite er kompilert uten [http://sqlite.org/fts3.html FTS3-modulen], søkefunksjoner vil ikke være tilgjengelig på dette bakstykket.",
+ 'config-register-globals' => "'''Advarsel: PHPs <code>[http://php.net/register_globals register_globals]</code>-alternativ er aktivert.'''
+'''Deaktiver det om du kan.'''
+MediaWiki vil fungere, men tjeneren din er utsatt for potensielle sikkerhetssårbarheter.",
+ 'config-magic-quotes-runtime' => "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] er aktiv!'''
+Dette alternativet ødelegger inndata på en uforutsigbar måte.
+Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ 'config-magic-quotes-sybase' => "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] er aktiv!'''
+Dette alternativet ødelegger inndata på en uforutsigbar måte.
+Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ 'config-mbstring' => "'''Kritisk: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] er aktiv!'''
+Dette alternativet fører til feil og kan ødelegge data på en uforutsigbar måte.
+Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ 'config-ze1' => "'''Kritisk: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] er aktiv!'''
+Dette alternativet fører til horrible feil med MediaWiki.
+Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ 'config-safe-mode' => "'''Advarsel:''' PHPs [http://www.php.net/features.safe-mode safe mode] er aktiv.
+Det kan føre til problem, spesielt hvis du bruker støtte for filopplastinger og <code>math</code>.",
+ 'config-xml-bad' => 'PHPs XML-modul mangler.
+MediaWiki krever funksjonene i denne modulen og vil ikke virke i denne konfigurasjonen.
+Hvis du kjører Mandrak, installer pakken php-xml.',
+ 'config-pcre' => 'PCRE-støttemodulen ser ut til å mangle.
+MediaWiki krever funksjonene for de Perl-kompatible regulære uttrykkene for å virke.',
+ 'config-memory-raised' => 'PHPs <code>memory_limit</code> er $1, økt til $2.',
+ 'config-memory-bad' => "'''Advarsel:''' PHPs <code>memory_limit</code> er $1.
+Dette er sannsynligvis for lavt.
+Installasjonen kan mislykkes!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] er innstallert',
+ 'config-apc' => '[http://www.php.net/apc APC] er innstallert',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] er innstallert',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] er installert',
+ 'config-no-cache' => "'''Advarsel:''' Kunne ikke finne [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].
+Objekthurtiglagring er ikke aktivert.",
+ 'config-diff3-bad' => 'GNU diff3 ikke funnet.',
+ 'config-imagemagick' => 'Fant ImageMagick: <code>$1</code>.
+Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
+ 'config-gd' => 'Fant innebygd GD-grafikkbibliotek.
+Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
+ 'config-no-scaling' => 'Kunne ikke finne GD-bibliotek eller ImageMagick.
+Bildeminiatyrisering vil være deaktivert.',
+ 'config-no-uri' => "'''Feil:''' Kunne ikke bestemme gjeldende URI.
+Installasjon avbrutt.",
+ 'config-uploads-not-safe' => "'''Advarsel:''' Din standardmappe for opplastinger <code>$1</code> er sårbar for kjøring av vilkårlige skript.
+Selv om MediaWiki sjekker alle opplastede filer for sikkerhetstrusler er det sterkt anbefalt å [http://www.mediawiki.org/wiki/Manual:Security#Upload_security lukke denne sikkerhetssårbarheten] før du aktiverer opplastinger.",
+ 'config-db-type' => 'Databasetype:',
+ 'config-db-host' => 'Databasevert:',
+ 'config-db-host-help' => 'Hvis databasetjeneren er på en annen tjener, skriv inn vertsnavnet eller IP-adressen her.
+
+Hvis du bruker en delt nettvert bør verten din oppgi det korrekte vertsnavnet i deres dokumentasjon.
+
+Hvis du installerer på en Windowstjener og bruker MySQL kan det hende at «localhost» ikke virker som tjenernavnet. Hvis ikke, prøv «127.0.0.1» for den lokale IP-adressen.',
+ 'config-db-host-oracle' => 'Database TNS:',
+ 'config-db-host-oracle-help' => 'Skriv inn et gyldig [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; en tnsnames.ora-fil må være synlig for installasjonsprosessen.<br />Hvis du bruker klientbibliotek 10g eller nyere kan du også bruke navngivingsmetoden [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Identifiser denne wikien',
+ 'config-db-name' => 'Databasenavn:',
+ 'config-db-name-help' => 'Velg et navn som identifiserer wikien din.
+Det bør ikke inneholde mellomrom eller bindestreker.
+
+Hvis du bruker en delt nettvert vil verten din enten gi deg et spesifikt databasenavn å bruke, eller la deg opprette databaser via kontrollpanelet.',
+ 'config-db-name-oracle' => 'Databaseskjema:',
+ 'config-db-install-account' => 'Brukerkonto for installasjon',
+ 'config-db-username' => 'Databasebrukernavn:',
+ 'config-db-password' => 'Databasepassord:',
+ 'config-db-install-help' => 'Skriv inn brukernavnet og passordet som vil bli brukt for å koble til databasen under installasjonsprosessen.',
+ 'config-db-account-lock' => 'Bruk det samme brukernavnet og passordet under normal drift',
+ 'config-db-wiki-account' => 'Brukerkonto for normal drift',
+ 'config-db-wiki-help' => 'Skriv inn brukernavnet og passordet som vil bli brukt til å koble til databasen under normal wikidrift.
+Hvis kontoen ikke finnes, og installasjonskontoen har tilstrekkelige privilegier, vil denne brukerkontoen bli opprettet med et minimum av privilegier, tilstrekkelig for å operere wikien.',
+ 'config-db-prefix' => 'Databasetabellprefiks:',
+ 'config-db-prefix-help' => 'Hvis du trenger å dele en database mellom flere wikier, eller mellom MediaWiki og andre nettapplikasjoner, kan du velge å legge til et prefiks til alle tabellnavnene for å unngå konflikter.
+Ikke bruk mellomrom eller bindestreker.
+
+Dette feltet er vanligvis tomt.',
+ 'config-db-charset' => 'Databasetegnsett',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binær',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 bakoverkompatibel UTF-8',
+ 'config-charset-help' => "'''Advarsel:''' Hvis du bruker '''bakoverkompatibel UTF-8''' på MySQL 4.1+, og deretter sikkerhetskopierer databasen med <code>mysqldump</code> kan det ødelegge alle ikke-ASCII tegn og irreversibelt ødelegge dine sikkerhetskopier!
+
+I '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.
+Dette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.
+I '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,
+men det vil ikke la deg lagre tegn over «[http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
+ 'config-mysql-old' => 'MySQL $1 eller senere kreves, du har $2.',
+ 'config-db-port' => 'Databaseport:',
+ 'config-db-schema' => 'Skjema for MediaWiki',
+ 'config-db-schema-help' => 'Ovennevnte skjema er som regel riktig.
+Bare endre dem hvis du vet at du trenger det.',
+ 'config-sqlite-dir' => 'SQLite datamappe:',
+ 'config-sqlite-dir-help' => "SQLite lagrer alle data i en enkelt fil.
+
+Mappen du oppgir må være skrivbar for nettjeneren under installasjonen.
+
+Den bør '''ikke''' være tilgjengelig fra nettet, dette er grunnen til at vi ikke legger det der PHP-filene dine er.
+
+Installasjonsprogrammet vil skrive en <code>.htaccess</code>-fil sammen med det, men om det mislykkes kan noen få tilgang til din råe database. Dette inkluderer rå brukerdata (e-postadresser, hashede passord) samt slettede revisjoner og andre begrensede data på wikien.
+
+Vurder å plassere databasen et helt annet sted, for eksempel i <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Standard tabellrom:',
+ 'config-oracle-temp-ts' => 'Midlertidig tabellrom:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki støtter følgende databasesystem:
+
+$1
+
+Hvis du ikke ser databasesystemet du prøver å bruke i listen nedenfor, følg instruksjonene det er lenket til over for å aktivere støtte.',
+ 'config-support-mysql' => '* $1 er det primære målet for MediaWiki og er best støttet ([http://www.php.net/manual/en/mysql.installation.php hvordan kompilere PHP med MySQL-støtte])',
+ 'config-support-postgres' => '* $1 er et populært åpen kildekode-databasesystem som er et alternativ til MySQL ([http://www.php.net/manual/en/pgsql.installation.php hvordan kompilere PHP med PostgreSQL-støtte])',
+ 'config-support-sqlite' => '* $1 er et lettvekts-databasesystem som er veldig godt støttet. ([http://www.php.net/manual/en/pdo.installation.php hvordan kompilere PHP med SQLite-støtte], bruker PDO)',
+ 'config-support-oracle' => '* $1 er en kommersiell bedriftsdatabase. ([http://www.php.net/manual/en/oci8.installation.php Hvordan kompilere PHP med OCI8-støtte])',
+ 'config-header-mysql' => 'MySQL-innstillinger',
+ 'config-header-postgres' => 'PostgreSQL-innstillinger',
+ 'config-header-sqlite' => 'SQLite-innstillinger',
+ 'config-header-oracle' => 'Oracle-innstillinger',
+ 'config-invalid-db-type' => 'Ugyldig databasetype',
+ 'config-missing-db-name' => 'Du må skrive inn en verdi for «Databasenavn»',
+ 'config-missing-db-server-oracle' => 'Du må skrive inn en verdi for «Database TNS»',
+ 'config-invalid-db-server-oracle' => 'Ugyldig database-TNS «$1».
+Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_) og punktum (.).',
+ 'config-invalid-db-name' => 'Ugyldig databasenavn «$1».
+Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).',
+ 'config-invalid-db-prefix' => 'Ugyldig databaseprefiks «$1».
+Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).',
+ 'config-connection-error' => '$1.
+
+Sjekk verten, brukernavnet og passordet nedenfor og prøv igjen.',
+ 'config-invalid-schema' => 'Ugyldig skjema for MediaWiki «$1».
+Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).',
+ 'config-postgres-old' => 'PostgreSQL $1 eller senere kreves, du har $2.',
+ 'config-sqlite-name-help' => 'Velg et navn som identifiserer wikien din.
+Ikke bruk mellomrom eller bindestreker.
+Dette vil bli brukt til SQLite-datafilnavnet.',
+ 'config-sqlite-parent-unwritable-group' => 'Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.
+
+Installasjonsprogrammet har bestemt brukeren nettjeneren din kjører som.
+Gjør <code><nowiki>$3</nowiki></code>-mappen skrivbar for denne for å fortsette.
+På et Unix/Linux-system, gjør:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.
+
+Installasjonsprogrammet kunne ikke bestemme brukeren nettjeneren din kjører som.
+Gjør <code><nowiki>$3</nowiki></code>-mappen globalt skrivbar for denne (og andre!) for å fortsette.
+På et Unix/Linux-system, gjør:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Feil under oppretting av datamappen «$1».
+Sjekk plasseringen og prøv igjen.',
+ 'config-sqlite-dir-unwritable' => 'Kan ikke skrive til mappen «$1».
+Endre dens tilganger slik at nettjeneren kan skrive til den og prøv igjen.',
+ 'config-sqlite-connection-error' => '$1.
+
+Sjekk datamappen og databasenavnet nedenfor og prøv igjen.',
+ 'config-sqlite-readonly' => 'Filen <code>$1</code> er ikke skrivbar.',
+ 'config-sqlite-cant-create-db' => 'Kunne ikke opprette databasefilen <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'PHP mangler FTS3-støtte, nedgraderer tabeller',
+ 'config-can-upgrade' => "Det er MediaWiki-tabeller i denne databasen.
+For å oppgradere dem til MediaWiki $1, klikk '''Fortsett'''.",
+ 'config-upgrade-done' => "Oppgradering fullført.
+
+Du kan nå [$1 begynne å bruke wikien din].
+
+Hvis du ønsker å regenerere <code>LocalSettings.php</code>-filen din, klikk på knappen nedenfor.
+Dette er '''ikke anbefalt''' med mindre du har problemer med wikien din.",
+ 'config-regenerate' => 'Regenerer LocalSettings.php →',
+ 'config-show-table-status' => 'SHOW TABLE STATUS etterspørselen mislyktes!',
+ 'config-unknown-collation' => "'''Advarsel:''' Databasen bruker en ukjent sortering.",
+ 'config-db-web-account' => 'Databasekonto for nettilgang',
+ 'config-db-web-help' => 'Velg brukernavnet og passordet som nettjeneren skal bruke for å koble til databasetjeneren under ordinær drift av wikien.',
+ 'config-db-web-account-same' => 'Bruk samme konto som for installasjonen',
+ 'config-db-web-create' => 'Opprett kontoen om den ikke finnes allerede',
+ 'config-db-web-no-create-privs' => 'Kontoen du oppga for installasjonen har ikke nok privilegier til å opprette en konto.
+Kontoen du oppgir her må finnes allerede.',
+ 'config-mysql-engine' => 'Lagringsmotor:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' er nesten alltid det beste alternativet siden den har god støtte for samtidighet («concurrency»).
+
+'''MyISAM''' kan være raskere i enbruker- eller les-bare-installasjoner.
+MyISAM-databaser har en tendens til å bli ødelagt oftere enn InnoDB-databaser.",
+ 'config-mysql-charset' => 'Databasetegnsett:',
+ 'config-mysql-binary' => 'Binær',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "I '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.
+Dette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.
+
+I '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,
+men det vil ikke la deg lagre tegn over «[http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
+ 'config-site-name' => 'Navn på wiki:',
+ 'config-site-name-help' => 'Dette vil vises i tittellinjen i nettleseren og diverse andre steder.',
+ 'config-site-name-blank' => 'Skriv inn et nettstedsnavn.',
+ 'config-project-namespace' => 'Prosjektnavnerom:',
+ 'config-ns-generic' => 'Prosjekt',
+ 'config-ns-site-name' => 'Samme som wikinavnet: $1',
+ 'config-ns-other' => 'Annet (spesifiser)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-project-namespace-help' => "Etter Wikipedias eksempel holder mange wikier deres sider med retningslinjer atskilt fra sine innholdssider, i et «'''prosjektnavnerom'''».
+Alle sidetitler i dette navnerommet starter med et gitt prefiks som du kan angi her.
+Tradisjonelt er dette prefikset avledet fra navnet på wikien, men det kan ikke innholde punkttegn som «#» eller «:».",
+ 'config-ns-invalid' => 'Det angitte navnerommet «<nowiki>$1</nowiki>» er ugyldig.
+Angi et annet prosjektnavnerom.',
+ 'config-admin-box' => 'Administratorkonto',
+ 'config-admin-name' => 'Ditt navn:',
+ 'config-admin-password' => 'Passord:',
+ 'config-admin-password-confirm' => 'Passord igjen:',
+ 'config-admin-help' => 'Skriv inn ditt ønskede brukernavn her, for eksempel «Joe Bloggs».
+Dette er navnet du vil bruke for å logge inn på denne wikien.',
+ 'config-admin-name-blank' => 'Skriv inn et administratorbrukernavn.',
+ 'config-admin-name-invalid' => 'Det angitte brukernavnet «<nowiki>$1</nowiki>» er ugyldig.
+Angi et annet brukernavn.',
+ 'config-admin-password-blank' => 'Skriv inn et passord for administratorkontoen.',
+ 'config-admin-password-same' => 'Passordet skal ikke være det samme som brukernavnet.',
+ 'config-admin-password-mismatch' => 'De to passordene du skrev inn samsvarte ikke.',
+ 'config-admin-email' => 'E-postadresse:',
+ 'config-admin-email-help' => 'Skriv inn en e-postadresse her for at du skal kunne motta e-post fra andre brukere på wikien, tilbakestille passordet ditt, og bli varslet om endringer på sider på overvåkningslisten din.',
+ 'config-admin-error-user' => 'Intern feil ved opprettelse av en admin med navnet «<nowiki>$1</nowiki>».',
+ 'config-admin-error-password' => 'Intern feil ved opprettelse av passord for admin «<nowiki>$1</nowiki>»: <pre>$2</pre>',
+ 'config-subscribe' => 'Abonner på [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-postlisten for utgivelsesannonseringer].',
+ 'config-subscribe-help' => 'Dette er en lav-volums e-postliste brukt til utgivelsesannonseringer, herunder viktige sikkerhetsannonseringer.
+Du bør abonnere på den og oppdatere MediaWikiinstallasjonen din når nye versjoner kommer ut.',
+ 'config-almost-done' => 'Du er nesten ferdig!
+Du kan hoppe over de resterende konfigurasjonene og installere wikien nå.',
+ 'config-optional-continue' => 'Spør meg flere spørsmål.',
+ 'config-optional-skip' => 'Jeg er lei, bare installer wikien.',
+ 'config-profile' => 'Brukerrettighetsprofil:',
+ 'config-profile-wiki' => 'Tradisjonell wiki',
+ 'config-profile-no-anon' => 'Kontoopprettelse påkrevd',
+ 'config-profile-fishbowl' => 'Kun autoriserte bidragsytere',
+ 'config-profile-private' => 'Privat wiki',
+ 'config-profile-help' => "Wikier fungerer best når du lar så mange mennesker som mulig redigere den.
+I MediaWiki er det lett å revidere siste endringer og tilbakestille eventuell skade som er gjort av naive eller ondsinnede brukere.
+
+Imidlertid har mange funnet at MediaWiki er nyttig i mange roller, og av og til er det ikke lett å overbevise alle om fordelene med wikimåten.
+Så du har valget.
+
+En '''{{int:config-profile-wiki}}''' tillater alle å redigere, selv uten å logge inn.
+En wiki med '''{{int:config-profile-no-anon}}''' tilbyr ekstra ansvarlighet, men kan avskrekke tilfeldige bidragsytere.
+
+'''{{int:config-profile-fishbowl}}'''-scenariet tillater godkjente brukere å redigere, mens publikum kan se sider, og også historikken.
+En '''{{int:config-profile-private}}''' tillater kun godkjente brukere å se sider, den samme gruppen som får lov til å redigere dem.
+
+Mer komplekse konfigurasjoner av brukerrettigheter er tilgjengelig etter installasjon, se det [http://www.mediawiki.org/wiki/Manual:User_rights relevante manualavsnittet].",
+ 'config-license' => 'Opphavsrett og lisens:',
+ 'config-license-cc-by-sa' => 'Creative Commons Navngivelse Del på samme vilkår',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Navngivelse Ikke-kommersiell Del på samme vilkår',
+ 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
+ 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 eller nyere',
+ 'config-license-pd' => 'Offentlig rom',
+ 'config-license-cc-choose' => 'Velg en egendefinert Creative Commons-lisens',
+ 'config-email-settings' => 'E-postinnstillinger',
+ 'config-enable-email' => 'Aktiver utgående e-post',
+ 'config-enable-email-help' => 'Hvis du vil at e-post skal virke må [http://www.php.net/manual/en/mail.configuration.php PHPs e-postinnstillinger] bli konfigurert riktig.
+Hvis du ikke ønsker noen e-postfunksjoner kan du deaktivere dem her.',
+ 'config-email-user' => 'Aktiver e-post mellom brukere',
+ 'config-email-user-help' => 'Tillat alle brukere å sende hverandre e-post hvis de har aktivert det i deres innstillinger.',
+ 'config-email-usertalk' => 'Aktiver brukerdiskusjonssidevarsler',
+ 'config-email-usertalk-help' => 'Tillat brukere å motta varsler ved endringer på deres brukerdiskusjonsside hvis de har aktivert dette i deres innstillinger.',
+ 'config-email-watchlist' => 'Aktiver overvåkningslistevarsler',
+ 'config-email-watchlist-help' => 'Tillat brukere å motta varsler ved endringer på deres overvåkede sider hvis de har aktivert dette i deres innstillinger.',
+ 'config-email-auth' => 'Aktiver e-postautentisering',
+ 'config-email-auth-help' => "Om dette alternativet er aktivert må brukere bekrefte sin e-postadresse ved å bruke en lenke som blir sendt til dem når de setter eller endrer adressen sin.
+Kun autentiserte e-postadresser kan motta e-post fra andre brukere eller endringsvarsel.
+Å sette dette valget er '''anbefalt''' for offentlige wikier på grunn av potensiell misbruk av e-postfunksjonene.",
+ 'config-email-sender' => 'Svar-e-postadresse:',
+ 'config-email-sender-help' => 'Skriv inn e-postadressen som skal brukes som svar-adresse ved utgående e-post.
+Det er hit returmeldinger vil bli sendt.
+Mange e-posttjenere krever at minst domenenavnet må være gyldig.',
+ 'config-upload-settings' => 'Bilde- og filopplastinger',
+ 'config-upload-enable' => 'Aktiver filopplastinger',
+ 'config-upload-help' => 'Filopplastinger kan potensielt utsette tjeneren din for sikkerhetsrisikoer.
+For mer informasjon, les [http://www.mediawiki.org/wiki/Manual:Security sikkerhetsseksjonen] i manualen.
+
+For å aktivere filopplastinger, endre modusen i <code>images</code>-undermappen i MediaWikis rotmappe slik at nettjeneren kan skrive til den.
+Aktiver så dette alternativet.',
+ 'config-upload-deleted' => 'Mappe for slettede filer:',
+ 'config-upload-deleted-help' => 'Velg en mappe for å arkivere slettede filer.
+Ideelt burde ikke denne være tilgjengelig for nettet.',
+ 'config-logo' => 'Logo-URL:',
+ 'config-logo-help' => 'MediaWikis standarddrakt inkluderer plass til en 135x160 pikslers logo i øvre venstre hjørne.
+Last opp et bilde i passende størrelse og skriv inn nettadressen her.
+
+Hvis du ikke ønsker en logo, la denne boksen være tom.',
+ 'config-instantcommons' => 'Aktiver Instant Commons',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] er en funksjon som gjør det mulig for wikier å bruke bilder, lyder og andre media funnet på nettstedet [http://commons.wikimedia.org/ Wikimedia Commons].
+For å gjøre dette krever MediaWiki tilgang til internett.
+
+For mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man setter opp dette for andre wikier enn Wikimedia Commons, konsulter [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos manualen].',
+ 'config-cc-again' => 'Velg igjen...',
+ 'config-cc-not-chosen' => 'Velg hvilken Creative Commons-lisens du ønsker og klikk «fortsett».',
+ 'config-advanced-settings' => 'Avansert konfigurasjon',
+ 'config-extensions' => 'Utvidelser',
+ 'config-install-step-done' => 'ferdig',
+ 'config-install-step-failed' => 'mislyktes',
+ 'config-install-extensions' => 'Inkludert utvidelser',
+ 'config-install-database' => 'Setter opp database',
+ 'config-install-user' => 'Oppretter databasebruker',
+ 'config-install-tables' => 'Oppretter tabeller',
+);
+
+/** Polish (Polski)
+ * @author Holek
+ * @author Sp5uhe
+ */
+$messages['pl'] = array(
+ 'config-desc' => 'Instalator MediaWiki',
+ 'config-title' => 'Instalacja MediaWiki $1',
+ 'config-information' => 'Informacja',
+ 'config-localsettings-upgrade' => 'Plik <code>LocalSettings.php</code> istnieje.
+Aby oprogramowanie zostało zaktualizowane musisz wstawić wartość <code>$wgUpgradeKey</code> w poniższe pole.
+Odnajdziesz ją w LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Wykryto obecność pliku LocalSettings.php.
+Do wykonania aktualizacji instalacji należy dodać opcję --upgrade=yes.',
+ 'config-localsettings-key' => 'Klucz aktualizacji',
+ 'config-localsettings-badkey' => 'Podany klucz jest nieprawidłowy',
+ 'config-upgrade-key-missing' => 'Wykryto zainstalowane wcześniej MediaWiki.
+Jeśli chcesz je zaktualizować dodaj na koniec pliku LocalSettings.php poniższą linię tekstu.
+
+$1',
+ 'config-localsettings-incomplete' => 'Istniejący plik LocalSettings.php wygląda na niekompletny.
+Brak wartości zmiennej $1.
+Zmień plik LocalSettings.php, tak by zawierał deklarację wartości tej zmiennej, a następnie kliknij „Dalej”.',
+ 'config-localsettings-connection-error' => 'Wystąpił błąd podczas łączenia z bazą danych z wykorzystaniem danych z LocalSettings.php lub AdminSettings.php.
+Popraw ustawienia i spróbuj ponownie.
+
+$1',
+ 'config-session-error' => 'Błąd uruchomienia sesji – $1',
+ 'config-session-expired' => 'Wygląda na to, że Twoja sesja wygasła.
+Czas życia sesji został skonfigurowany na $1.
+Możesz go wydłużyć zmieniając <code>session.gc_maxlifetime</code> w pliku php.ini.
+Uruchom ponownie proces instalacji.',
+ 'config-no-session' => 'Dane sesji zostały utracone.
+Sprawdź plik php.ini i upewnij się, że <code>session.save_path</code> wskazuje na odpowiedni katalog.',
+ 'config-your-language' => 'Język',
+ 'config-your-language-help' => 'Wybierz język używany podczas procesu instalacji.',
+ 'config-wiki-language' => 'Język wiki',
+ 'config-wiki-language-help' => 'Wybierz język, w którym będzie tworzona większość treści wiki',
+ 'config-back' => '← Wstecz',
+ 'config-continue' => 'Dalej →',
+ 'config-page-language' => 'Język',
+ 'config-page-welcome' => 'Witamy w MediaWiki!',
+ 'config-page-dbconnect' => 'Połączenie z bazą danych',
+ 'config-page-upgrade' => 'Uaktualnienie istniejącej instalacji',
+ 'config-page-dbsettings' => 'Ustawienia bazy danych',
+ 'config-page-name' => 'Nazwa',
+ 'config-page-options' => 'Opcje',
+ 'config-page-install' => 'Instaluj',
+ 'config-page-complete' => 'Zakończono!',
+ 'config-page-restart' => 'Ponowne uruchomienie instalacji',
+ 'config-page-readme' => 'Podstawowe informacje',
+ 'config-page-releasenotes' => 'Informacje o wersji',
+ 'config-page-copying' => 'Kopiowanie',
+ 'config-page-upgradedoc' => 'Uaktualnienie',
+ 'config-page-existingwiki' => 'Istniejąca wiki',
+ 'config-help-restart' => 'Czy chcesz usunąć wszystkie zapisane dane, które podałeś i uruchomić ponownie proces instalacji?',
+ 'config-restart' => 'Tak, zacznij od nowa',
+ 'config-welcome' => '=== Sprawdzenie środowiska instalacji ===
+Wykonywane są podstawowe testy sprawdzające czy to środowisko jest odpowiednie dla instalacji MediaWiki.
+Jeśli potrzebujesz pomocy podczas instalacji załącz wyniki tych testów.',
+ 'config-copyright' => "=== Prawa autorskie i warunki użytkowania ===
+
+$1
+
+To oprogramowanie jest wolne; możesz je rozprowadzać dalej i modyfikować zgodnie z warunkami licencji GNU General Public License opublikowanej przez Free Software Foundation w wersji 2 tej licencji lub (według Twojego wyboru) którejś z późniejszych jej wersji.
+
+Niniejsze oprogramowanie jest rozpowszechniane w nadziei, że będzie użyteczne, ale '''bez żadnej gwarancji'''; nawet bez domniemanej gwarancji '''handlowej''' lub '''przydatności do określonego celu'''.
+Zobacz treść licencji GNU General Public License, aby uzyskać więcej szczegółów.
+
+Razem z oprogramowaniem powinieneś otrzymać <doclink href=Copying>kopię licencji GNU General Public License</doclink>. Jeśli jej nie otrzymałeś, napisz do Free Software Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. lub [http://www.gnu.org/copyleft/gpl.html przeczytaj ją online].",
+ 'config-sidebar' => '* [http://www.mediawiki.org Strona domowa MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Podręcznik użytkownika]
+* [http://www.mediawiki.org/wiki/Manual:Contents Podręcznik administratora]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Odpowiedzi na często zadawane pytania]
+----
+* <doclink href=Readme>Przeczytaj to</doclink>
+* <doclink href=ReleaseNotes>Informacje o tej wersji</doclink>
+* <doclink href=Copying>Kopiowanie</doclink>
+* <doclink href=UpgradeDoc>Aktualizacja</doclink>',
+ 'config-env-good' => 'Środowisko oprogramowania zostało sprawdzone.
+Możesz teraz zainstalować MediaWiki.',
+ 'config-env-bad' => 'Środowisko oprogramowania zostało sprawdzone.
+Nie możesz zainstalować MediaWiki.',
+ 'config-env-php' => 'Zainstalowane jest PHP w wersji $1.',
+ 'config-unicode-using-utf8' => 'Korzystanie z normalizacji Unicode utf8_normalize.so napisanej przez Brion Vibbera.',
+ 'config-unicode-using-intl' => 'Korzystanie z [http://pecl.php.net/intl rozszerzenia intl PECL] do normalizacji Unicode.',
+ 'config-unicode-pure-php-warning' => "'''Uwaga!''' [http://pecl.php.net/intl Rozszerzenie intl PECL] do obsługi normalizacji Unicode nie jest dostępne. Użyta zostanie mało wydajna zwykła implementacja w PHP.
+Jeśli prowadzisz stronę o dużym natężeniu ruchu, powinieneś zapoznać się z informacjami o [http://www.mediawiki.org/wiki/Unicode_normalization_considerations normalizacji Unicode].",
+ 'config-unicode-update-warning' => "'''Uwaga''' – zainstalowana wersja normalizacji Unicode korzysta z nieaktualnej biblioteki [http://site.icu-project.org/ projektu ICU].
+Powinieneś [http://www.mediawiki.org/wiki/Unicode_normalization_considerations zrobić aktualizację] jeśli chcesz korzystać w pełni z Unicode.",
+ 'config-no-db' => 'Nie można odnaleźć właściwego sterownika bazy danych!',
+ 'config-no-db-help' => 'Należy zainstalować sterownik bazy danych dla PHP.
+Obsługiwane są następujące typy baz danych: $1.
+
+Jeżeli korzystasz ze współdzielonego hostingu, zwróć się do administratora o zainstalowanie odpowiedniego sterownika bazy danych.
+Jeśli skompilowałeś PHP samodzielnie, skonfiguruj je ponownie z włączonym klientem bazy danych, na przykład za pomocą polecenia
+<code>./configure --with-mysql</code>.
+Jeśli zainstalowałeś PHP jako pakiet Debiana lub Ubuntu, musisz również zainstalować moduł php5-mysql.',
+ 'config-no-fts3' => "'''Uwaga''' – SQLite został skompilowany bez [http://sqlite.org/fts3.html modułu FTS3] – funkcje wyszukiwania nie będą dostępne.",
+ 'config-register-globals' => "'''Uwaga – w konfiguracji PHP włączona jest opcja <code>[http://php.net/register_globals register_globals]</code>.'''
+'''Jeśli możesz, wyłącz ją.'''
+MediaWiki będzie działać, ale Twój serwer może być narażony potencjalnymi lukami w zabezpieczeniach.",
+ 'config-magic-quotes-runtime' => "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''
+Ta opcja powoduje nieprzewidywalne uszkodzenia wprowadzanych danych.
+Zainstalować lub korzystać z MediaWiki można pod warunkiem, że ta opcja jest wyłączona.",
+ 'config-magic-quotes-sybase' => "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''
+Ta opcja powoduje nieprzewidywalne uszkodzenia wprowadzanych danych.
+Zainstalować lub korzystać z MediaWiki można pod warunkiem, że ta opcja jest wyłączona.",
+ 'config-mbstring' => "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''
+Ta opcja powoduje błędy i może wywołać nieprzewidywalne uszkodzenia wprowadzanych danych.
+Zainstalować lub korzystać z MediaWiki można pod warunkiem, że ta opcja jest wyłączona.",
+ 'config-ze1' => "'''Błąd krytyczny – włączono [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]!'''
+Ta opcja powoduje okropne błędy podczas korzystania z MediaWiki.
+Zainstalować lub korzystać z MediaWiki można wyłącznie wtedy, gdy ta opcja jest wyłączona.",
+ 'config-safe-mode' => "'''Ostrzeżenie''' – uaktywniono [http://www.php.net/features.safe-mode tryb awaryjny] PHP.
+Opcja ta może powodować problemy, szczególnie w przypadku korzystania z przesyłania plików i używania znacznika <code>math</code>.",
+ 'config-xml-bad' => 'Brak modułu XML dla PHP.
+MediaWiki wymaga funkcji z tego modułu i nie może działać w tej konfiguracji.
+Jeśli korzystasz z Mandrake, zainstaluj pakiet php-xml.',
+ 'config-pcre' => 'Wygląda na to, że brak modułu PCRE.
+MediaWiki do pracy wymaga funkcji obsługi wyrażeń regularnych kompatybilnej z Perlem.',
+ 'config-pcre-no-utf8' => "'''Błąd krytyczny''' – wydaje się, że moduł PCRE w PHP został skompilowany bez wsparcia dla UTF‐8.
+MediaWiki wymaga wsparcia dla UTF‐8 do prawidłowego działania.",
+ 'config-memory-raised' => 'PHP <code>memory_limit</code> było ustawione na $1, zostanie zwiększone do $2.',
+ 'config-memory-bad' => "'''Uwaga:''' PHP <code>memory_limit</code> jest ustawione na $1.
+To jest prawdopodobnie zbyt mało.
+Instalacja może się nie udać!",
+ 'config-xcache' => '[Http://trac.lighttpd.net/xcache/ XCache] jest zainstalowany',
+ 'config-apc' => '[Http://www.php.net/apc APC] jest zainstalowany',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] jest zainstalowany',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] jest zainstalowany',
+ 'config-no-cache' => "'''Uwaga:''' Nie można odnaleźć [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] lub [http://www.iis.net/download/WinCacheForPhp WinCache].
+Buforowanie obiektów nie będzie możliwe.",
+ 'config-diff3-bad' => 'Nie znaleziono GNU diff3.',
+ 'config-imagemagick' => 'Odnaleziono ImageMagick <code>$1</code>.
+Miniatury grafik będą generowane jeśli włączysz przesyłanie plików.',
+ 'config-gd' => 'Odnaleziono wbudowaną bibliotekę graficzną GD.
+Miniatury grafik będą generowane jeśli włączysz przesyłanie plików.',
+ 'config-no-scaling' => 'Nie można odnaleźć biblioteki GD lub ImageMagick.
+Tworzenie miniatur grafik będzie wyłączone.',
+ 'config-no-uri' => "'''Błąd.''' Nie można określić aktualnego URI.
+Instalacja została przerwana.",
+ 'config-uploads-not-safe' => "'''Uwaga''' – domyślny katalog do którego zapisywane są przesyłane pliki <code>$1</code> jest podatny na wykonanie dowolnego skryptu.
+Chociaż MediaWiki sprawdza wszystkie przesłane pliki pod kątem bezpieczeństwa, zaleca się jednak, aby [http://www.mediawiki.org/wiki/Manual:Security#Upload_security zamknąć tę lukę w zabezpieczeniach] przed włączeniem przesyłania plików.",
+ 'config-brokenlibxml' => 'Twój system jest kombinacją wersji PHP i libxml2, które zawierają błędy mogące powodować ukryte uszkodzenia danych w MediaWiki i innych aplikacjach sieci web.
+Wykonaj aktualizację PHP do wersji 5.2.9 lub późniejszej oraz libxml2 do wersji 2.7.3 lub późniejszej ([http://bugs.php.net/bug.php?id=45996 błąd w PHP]).
+Instalacja została przerwana.',
+ 'config-using531' => 'PHP $1 nie współpracuje poprawnie z MediaWiki z powodu błędu dotyczącego referencyjnych argumentów funkcji <code>__call()</code>.
+Uaktualnij do PHP 5.3.2 lub nowszego. Możesz również cofnąć wersję do PHP 5.3.0 aby naprawić ten błąd ([http://bugs.php.net/bug.php?id=50394 błąd w PHP]).
+Instalacja została przerwana.',
+ 'config-db-type' => 'Typ bazy danych',
+ 'config-db-host' => 'Adres serwera bazy danych',
+ 'config-db-host-help' => 'Jeśli serwer bazy danych jest na innej maszynie, wprowadź jej nazwę domenową lub adres IP.
+
+Jeśli korzystasz ze współdzielonego hostingu, operator serwera powinien podać Ci prawidłową nazwę serwera w swojej dokumentacji.
+
+Jeśli instalujesz oprogramowanie na serwerze Windowsowym i korzystasz z MySQL, użycie „localhost” może nie zadziałać jako nazwa hosta. Jeśli wystąpi ten problem użyj „127.0.0.1” jako lokalnego adresu IP.',
+ 'config-db-host-oracle' => 'TNS bazy danych',
+ 'config-db-host-oracle-help' => 'Wprowadź prawidłową [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nazwę połączenia lokalnego]. Plik „tnsnames.ora” musi być widoczny dla instalatora.<br />Jeśli używasz biblioteki klienckiej 10g lub nowszej możesz również skorzystać z metody nazw [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm łatwego łączenia].',
+ 'config-db-wiki-settings' => 'Zidentyfikuj tę wiki',
+ 'config-db-name' => 'Nazwa bazy danych',
+ 'config-db-name-help' => 'Wybierz nazwę, która zidentyfikuje Twoją wiki.
+Nie może ona zawierać spacji.
+
+Jeśli korzystasz ze współdzielonego hostingu, dostawca usługi hostingowej może wymagać użycia konkretnej nazwy bazy danych lub pozwalać na tworzenie baz danych za pośrednictwem panelu użytkownika.',
+ 'config-db-name-oracle' => 'Schemat bazy danych',
+ 'config-db-install-account' => 'Konto użytkownika dla instalatora',
+ 'config-db-username' => 'Nazwa użytkownika bazy danych',
+ 'config-db-password' => 'Hasło bazy danych',
+ 'config-db-install-username' => 'Wprowadź nazwę użytkownika, który będzie używany do łączenia się z bazą danych podczas procesu instalacji.
+Nie jest to nazwa konta MediaWiki, a użytkownika bazy danych.',
+ 'config-db-install-password' => 'Wprowadź hasło, które będzie wykorzystywane do łączenia się z bazą danych w procesie instalacji.
+To nie jest hasło konta MediaWiki, lecz hasło do bazy danych.',
+ 'config-db-install-help' => 'Podaj nazwę użytkownika i jego hasło, które zostaną użyte do połączenia z bazą danych w czasie procesu instalacji.',
+ 'config-db-account-lock' => 'Użyj tej samej nazwy użytkownika i hasła w czasie normalnej pracy.',
+ 'config-db-wiki-account' => 'Konto użytkownika do normalnej pracy',
+ 'config-db-prefix' => 'Przedrostek tabel bazy danych',
+ 'config-db-charset' => 'Zestaw znaków bazy danych',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binarny',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 kompatybilny wstecz UTF-8',
+ 'config-mysql-old' => 'Wymagany jest MySQL $1 lub nowszy; korzystasz z $2.',
+ 'config-db-port' => 'Port bazy danych',
+ 'config-db-schema' => 'Schemat dla MediaWiki',
+ 'config-db-schema-help' => 'Ten schemat jest zazwyczaj właściwy.
+Zmień go wyłącznie jeśli jesteś pewien, że powinieneś.',
+ 'config-sqlite-dir' => 'Katalog danych SQLite',
+ 'config-oracle-def-ts' => 'Domyślna przestrzeń tabel',
+ 'config-oracle-temp-ts' => 'Przestrzeń tabel tymczasowych',
+ 'config-support-info' => 'MediaWiki może współpracować z następującymi systemami baz danych:
+
+$1
+
+Jeśli system baz danych, z którego chcesz skorzystać nie jest wymieniony, postępuj zgodnie z instrukcjami aby móc z niego skorzystać.',
+ 'config-support-mysql' => '* $1 jest domyślną bazą danych dla MediaWiki i jest najlepiej wspierane ([http://www.php.net/manual/en/mysql.installation.php jak skompilować PHP ze wsparciem dla MySQL])',
+ 'config-support-postgres' => '* $1 jest popularnym systemem baz danych z otwartym kodem; jest alternatywą dla MySQL ([http://www.php.net/manual/en/pgsql.installation.php jak skompilować PHP ze wsparciem dla PostgreSQL])',
+ 'config-support-sqlite' => '* $1 jest lekkim systemem bazy danych, który jest bardzo dobrze wspierany. ([http://www.php.net/manual/en/pdo.installation.php Jak skompilować PHP ze wsparciem dla SQLite], korzystając z PDO)',
+ 'config-support-oracle' => '* $1 jest komercyjną profesjonalną bazą danych. ([http://www.php.net/manual/en/oci8.installation.php Jak skompilować PHP ze wsparciem dla OCI8])',
+ 'config-header-mysql' => 'Ustawienia MySQL',
+ 'config-header-postgres' => 'Ustawienia PostgreSQL',
+ 'config-header-sqlite' => 'Ustawienia SQLite',
+ 'config-header-oracle' => 'Ustawienia Oracle',
+ 'config-invalid-db-type' => 'Nieprawidłowy typ bazy danych',
+ 'config-missing-db-name' => 'Należy wpisać wartość w polu „Nazwa bazy danych”',
+ 'config-missing-db-host' => 'Musisz wpisać wartość w polu „Serwer bazy danych”',
+ 'config-missing-db-server-oracle' => 'Należy wpisać wartość w polu „Baza danych TNS”',
+ 'config-invalid-db-server-oracle' => 'Nieprawidłowa baza danych TNS „$1”.
+Używaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) i kropek (.).',
+ 'config-invalid-db-name' => 'Nieprawidłowa nazwa bazy danych „$1”.
+Używaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) lub znaku odejmowania (-).',
+ 'config-invalid-db-prefix' => 'Nieprawidłowy prefiks bazy danych „$1”.
+Używaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) lub znaku odejmowania (-).',
+ 'config-connection-error' => '$1.
+
+Sprawdź adres serwera, nazwę użytkownika i hasło, a następnie spróbuj ponownie.',
+ 'config-invalid-schema' => 'Nieprawidłowy schemat dla MediaWiki „$1”.
+Używaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9) i podkreślenia (_).',
+ 'config-postgres-old' => 'Wymagany jest PostgreSQL $1 lub nowszy; korzystasz z $2.',
+ 'config-sqlite-name-help' => 'Wybierz nazwę, która będzie identyfikować Twoją wiki.
+Nie wolno używać spacji ani myślników.
+Zostanie ona użyta jako nazwa pliku danych SQLite.',
+ 'config-sqlite-mkdir-error' => 'Błąd podczas tworzenia katalogu dla danych „$1”.
+Sprawdź lokalizację i spróbuj ponownie.',
+ 'config-sqlite-dir-unwritable' => 'Nie można zapisać do katalogu „$1”.
+Zmień uprawnienia dostępu do katalogu tak, aby serwer WWW mógł pisać do niego, a następnie spróbuj ponownie.',
+ 'config-sqlite-connection-error' => '$1.
+
+Sprawdź katalog danych oraz nazwę bazy danych, a następnie spróbuj ponownie.',
+ 'config-sqlite-readonly' => 'Plik <code>$1</code> nie jest zapisywalny.',
+ 'config-sqlite-cant-create-db' => 'Nie można utworzyć pliku bazy danych <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'Brak wsparcia FTS3 dla PHP. Tabele zostały cofnięte',
+ 'config-can-upgrade' => "W bazie danych są już tabele MediaWiki.
+Aby uaktualnić je do MediaWiki $1, kliknij '''Dalej'''.",
+ 'config-upgrade-done-no-regenerate' => 'Aktualizacja zakończona.
+
+Możesz wreszcie [$1 zacząć korzystać ze swojej wiki].',
+ 'config-regenerate' => 'Ponowne generowanie LocalSettings.php →',
+ 'config-show-table-status' => 'Zapytanie „SHOW TABLE STATUS” nie powiodło się!',
+ 'config-unknown-collation' => "'''Uwaga''' – bazy danych używa nierozpoznanej metody porównywania.",
+ 'config-db-web-account' => 'Konto bazy danych dla dostępu przez WWW',
+ 'config-db-web-help' => 'Wybierz nazwę użytkownika i hasło, z których korzystać będzie serwer WWW do łączenia się z serwerem baz danych, podczas zwykłej pracy z wiki.',
+ 'config-db-web-account-same' => 'Użyj tego samego konta, co dla instalacji',
+ 'config-db-web-create' => 'Utwórz konto, jeśli jeszcze nie istnieje',
+ 'config-db-web-no-create-privs' => 'Konto podane do wykonania instalacji nie ma wystarczających uprawnień, aby utworzyć nowe konto.
+Konto, które wskazałeś tutaj musi już istnieć.',
+ 'config-mysql-engine' => 'Silnik przechowywania',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => 'Zestaw znaków bazy danych',
+ 'config-mysql-binary' => 'binarny',
+ 'config-mysql-utf8' => 'UTF‐8',
+ 'config-site-name' => 'Nazwa wiki',
+ 'config-site-name-help' => 'Ten napis pojawi się w pasku tytułowym przeglądarki oraz w różnych innych miejscach.',
+ 'config-site-name-blank' => 'Wprowadź nazwę witryny.',
+ 'config-project-namespace' => 'Przestrzeń nazw projektu',
+ 'config-ns-generic' => 'Projekt',
+ 'config-ns-site-name' => 'Taka sama jak nazwa wiki $1',
+ 'config-ns-other' => 'Inna (należy określić)',
+ 'config-ns-other-default' => 'MojaWiki',
+ 'config-ns-invalid' => 'Podana przestrzeń nazw „<nowiki>$1</nowiki>” jest nieprawidłowa.
+Podaj inną przestrzeń nazw projektu.',
+ 'config-admin-box' => 'Konto administratora',
+ 'config-admin-name' => 'Administrator',
+ 'config-admin-password' => 'Hasło',
+ 'config-admin-password-confirm' => 'Hasło powtórnie',
+ 'config-admin-help' => 'Wprowadź preferowaną nazwę użytkownika, na przykład „Jan Kowalski”.
+Tej nazwy będziesz używać do logowania się do wiki.',
+ 'config-admin-name-blank' => 'Wpisz nazwę użytkownika, który będzie administratorem.',
+ 'config-admin-name-invalid' => 'Podana nazwa użytkownika „<nowiki>$1</nowiki>” jest nieprawidłowa.
+Podaj inną nazwę.',
+ 'config-admin-password-blank' => 'Wprowadź hasło dla konta administratora.',
+ 'config-admin-password-same' => 'Hasło nie może być takie samo jak nazwa użytkownika.',
+ 'config-admin-password-mismatch' => 'Wprowadzone dwa hasła różnią się między sobą.',
+ 'config-admin-email' => 'Adres e‐mail',
+ 'config-admin-email-help' => 'Wpisz adres e‐mail, aby mieć możliwość odbierania e‐maili od innych użytkowników na wiki, zresetowania hasła oraz otrzymywania powiadomień o zmianach na stronach z listy obserwowanych.',
+ 'config-admin-error-user' => 'Błąd wewnętrzny podczas tworzenia konta administratora o nazwie „<nowiki>$1</nowiki>”.',
+ 'config-admin-error-password' => 'Wewnętrzny błąd podczas ustawiania hasła dla administratora „<nowiki>$1</nowiki>”: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Wpisałeś nieprawidłowy adres e‐mail',
+ 'config-subscribe' => 'Zapisz się na [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce listę pocztową z ogłaszaniami o nowych wersjach].',
+ 'config-subscribe-help' => 'Jest to lista o małej liczbie wiadomości, wykorzystywana do przesyłania informacji o udostępnieniu nowej wersji oraz istotnych sprawach dotyczących bezpieczeństwa.
+Powinieneś zapisać się na tę listę i aktualizować zainstalowane oprogramowanie MediaWiki gdy pojawia się nowa wersja.',
+ 'config-almost-done' => 'To już prawie koniec!
+Możesz pominąć pozostałe czynności konfiguracyjne i zainstalować wiki.',
+ 'config-optional-continue' => 'Zadaj mi więcej pytań.',
+ 'config-optional-skip' => 'Jestem już znudzony, po prostu zainstaluj wiki.',
+ 'config-profile' => 'Profil uprawnień użytkowników',
+ 'config-profile-wiki' => 'Tradycyjne wiki',
+ 'config-profile-no-anon' => 'Wymagane utworzenie konta',
+ 'config-profile-fishbowl' => 'Wyłącznie zatwierdzeni edytorzy',
+ 'config-profile-private' => 'Prywatna wiki',
+ 'config-license' => 'Prawa autorskie i licencja',
+ 'config-license-none' => 'Brak stopki z licencją',
+ 'config-license-cc-by-sa' => 'Creative Commons – za uznaniem autora, na tych samych zasadach',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons – za uznaniem autora, bez użycia komercyjnego, na tych samych zasadach',
+ 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
+ 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 lub późniejsza',
+ 'config-license-pd' => 'Domena publiczna',
+ 'config-license-cc-choose' => 'Wybierz własną licencję Creative Commons',
+ 'config-email-settings' => 'Ustawienia e-maili',
+ 'config-enable-email' => 'Włącz wychodzące wiadomości e–mail',
+ 'config-email-user' => 'Włącz możliwość przesyłania e‐maili pomiędzy użytkownikami',
+ 'config-email-user-help' => 'Zezwalaj użytkownikom na wysyłanie wzajemnie e‐maili, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.',
+ 'config-email-usertalk' => 'Włącz powiadamianie o zmianach na stronie dyskusji użytkownika',
+ 'config-email-usertalk-help' => 'Pozwól użytkownikom otrzymywać powiadomienia o zmianach na stronie dyskusji użytkownika, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.',
+ 'config-email-watchlist' => 'Włącz powiadomienie o zmianach stron obserwowanych',
+ 'config-email-watchlist-help' => 'Pozwól użytkownikom otrzymywać powiadomienia o zmianach na stronach obserwowanych, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.',
+ 'config-email-auth' => 'Włącz uwierzytelnianie e‐mailem',
+ 'config-email-sender' => 'Zwrotny adres e‐mail',
+ 'config-upload-settings' => 'Przesyłanie obrazków i plików',
+ 'config-upload-enable' => 'Włącz przesyłanie plików na serwer',
+ 'config-upload-deleted' => 'Katalog dla usuniętych plików',
+ 'config-upload-deleted-help' => 'Wybierz katalog, w którym będzie archiwum usuniętych plików.
+Najlepiej, aby nie był on dostępny z internetu.',
+ 'config-logo' => 'Adres URL logo',
+ 'config-instantcommons' => 'Włącz Instant Commons',
+ 'config-cc-error' => 'Wybieranie licencji Creative Commons nie dało wyniku.
+Wpisz nazwę licencji ręcznie.',
+ 'config-cc-again' => 'Wybierz jeszcze raz...',
+ 'config-cc-not-chosen' => 'Wybierz którą chcesz licencję Creative Commons i kliknij „Dalej”.',
+ 'config-advanced-settings' => 'Konfiguracja zaawansowana',
+ 'config-cache-options' => 'Ustawienia buforowania obiektów',
+ 'config-cache-none' => 'Brak buforowania (wszystkie funkcje będą działać, ale mogą wystąpić kłopoty z wydajnością na dużych witrynach wiki)',
+ 'config-cache-accel' => 'Buforowania obiektów PHP (APC, eAccelerator, XCache lub WinCache)',
+ 'config-cache-memcached' => 'Użyj Memcached (wymaga dodatkowej instalacji i konfiguracji)',
+ 'config-memcached-servers' => 'Serwery Memcached:',
+ 'config-memcached-help' => 'Lista adresów IP do wykorzystania przez Memcached.
+Adresy powinny być umieszczane po jednym w linii i określać również wykorzystywany port. Na przykład:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-extensions' => 'Rozszerzenia',
+ 'config-install-alreadydone' => "'''Uwaga''' – wydaje się, że MediaWiki jest już zainstalowane, a obecnie próbujesz zainstalować je ponownie.
+Przejdź do następnej strony.",
+ 'config-install-step-done' => 'gotowe',
+ 'config-install-step-failed' => 'nieudane',
+ 'config-install-extensions' => 'Włącznie z rozszerzeniami',
+ 'config-install-database' => 'Konfigurowanie bazy danych',
+ 'config-install-pg-schema-failed' => 'Utworzenie tabel nie powiodło się.
+Upewnij się, że użytkownik „$1” może zapisywać do schematu „$2”.',
+ 'config-install-pg-commit' => 'Zatwierdzanie zmian',
+ 'config-pg-no-plpgsql' => 'Musisz zainstalować język PL/pgSQL w bazie danych $1',
+ 'config-install-user' => 'Tworzenie użytkownika bazy danych',
+ 'config-install-user-grant-failed' => 'Przyznanie uprawnień użytkownikowi „$1” nie powiodło się – $2',
+ 'config-install-tables' => 'Tworzenie tabel',
+ 'config-install-tables-exist' => "'''Uwaga''' – wygląda na to, że tabele MediaWiki już istnieją.
+Pomijam tworzenie tabel.",
+ 'config-install-tables-failed' => "'''Błąd''' – tworzenie tabeli nie powiodło się z powodu błędu – $1",
+ 'config-install-interwiki' => 'Wypełnianie tabeli domyślnymi interwiki',
+ 'config-install-interwiki-list' => 'Nie można odnaleźć pliku <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Uwaga''' – wygląda na to, że tabela interwiki ma już jakieś wpisy.
+Tworzenie domyślnej listy pominięto.",
+ 'config-install-keys' => 'Generowanie tajnego klucza',
+ 'config-install-sysop' => 'Tworzenie konta administratora',
+ 'config-install-subscribe-fail' => 'Nie można zapisać na listę „mediawiki-announce“',
+ 'config-install-mainpage' => 'Tworzenie strony głównej z domyślną zawartością',
+ 'config-install-mainpage-failed' => 'Nie udało się wstawić strony głównej – $1',
+ 'config-download-localsettings' => 'Pobierz LocalSettings.php',
+ 'config-help' => 'pomoc',
+);
+
+/** Piedmontese (Piemontèis)
+ * @author Borichèt
+ * @author Dragonòt
+ * @author Krinkle
+ */
+$messages['pms'] = array(
+ 'config-desc' => "L'instalador për mediaWiki",
+ 'config-title' => 'Anstalassion ëd MediaWiki $1',
+ 'config-information' => 'Anformassion',
+ 'config-localsettings-upgrade' => "A l'é stàit trovà n'archivi <code>LocalSettings.php</code>.
+Për agiorné cost'anstalassion, ch'a anserissa ël valor ëd <code>\$wgUpgradeKey</code> ant la casela sì-sota.
+A la trovrà an LocalSetting.php.",
+ 'config-localsettings-key' => "Ciav d'agiornament:",
+ 'config-session-error' => 'Eror an fasend parte la session: $1',
+ 'config-session-expired' => "Ij sò dat ëd session a smijo scadù.
+Le session a son configurà për na durà ëd $1.
+A peul aumenté sòn an ampostand <code>session.gc_maxlifetime</code> an php.ini.
+Ch'a anandia torna ël process d'instalassion.",
+ 'config-no-session' => "Ij sò dat ëd session a son përdù!
+Ch'a contròla sò php.ini e ch'as sigura che <code>session.save_path</code> a sia ampostà ant ël dossié giust.",
+ 'config-your-language' => 'Toa lenga:',
+ 'config-your-language-help' => "Selessioné na lenga da dovré durant ël process d'instalassion.",
+ 'config-wiki-language' => 'Lenga dla Wiki:',
+ 'config-wiki-language-help' => 'Selession-a la lenga dont la wiki a sarà prevalentement scrivùa.',
+ 'config-back' => '← André',
+ 'config-continue' => 'Continua →',
+ 'config-page-language' => 'Lenga',
+ 'config-page-welcome' => 'Bin ëvnù a MediaWiki!',
+ 'config-page-dbconnect' => 'Coleghesse a la base ëd dàit',
+ 'config-page-upgrade' => "Agiorné l'instalassion esistenta",
+ 'config-page-dbsettings' => 'Ampostassion dla base ëd dàit',
+ 'config-page-name' => 'Nòm',
+ 'config-page-options' => 'Opsion',
+ 'config-page-install' => 'Instala',
+ 'config-page-complete' => 'Completa!',
+ 'config-page-restart' => "Fé torna parte l'instalassion",
+ 'config-page-readme' => 'Lesme',
+ 'config-page-releasenotes' => 'Nòte ëd publicassion',
+ 'config-page-copying' => 'Copié',
+ 'config-page-upgradedoc' => 'Agiorné',
+ 'config-help-restart' => "Veul-lo scancelé tùit ij dat salvà ch'a l'ha anserì e anandié torna ël process d'instalassion?",
+ 'config-restart' => 'É!, felo torna parte',
+ 'config-welcome' => "=== Contròj d'ambient ===
+Dle verìfiche ëd base a son fàite për vëdde se st'ambient a va bin për l'instalassion ëd MediaWiki.
+S'a l'ha da manca d'agiut durant l'anstalassion, a dovrìa fornì j'arzultà dë sti contròj.",
+ 'config-copyright' => "=== Drit d'Autor e Condission ===
+
+$1
+
+Cost-sì a l'é un programa lìber e a gràtis: a peul ridistribuilo e/o modifichelo sota le condission dla licensa pùblica general GNU com publicà da la Free Software Foundation; la version 2 dla Licensa, o (a toa sèrnìa) qualsëssìa version pi recenta.
+
+Cost programa a l'é distribuì ant la speransa ch'a sia ùtil, ma '''sensa gnun-e garansìe'''; sensa gnanca la garansia implìssita ëd '''comersiabilità''' o '''d'esse adat a un but particolar'''.
+
+A dovrìa avèj arseivù <doclink href=Copying>na còpia ëd la licensa pùblica general GNU</doclink> ansema a sto programa; dësnò, ch'a scriva a la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA opura [http://www.gnu.org/copyleft/gpl.html ch'a la lesa an linia].",
+ 'config-sidebar' => "* [http://www.mediawiki.org Intrada MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Guida dl'Utent]
+* [http://www.mediawiki.org/wiki/Manual:Contents Guida dl'Aministrator]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Soens an ciamo]",
+ 'config-env-good' => "L'ambient a l'é stàit controlà.
+It peule instalé MediaWiki.",
+ 'config-env-bad' => "L'ambient a l'é stàit controlà.
+It peule pa instalé MediaWiki.",
+ 'config-env-php' => "PHP $1 a l'é instalà.",
+ 'config-unicode-using-utf8' => 'As deuvra utf8_normalize.so ëd Brion Vibber për la normalisassion Unicode.',
+ 'config-unicode-using-intl' => "As deuvra l'[http://pecl.php.net/intl estension intl PECL] për la normalisassion Unicode.",
+ 'config-unicode-pure-php-warning' => "'''Avis:''' L'[http://pecl.php.net/intl estension intl PECL] a l'é pa disponìbil për gestì la normalisassion Unicode, da già che l'implementassion an PHP pur a faliss për lentëssa.
+S'a gestiss un sit a àut tràfich, a dovrìa lese cheicòs an sla [http://www.mediawiki.org/wiki/Unicode_normalization_considerations normalisassion Unicode].",
+ 'config-unicode-update-warning' => "'''Avis:''' La version instalà dlë spassiador ëd normalisassion Unicode a deuvra na version veja ëd la librarìa dël [http://site.icu-project.org/ proget ICU].
+A dovrìa fé n'[http://www.mediawiki.org/wiki/Unicode_normalization_considerations agiornament] s'a l'é anteressà a dovré Unicode.",
+ 'config-no-db' => 'Impossìbil tové un pilòta ëd base ëd dàit bon!',
+ 'config-no-db-help' => "A dev instalé un pilòta ëd base ëd dàit për PHP.
+A son mantnùe le sòrt ëd base ëd dàit sì-dapress: $1.
+
+S'a l'é ospità ëd fasson partagià, ch'a ciama al fornidor d'ospitalità d'instalé un pilòta ëd base ëd dàit adat.
+S'a l'ha compilà chiel-midem PHP, ch'a lo configura torna con un client ëd base ëd dàit abilità, për esempi an dovrand <code>./configure --with-mysql</code>.
+S'a l'ha instalà PHP da un pachet Debian o Ubuntu, antlora a dev ëdcò instalé ël mòdul php5-mysql.",
+ 'config-no-fts3' => "'''Avis''': SQLite a l'é compilà sensa ël mòdul [http://sqlite.org/fts3.html FTS3], le funsion d'arserca a saran pa disponìbij su cost motor.",
+ 'config-register-globals' => "'''Avis: L'opsion <code>[http://php.net/register_globals register_globals]</code> ëd PHP a l'é abilità.'''
+'''Ch'a la disabìlita s'a peul.'''
+MediaWiki a marcërà, ma sò servent a l'é espòst a 'd possìbij vunerabilità ëd sicurëssa.",
+ 'config-magic-quotes-runtime' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] a l'é ativ!'''
+Costa opsion a danegia ij dat d'intrada an manera pa prevedìbil.
+A peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
+ 'config-magic-quotes-sybase' => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] a l'é ativ!'''
+Costa opsion a danegia ij dat d'intrada an manera pa prevedìbil.
+A peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
+ 'config-mbstring' => "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] a l'é ativ!'''
+Costa opsion a càusa d'eror e a peul danegié ij dat d'intrada an manera pa prevedìbil.
+A peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
+ 'config-ze1' => "'''Fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] a l'é ativ!'''
+Costa opsion a càusa dij bigat afros con MediaWiki.
+A peul pa instalé o dovré MediaWiki se st'opsion a l'é pa disabilità.",
+ 'config-safe-mode' => "'''Avis:''' [http://www.php.net/features.safe-mode Safe mode] ëd PHP a l'é ativ.
+A peul causé ëd problema, dzortut s'as deuvro ël cariament d'archivi e ël manteniment ëd <code>math</code>.",
+ 'config-xml-bad' => "Mòdul XML ed PHP mancant.
+MediaWiki a l'ha da manca dle funsion an sto mòdul e a travajërà pa an costa configurassion.
+S'a fa giré mandrake, ch'a instala ël pachet php-xml.",
+ 'config-pcre' => "A smija che ël mòdul d'apògg PCRE a sia mancant.
+MediaWiki a l'ha da manca dle funsion d'espression regolar Perl-compatìbij për marcé.",
+ 'config-memory-raised' => "<code>memory_limit</code> ëd PHP a l'é $1, aussà a $2.",
+ 'config-memory-bad' => "'''Avis:''' <code>memory_limit</code> ëd PHP a l'é $1.
+Sossì a l'é probabilment tròp bass.
+L'instalassion a peul falì!",
+ 'config-xcache' => "[http://trac.lighttpd.net/xcache/ XCache] a l'é instalà",
+ 'config-apc' => "[http://www.php.net/apc APC] a l'é instalà",
+ 'config-eaccel' => "[http://eaccelerator.sourceforge.net/ eAccelerator] a l'é instalà",
+ 'config-wincache' => "[http://www.iis.net/download/WinCacheForPhp WinCache] a l'é instalà",
+ 'config-no-cache' => "'''Avis:''' As treuva pa [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache]. Ël buté d'oget an memòria local a l'é pa abilità.",
+ 'config-diff3-bad' => 'GNU diff3 pa trovà.',
+ 'config-imagemagick' => "Trovà ImageMagick: <code>$1</code>.
+La miniaturisassion ëd figure a sarà abilità s'it abìlite le carie.",
+ 'config-gd' => "Trovà la librarìa gràfica antëgrà GD.
+La miniaturisassion ëd figure a sarà abilità s'a abìlita ij cariament.",
+ 'config-no-scaling' => 'As treuva pa la librarìa GD o ImageMagick.
+La miniaturisassion ëd figure a sarà disabilità.',
+ 'config-no-uri' => "'''Eror:''' As peul pa determiné l'URI corenta.
+Instalassion abortìa.",
+ 'config-uploads-not-safe' => "'''Avis:''' Sò dossié stàndard për carié <code>$1</code> a l'é vulneràbil a l'esecussion ëd qualsëssìa senari.
+Bele che MediaWiki a contròla j'aspet ëd sicurëssa ëd tùit j'archivi carià, a l'é motobin arcomandà ëd [http://www.mediawiki.org/wiki/Manual:Security#Upload_security saré ës përtus ëd sicurëssa] prima d'abilité ij cariament.",
+ 'config-db-type' => 'Sòrt ëd base ëd dàit:',
+ 'config-db-host' => 'Ospitant ëd la base ëd dàit:',
+ 'config-db-host-help' => "Se sò servent ëd base ëd dàit a l'é su un servent diferent, ch'a anseriss ambelessì ël nòm dl'ospitant o l'adrëssa IP.
+
+S'a deuvra n'ospitalità partagià, sò fornidor d'ospitalità a dovrìa deje ël nòm dl'ospitant giust ant soa documentassion.
+
+Se a anstala su un servent Windows e a deuvra MySQL, dovré \"localhost\" a podrìa funsioné nen com nòm dël servent. S'a marcia nen, ch'a preuva \"127.0.0.1\" com adrëssa IP local.",
+ 'config-db-host-oracle' => 'TNS dla base ëd dàit:',
+ 'config-db-host-oracle-help' => "Anserì un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nòm ëd conession local] bon; n'archivi tnsnames.ora a dev esse visìbil da costa anstalassion..<br />S'a deuvra le librarìe cliente 10g o pi neuve a peul ëdcò dovré ël métod ëd nominassion [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ 'config-db-wiki-settings' => 'Identìfica sta wiki',
+ 'config-db-name' => 'Nòm dla base ëd dàit:',
+ 'config-db-name-help' => "Ch'a serna un nòm ch'a identìfica soa wiki.
+A dovrìa conten-e gnun ëspassi o tratin.
+
+S'a deuvra n'ospitalità partagià, sò fornidor ëd l'ospitalità a-j darà un nòm ëd base ëd dàit specìfich da dovré, o a lassrà ch'a lo crea via un panel ëd contròl.",
+ 'config-db-name-oracle' => 'Schema dla base ëd dàit:',
+ 'config-db-install-account' => "Cont d'utent për l'instalassion.",
+ 'config-db-username' => "Nòm d'utent dla base ëd dàit:",
+ 'config-db-password' => 'Ciav dla base ëd dàit:',
+ 'config-db-install-help' => "Ch'a anserissa lë stranòm d'utent e la ciav che a saran dovrà për coleghesse a la base ëd dàit durant ël process d'instalassion.",
+ 'config-db-account-lock' => "Dovré ij midem stranòm d'utent e ciav durant j'operassion normaj",
+ 'config-db-wiki-account' => "Cont d'utent për j'operassion normaj",
+ 'config-db-wiki-help' => "Ch'a anseriss lë stranòm d'utent e la ciav che a saran dovrà për coleghesse a la base ëd dàit durant j'operassion normaj dla wiki.
+S'ël cont a esist pa, e ël cont d'instalassion a l'ha ij privilegi ch'a-i van, sto cont utent a sarà creà con ij privilegi mìnin për fé marcé la wiki.",
+ 'config-db-prefix' => 'Prefiss dle tàule dla base ëd dàit:',
+ 'config-db-prefix-help' => "S'a l'ha dabzògn ëd partagé na base ëd dàit an tra vàire wiki, o tra MediaWiki e n'àutra aplicassion dl'aragnà, a peul serne ëd gionté un prefiss a tùit ij nòm ëd le tàule për evité ëd conflit.
+Ch'a deuvra ni dë spassi ni ëd tratin.
+
+Cost camp a l'é lassà normalment veuid.",
+ 'config-db-charset' => 'Ansema dij caràter dla base ëd dàit',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binari',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => "MySQL 4.0 compatìbil a l'andaré con UTF-8",
+ 'config-charset-help' => "'''Avis:''' S'a deuvra '''UTF-8 compatìbil a l'andaré''' su MySQL 4.1+, e peui a fa na còpia con <code>mysqldump</code>, a podrìa scancelé tùit ij caràter nen-ASCII, dësbland sensa speranse soe còpie!
+
+An '''manera binaria''', MediaWiki a memorisa ël test UTF-8 an dij camp binari ant la base ëd dàit.
+Sossì a l'é pi eficient che la manera UTF-8 ëd MySQL, e a përmët ëd dovré tut l'ansema ëd caràter Unicode.
+An '''manera UTF-8''', MySQL a arconòss an che ansema ëd caràter a son ij sò dat, e a peul presenteje e convertije apropriatament, ma a-j lassrà pa memorisé ij caràter dzora al [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes pian multilenghe ëd base].",
+ 'config-mysql-old' => "A-i é da manca ëd MySQL $1 o pi recent, chiel a l'ha $2.",
+ 'config-db-port' => 'Porta dla base ëd dàit:',
+ 'config-db-schema' => 'Schema për MediaWiki',
+ 'config-db-schema-help' => "Jë schema sì-dzora a son normalment giust.
+Ch'a-j cangia mach s'a sa ch'a n'ha da manca.",
+ 'config-sqlite-dir' => 'Dossié dij dat SQLite:',
+ 'config-sqlite-dir-help' => "SQLite a memorisa tùit ij dat ant n'archivi ùnich.
+
+Ël dossié che chiel a forniss a dev esse scrivìbil dal servent durant l'instalassion.
+
+A dovrìa '''pa''' esse acessìbil da l'aragnà, sossì a l'é për sòn ch'i l'oma pa butalo andova a-i son ij sò file PHP.
+
+L'instalador a scriverà n'archivi <code>.htaccess</code> ansema con chiel, ma se lòn a faliss quaidun a peul intré an soa base ëd dàit originaria.
+Lòn a comprend ij dat brut ëd l'utent (adrëssa ëd pòsta eletrònica, ciav tërbola) e ëdcò le revision scancelà e d'àutri dat segret ëd la wiki.
+
+Ch'a consìdera ëd buté la base ëd dàit tuta antrega da n'àutra part, për esempi an <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Spassi dla tàula dë stàndard:',
+ 'config-oracle-temp-ts' => 'Spassi dla tàula temporani:',
+ 'config-support-info' => "MediaWiki a manten ij sistema ëd base ëd dàit sì-dapress:
+
+$1
+
+S'a vëd pa listà sì-sota ël sistema ëd base ëd dàit ch'a preuva a dovré, antlora va andaré a j'istrussion dl'anliura sì-dzora për abilité ël manteniment.",
+ 'config-support-mysql' => "* $1 e l'é l'obietiv primari për MediaWiki e a l'é mej mantnù ([http://www.php.net/manual/en/mysql.installation.php com compilé PHP con ël manteniment MySQL])",
+ 'config-support-postgres' => "* $1 e l'é un sistema ëd base ëd dàit popolar a sorgiss duverta com alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php com compilé PHP con ël manteniment ëd PostgreSQL])",
+ 'config-support-sqlite' => "* $1 e l'é un sistema ëd base ëd dàit leger che a l'é motobin bin mantnù ([http://www.php.net/manual/en/pdo.installation.php com compilé PHP con ël manteniment ëd SQLite], a deuvra PDO)",
+ 'config-support-oracle' => "* $1 a l'é na base ëd dàit comersial për j'amprèise. ([http://www.php.net/manual/en/oci8.installation.php Com compilé PHP con ël manteniment OCI8])",
+ 'config-header-mysql' => 'Ampostassion MySQL',
+ 'config-header-postgres' => 'Ampostassion PostgreSQL',
+ 'config-header-sqlite' => 'Ampostassion SQLite',
+ 'config-header-oracle' => 'Ampostassion Oracle',
+ 'config-invalid-db-type' => 'Sòrt ëd ëd base ëd dàit pa bon-a',
+ 'config-missing-db-name' => 'A dev buteje un valor për "Nòm ëd la base ëd dàit"',
+ 'config-missing-db-server-oracle' => 'A dev buteje un valor për "TNS ëd la base ëd dat"',
+ 'config-invalid-db-server-oracle' => 'TNS ëd la base ëd dat pa bon "$1".
+Dovré mach dle litre ASCII (a-z, A-Z), nùmer (0-9), sotlignadure (_) e pontin (.).',
+ 'config-invalid-db-name' => 'Nòm ëd la base ëd dàit pa bon "$1".
+Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9) e sotlignadure (_).',
+ 'config-invalid-db-prefix' => 'Prefiss dla base ëd dàit pa bon "$1".
+Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9) e sotlignadure (_).',
+ 'config-connection-error' => "$1.
+
+Controla l'ospitant, lë stranòm d'utent e la ciav sì-sota e prové torna.",
+ 'config-invalid-schema' => 'Schema pa bon për MediaWiki "$1".
+Dovré mach litre ASCII (a-z, A-Z), nùmer (0-9) e sotlignadure (_).',
+ 'config-postgres-old' => "A-i é da manca ëd PostgreSQL $1 o pi recent, chiel a l'ha $2.",
+ 'config-sqlite-name-help' => "Serne un nòm ch'a identìfica soa wiki.
+Dovré nì dë spassi nì ëd tratin.
+Sòn a sarà dovrà për ël nòm ëd l'archivi ëd dat SQLite.",
+ 'config-sqlite-parent-unwritable-group' => "As peul pa creesse ël dossié ëd dat <code><nowiki>$1</nowiki></code>, përchè ël dossié a mont <code><nowiki>$2</nowiki></code> a l'é pa scrivìbil dal servent.
+
+L'instalador a l'ha determinà sota che utent a gira sò servent.
+Fé an manera che ël dossié <code><nowiki>$3</nowiki></code> a sia scrivìbil da chiel për continué.
+Su un sistema Unix/Linux buté:
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>",
+ 'config-sqlite-parent-unwritable-nogroup' => "As peul pa creesse ël dossié ëd dat <code><nowiki>$1</nowiki></code>, përchè ël dossié a mont <code><nowiki>$2</nowiki></code> a l'é pa scrivìbil dal servent.
+
+L'instalador a peul pa determiné l'utent sota ël qual a gira sò servent.
+Fé an manera che ël dossié <code><nowiki>$3</nowiki></code> a sia scrivìbil globalment da chiel (e da d'àutri) për continué.
+Su un sistema Unix/Linux buté:
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>",
+ 'config-sqlite-mkdir-error' => 'Eror an creand ël dossié ëd dat "$1".
+Ch\'a contròla la locassion e ch\'a preuva torna.',
+ 'config-sqlite-dir-unwritable' => 'As peul pa scrivse an sël dossié "$1".
+Modifiché ij sò përmess an manera che ël servent a peula scrivje ansima, e prové torna.',
+ 'config-sqlite-connection-error' => '$1.
+
+Controlé ël dossié ëd dat e ël nòm ëd la base ëd dàit ambelessì-sota e prové torna.',
+ 'config-sqlite-readonly' => "L'archivi <code>$1</code> a l'é nen scrivìbil.",
+ 'config-sqlite-cant-create-db' => "As peul pa cresse l'archivi ëd base ëd dàit <code>$1</code>.",
+ 'config-sqlite-fts3-downgrade' => "PHP a l'ha pa ël supòrt ëd FTS3, le tàule a son degradà",
+ 'config-can-upgrade' => "A-i é dle tàule MediaWiki an costa base ëd dàit.
+Për agiorneje a MediaWiki $1, ch'a sgnaca su '''Continué'''.",
+ 'config-upgrade-done' => "Agiornament completà.
+
+Adess a peule [$1 ancaminé a dovré soa wiki].
+
+S'a veul generé torna sò archivi <code>LocalSettings.php</code>, ch'a sgnaca ël boton sì-sota.
+Sòn a l'è '''pa arcomandà''' gavà ch'a rancontra dij problema con soa wiki.",
+ 'config-regenerate' => 'Generé torna LocalSettings.php →',
+ 'config-show-table-status' => 'Arcesta SHOW TABLE STATUS falìa!',
+ 'config-unknown-collation' => "'''Avis:''' La base ëd dàit a deuvra na classificassion pa arconossùa.",
+ 'config-db-web-account' => "Cont dla base ëd dàit për l'acess a l'aragnà",
+ 'config-db-web-help' => "Ch'a selession-a lë stranòm d'utent e la ciav che ël servent ëd l'aragnà a dovrërà për coleghesse al servent dle base ëd dàit, durant j'operassion ordinarie dla wiki.",
+ 'config-db-web-account-same' => "Ch'a deuvra ël midem cont com për l'istalassion",
+ 'config-db-web-create' => "Crea ël cont se a esist pa anco'",
+ 'config-db-web-no-create-privs' => "Ël cont ch'a l'ha specificà për l'instalassion a l'ha pa basta 'd privilegi për creé un cont.
+Ël cont ch'a spessìfica ambelessì a dev già esiste.",
+ 'config-mysql-engine' => 'Motor ëd memorisassion:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' a l'é scasi sempe la mej opsion, da già ch'a l'ha un bon manteniment dla concorensa.
+
+'''MyISAM''' a peul esse pi lest an instalassion për n'utent sol o mach an letura.
+La base ëd dàit MyISAM a tira a corompse pi 'd soens che la base ëd dàit InnoDB.",
+ 'config-mysql-charset' => 'Ansem ëd caràter dla base ëd dàit:',
+ 'config-mysql-binary' => 'Binari',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "An '''manera binaria''', MediaWiki a memorisa ël test UTF-8 ant la base ëd dàit an camp binari.
+Sòn a l'é pi eficient che la manera UTF-8 ëd MySQL, e a-j përmët ëd dovré l'ansema antregh ëd caràter Unicode.
+
+An '''manera UTF-8''', MySQL a conossrà an che ansem ëd caràter a son ij sò dat, e a peul presenteje e convertije apropriatament, ma a-j lassa pa memorisé ij caràter ëdzora al [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes pian multilenghìstich ëd base].",
+ 'config-site-name' => 'Nòm ëd la wiki:',
+ 'config-site-name-help' => "Sòn a comparirà ant la bara dël tìtol dël navigador e an vàire d'àutri pòst.",
+ 'config-site-name-blank' => "Ch'a buta un nòm ëd sit.",
+ 'config-project-namespace' => 'Spassi nominal dël proget:',
+ 'config-ns-generic' => 'Proget',
+ 'config-ns-site-name' => 'Midem com ël nom dla wiki: $1',
+ 'config-ns-other' => 'Àutr (specìfica)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-project-namespace-help' => "Andasend daré a l'esempi ëd Wikipedia, vàire wiki a manten-o soe pàgine ëd regolament separà da soe pàgine ëd contnù, ant në \"'''spassi nominal ëd proget'''\".
+Tùit ij tìtoj ëd pàgina ant cost ëspassi nominal a parto con un sert prefiss, che a peul specifiché ambelessì.
+Tradissionalment, sto prefiss a l'é derivà dal nòm ëd la wiki, ma a peul pa conten-e caràter ëd pontegiatura coma \"#\" o \":\".",
+ 'config-ns-invalid' => 'Lë spassi nominal specificà "<nowiki>$1</nowiki>" a l\'é pa bon.
+Specìfica në spassi nominal ëd proget diferent.',
+ 'config-admin-box' => "Cont ëd l'Aministrator",
+ 'config-admin-name' => 'Tò nòm:',
+ 'config-admin-password' => 'Ciav:',
+ 'config-admin-password-confirm' => 'Buté torna la ciav:',
+ 'config-admin-help' => "Ch'a butà ambelessì tò stranòm d'utent preferì, për esempi \"Gioann Scriv\".
+Cost-sì a l'é lë stranòm ch'a dovrërà për intré ant la wiki.",
+ 'config-admin-name-blank' => "Ch'a anserissa në stranòm d'aministrator.",
+ 'config-admin-name-invalid' => 'Ël nòm utent specificà "<nowiki>$1</nowiki>" a l\'é pa bon.
+Specìfica un nòm utent diferent.',
+ 'config-admin-password-blank' => "Ch'a anserissa na ciav për ël cont d'aministrator.",
+ 'config-admin-password-same' => "La ciav a dev nen esse l'istessa ëd lë stranòm d'utent.",
+ 'config-admin-password-mismatch' => "Le doe ciav che a l'ha scrivù a son diferente antra 'd lor.",
+ 'config-admin-email' => 'Adrëssa ëd pòsta eletrònica:',
+ 'config-admin-email-help' => "Ch'a anserissa ambelessì n'adrëssa ëd pòsta eletrònica për përmëtt-je d'arsèive ëd mëssagi da d'àutri utent an sla wiki, riamposté soa ciav, e esse anformà ëd camgiament a le pàgine ch'a ten sot-euj.",
+ 'config-admin-error-user' => 'Eror antern an creand n\'aministrator con lë stranòm "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Eror antern an ampostand na ciav për l\'admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-subscribe' => "Ch'a sot-scriva la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista ëd discussion ëd j'anonsi ëd publicassion].",
+ 'config-subscribe-help' => "Costa a l'é na lista ëd discussion a bass tràfich dovrà për j'anonsi ëd publicassion, comprèis d'amportant anonsi ëd sicurëssa.
+A dovrìa sot-ëscrivla e agiorné soa instalassion mediaWiki quand che ëd version neuve a rivo.",
+ 'config-almost-done' => "A l'ha bele che fàit!
+A peul adess sauté la configurassion rimanenta e instalé dlongh la wiki.",
+ 'config-optional-continue' => "Ciameme d'àutre chestion.",
+ 'config-optional-skip' => 'I son già anojà, instala mach la wiki.',
+ 'config-profile' => "Profil dij drit d'utent:",
+ 'config-profile-wiki' => 'Wiki tradissional',
+ 'config-profile-no-anon' => 'A venta creé un cont',
+ 'config-profile-fishbowl' => 'Mach editor autorisà',
+ 'config-profile-private' => 'Wiki privà',
+ 'config-profile-help' => "Le wiki a marcio mej quand ch'a lassa che pì përsone possìbij a-j modìfico.
+An MediaWiki, a l'é bel fé revisioné ij cambi recent, e buté andré minca dann che a sia fàit da utent noviss o malissios.
+
+An tùit ij cas, an tanti a l'han trovà che MediaWiki a sia ùtil ant na gran varietà ëd manere, e dle vire a l'é pa bel fé convince cheidun dij vantagi dla wiki.
+Parèj a l'ha doe possibilità.
+
+Un '''{{int:config-profile-wiki}}''' a përmët a chicassìa ëd modifiché, bele sensa intré ant ël sistema.
+Na wiki con '''{{int:config-profile-no-anon}}''' a dà pì 'd contròl, ma a peul slontané dij contribudor casuaj.
+
+Ël senari '''{{int:config-profile-fishbowl}}''' a përmët a j'utent aprovà ëd modifiché, ma ël pùblich a peul vëdde le pàgine, comprèisa la stòria.
+Un '''{{int:config-profile-private}}''' a përmët mach a j'utent aprovà ëd vëdde le pàgine, con la midema partìa ch'a peul modifiché.
+
+Configurassion ëd drit d'utent pi complicà a son disponìbij apress l'instalassion, vëdde la [http://www.mediawiki.org/wiki/Manual:User_rights pàgina a pòsta dël manual].",
+ 'config-license' => "Drit d'autor e licensa",
+ 'config-license-none' => 'Gnun-a licensa an nòta an bass',
+ 'config-license-cc-by-sa' => 'Creative Commons atribussion an part uguaj',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons atribussion nen comersial an part uguaj',
+ 'config-license-gfdl-old' => 'Licensa ëd documentassion lìbera GNU 1.2',
+ 'config-license-gfdl-current' => 'Licensa ëd documentassion lìbera GNU 1.3 o pi recenta',
+ 'config-license-pd' => 'Domini Pùblich',
+ 'config-license-cc-choose' => 'Selessioné na licensa Creative Commons përsonalisà',
+ 'config-license-help' => "Vàire wiki pùbliche a buto tute le contribussion sota na [http://freedomdefined.org/Definition licensa lìbera]. Sòn a giuta a creé un sens d'apartenensa a la comunità e a ancoragia ëd contribussion ëd longa durà.
+A l'é generalment nen necessari për na wiki privà o d'asienda.
+
+S'a veul podèj dovré dij test da Wikipedia, e a veul che Wikipedia a aceta dij test copià da soa wiki, a dovrìa serne '''Creative Commons Attribution Share Alike'''.
+
+La GNU Free Documentation License a l'era la veja licensa dont sota a-i era Wikipedia.
+A l'é anco' na licensa bon-a, an tùit ij cas, sta licensa a l'ha chèich funsion ch'a rendo difìcij l'utilisassion e l'antërpretassion.",
+ 'config-email-settings' => 'Ampostassion ëd pòsta eletrònica',
+ 'config-enable-email' => 'Abilité ij mëssagi ëd pòsta eletrònica an surtìa',
+ 'config-enable-email-help' => "S'a veul che la pòsta eletrònica a marcia, j'[http://www.php.net/manual/en/mail.configuration.php ampostassion ëd pòsta eletrònica PHP] a devo esse configurà për da bin.
+S'a veul pa 'd funsion ëd pòsta eletrònica, a dev disabiliteje ambelessì.",
+ 'config-email-user' => 'Abilité ij mëssagi ëd pòsta eletrònica da utent a utent',
+ 'config-email-user-help' => "A përmët a tùit j'utent ëd mandesse ëd mëssagi ëd pòsta eletrònica se lor a l'han abilità sòn an soe preferense.",
+ 'config-email-usertalk' => "Abilité notìfica dle pàgine ëd discussion dj'utent",
+ 'config-email-usertalk-help' => "A përmët a j'utent d'arsèive na notìfica dle modìfiche dle pàgine ëd discussion d'utent, s'a l'han abilitalo ant soe preferense.",
+ 'config-email-watchlist' => "Abilité la notìfica ëd lòn ch'as ten sot euj",
+ 'config-email-watchlist-help' => "A përmët a j'utent d'arsèive dle notificassion a propòsit dle pàgine ch'a ten-o sot euj s'a l'han abilitalo ant soe preferense.",
+ 'config-email-auth' => "Abilité l'autenticassion për pòsta eletrònica",
+ 'config-email-auth-help' => "Se st'opsion a l'é abilità, j'utent a devo confirmé soe adrësse ëd pòsta eletrònica an dovrand un colegament mandà a lor quand ch'a l'han ampostala o cambiala.
+Mach j'adrësse ëd pòsta eletrònica autenticà a peulo arsèive ëd mëssagi da j'àutri utent o cangé adrëssa ëd notìfica.
+Amposté st'opsion a l'é '''arcomandà''' për le wiki pùbliche a càusa ëd possìbij abus ëd le funsion ëd pòsta eletrònica.",
+ 'config-email-sender' => 'Adrëssa ëd pòsta eletrònica ëd ritorn:',
+ 'config-email-sender-help' => "Ch'a anserissa l'adrëssa ëd pòsta eletrònica da dovré com adrëssa d'artorn dij mëssagi an surtìa.
+Ambelessì a l'é andova j'arspòste a saran mandà.
+Motobin ëd servent ëd pòsta a ciamo che almanch la part dël nòm ëd domini a sia bon-a.",
+ 'config-upload-settings' => 'Cariament ëd figure e archivi',
+ 'config-upload-enable' => "Abilité ël cariament d'archivi",
+ 'config-upload-help' => "Carié d'archivi potensialment a espon sò servent a d'arzigh ëd sicurëssa.
+Per pi d'anformassion, ch'a lesa la [http://www.mediawiki.org/wiki/Manual:Security session ëd sicurëssa] d'ës manual.
+
+Për abilité ël cariament d'archivi, ch'a modìfica la manera dël sot-dossié dle <code>figure</code> sota al dossié rèis ëd MediaWiki an manera che ël servent dl'aragnà a peussa scrivlo.
+Peui ch'a abìlita costa opsion.",
+ 'config-upload-deleted' => "Dossié për j'archivi scancelà:",
+ 'config-upload-deleted-help' => "ch'a serna un dossié andova goerné j'archivi scancelà.
+Idealment, sòn a dovrìa pa esse acessìbil an sl'aragnà.",
+ 'config-logo' => 'Anliura dla marca:',
+ 'config-logo-help' => "La pel dë stàndard ëd MediaWiki a comprend lë spassi për na marca ëd 135x160 pontin ant ël canton an àut a snista.
+Ch'a dëscaria na figura ëd la dimension aproprià, e ch'a anserissa l'anliura ambelessì.
+
+S'a veul gnun-e marche, ch'a lassa ës camp bianch.",
+ 'config-instantcommons' => 'Abìlita Instant Commons',
+ 'config-instantcommons-help' => "[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] a l'é na funsion ch'a përmët a le wiki ëd dovré dle figure, dij son e d'àutri mojen trovà an sël sit [http://commons.wikimedia.org/ Wikimedia Commons].
+Për dovré sossì, MediaWiki a l'ha da manca dl'acess a la ragnà.
+
+Për pi d'anformassion su sta funsion, comprèise j'istrussion ëd com ampostela për wiki diferente da Wikimedia Commons, ch'a consulta [http://mediawiki.org/wiki/Manual:\$wgForeignFileRepos ël manual].",
+ 'config-cc-error' => "La selession ëd la licensa Creative Commons a l'ha dàit gnun arzultà.
+Ch'a anserissa ël nòm dla licensa a man.",
+ 'config-cc-again' => 'Torna cheuje...',
+ 'config-cc-not-chosen' => 'Sern che licensa Creative Commons it veule e sgnaca "anans".',
+ 'config-advanced-settings' => 'Configurassion avansà',
+ 'config-cache-options' => "Ampostassion për la memorisassion local d'oget:",
+ 'config-cache-help' => "La memorisassion loca d'oget a l'é dovrà për amelioré l'andi ëd MediaWiki an butant an local dij dat dovrà 'd soens.
+Ij sit da mesan a gròss a son motobin ancoragià a abilité sòn, e ij sit cit a l'avran ëdcò dij benefissi.",
+ 'config-cache-none' => "Gnun-a memorisassion local (gnun-a funsionalità gavà, ma l'andi a peul esse anfluensà an sij sit ëd wiki gròsse)",
+ 'config-cache-accel' => "Memorisassion local d'oget PHP (APC, eAccelerator, XCache o WinCache)",
+ 'config-cache-memcached' => "Dovré Memcached (a ciama n'ampostassion e na configurassion adissionaj)",
+ 'config-memcached-servers' => 'Servent Memcached:',
+ 'config-memcached-help' => "Lista d'adrësse IP da dovré për Memcached.
+A dovrìa esse separà con dle vìrgole e specifiché la pòrta da dovré (për esempi: 127.0.0.1:11211, 192.168.1.25:11211).",
+ 'config-extensions' => 'Estension',
+ 'config-extensions-help' => "J'estension listà dì-sota a son ëstàite trovà ant sò dossié <code>./extensions</code>.
+
+A peulo avèj da manca ëd configurassion adissionaj, ma a peul abiliteje adess",
+ 'config-install-alreadydone' => "'''Avis''' A smija ch'a l'abie già instalà MediaWiki e ch'a preuva a instalelo torna.
+Për piasì, ch'a vada a la pàgina ch'a-i ven.",
+ 'config-install-step-done' => 'fàit',
+ 'config-install-step-failed' => 'falì',
+ 'config-install-extensions' => "Comprende j'estension",
+ 'config-install-database' => 'Creassion ëd la base ëd dàit',
+ 'config-install-pg-schema-failed' => 'Creassion dle tàule falìa.
+Sigurte che l\'utent "$1" a peussa scrive lë schema "$2".',
+ 'config-install-user' => "Creassion ëd n'utent ëd la base ëd dàit",
+ 'config-install-user-grant-failed' => 'Falì a dé ij përmess a l\'utent "$1": $2',
+ 'config-install-tables' => 'Creassion dle tàule',
+ 'config-install-tables-exist' => "'''Avis''': A smija che le tàule ëd mediaWiki a esisto già.
+Sauté la creassion.",
+ 'config-install-tables-failed' => "'''Eror''': Creassion ëd le tàule falìa con l'eror sì-dapress: $1",
+ 'config-install-interwiki' => "Ampiniment dë stàndard ëd le tàule dj'anliure interwiki",
+ 'config-install-interwiki-list' => "As peul pa trovesse l'archivi <code>interwiki.list</code>.",
+ 'config-install-interwiki-exists' => "'''Avis''': La tàula interwiki a smija ch'a l'abia già dj'element.
+Për stàndard, la lista a sarà sautà.",
+ 'config-install-keys' => 'Generassion ëd la ciav segreta',
+ 'config-install-sysop' => "Creassion dël cont ëd l'utent aministrator",
+ 'config-install-done' => "'''Congratulassion!'''
+A l'ha instalà për da bin mediaWiki.
+
+L'instalador a l'ha generà n'archivi <code>LocalSettings.php</code>.
+A conten tuta soa configurassion.
+
+A dovrà [$1 dëscarielo] e butelo ant la bas ëd l'instalassion ëd soa wiki (ël midem dossié d'index.php).
+'''Nòta''': S'a lo fa nen adess, cost archivi ëd configurassion generà a sarà pa disponìbil për chiel pi tard s'a chita l'instalassion sensa dëscarielo.
+
+Quand che a l'é stàit fàit, a peul '''[$2 intré an soa wiki]'''.",
+);
+
+/** Pashto (پښتو)
+ * @author Ahmed-Najib-Biabani-Ibrahimkhel
+ */
+$messages['ps'] = array(
+ 'config-your-language' => 'ستاسې ژبه:',
+ 'config-wiki-language' => 'د ويکي ژبه:',
+ 'config-page-language' => 'ژبه',
+ 'config-page-welcome' => 'مېډياويکي ته ښه راغلاست!',
+ 'config-page-name' => 'نوم',
+ 'config-page-install' => 'لګول',
+ 'config-page-complete' => 'بشپړ!',
+ 'config-env-php' => 'د $1 PHP نصب شو.',
+);
+
+/** Portuguese (Português)
+ * @author Crazymadlover
+ * @author Hamilton Abreu
+ * @author Platonides
+ * @author SandroHc
+ * @author Waldir
+ */
+$messages['pt'] = array(
+ 'config-desc' => 'O instalador do MediaWiki',
+ 'config-title' => 'Instalação MediaWiki $1',
+ 'config-information' => 'Informação',
+ 'config-localsettings-upgrade' => 'Foi detectado um ficheiro <code>LocalSettings.php</code>.
+Para actualizar esta instalação, por favor introduza o valor de <code>$wgUpgradeKey</code> na caixa abaixo.
+Encontra este valor no LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Foi detectada a existência de um ficheiro LocalSettings.php.
+Para actualizar esta instalação execute o update.php, por favor.',
+ 'config-localsettings-key' => 'Chave de actualização:',
+ 'config-localsettings-badkey' => 'A chave que forneceu está incorreta.',
+ 'config-upgrade-key-missing' => 'Foi detectada uma instalação existente do MediaWiki.
+Para actualizar esta instalação, por favor coloque a seguinte linha no final do seu LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'O ficheiro LocalSettings.php existente parece estar incompleto.
+A variável $1 não está definida.
+Por favor defina esta variável no LocalSettings.php e clique "Continuar".',
+ 'config-localsettings-connection-error' => 'Ocorreu um erro ao ligar à base de dados usando as configurações especificadas no LocalSettings.php ou AdminSettings.php. Por favor corrija essas configurações e tente novamente.
+
+$1',
+ 'config-session-error' => 'Erro ao iniciar a sessão: $1',
+ 'config-session-expired' => 'Os seus dados de sessão parecem ter expirado.
+As sessões estão configuradas para uma duração de $1.
+Pode aumentar esta duração configurando <code>session.gc_maxlifetime</code> no php.ini.
+Reinicie o processo de instalação.',
+ 'config-no-session' => 'Os seus dados de sessão foram perdidos!
+Verifique o seu php.ini e certifique-se de que em <code>session.save_path</code> está definido um directório apropriado.',
+ 'config-your-language' => 'A sua língua:',
+ 'config-your-language-help' => 'Seleccione a língua que será usada durante o processo de instalação.',
+ 'config-wiki-language' => 'Língua da wiki:',
+ 'config-wiki-language-help' => 'Seleccione a língua que será predominante na wiki.',
+ 'config-back' => '← Voltar',
+ 'config-continue' => 'Continuar →',
+ 'config-page-language' => 'Língua',
+ 'config-page-welcome' => 'Bem-vindo(a) ao MediaWiki!',
+ 'config-page-dbconnect' => 'Ligar à base de dados',
+ 'config-page-upgrade' => 'Actualizar a instalação existente',
+ 'config-page-dbsettings' => 'Configurações da base de dados',
+ 'config-page-name' => 'Nome',
+ 'config-page-options' => 'Opções',
+ 'config-page-install' => 'Instalar',
+ 'config-page-complete' => 'Terminado!',
+ 'config-page-restart' => 'Reiniciar a instalação',
+ 'config-page-readme' => 'Leia-me',
+ 'config-page-releasenotes' => 'Notas de lançamento',
+ 'config-page-copying' => 'A copiar',
+ 'config-page-upgradedoc' => 'A actualizar',
+ 'config-page-existingwiki' => 'Wiki existente',
+ 'config-help-restart' => 'Deseja limpar todos os dados gravados que introduziu e reiniciar o processo de instalação?',
+ 'config-restart' => 'Sim, reiniciar',
+ 'config-welcome' => '=== Verificações do ambiente ===
+São realizadas verificações básicas para determinar se este ambiente é apropriado para instalação do MediaWiki.
+Se necessitar de pedir ajuda durante a instalação, deve fornecer os resultados destas verificações.',
+ 'config-copyright' => "=== Direitos de autor e Termos de uso ===
+
+$1
+
+Este programa é software livre; pode redistribuí-lo e/ou modificá-lo nos termos da licença GNU General Public License, tal como publicada pela Free Software Foundation; tanto a versão 2 da Licença, como (por opção sua) qualquer versão posterior.
+
+Este programa é distribuído na esperança de que seja útil, mas '''sem qualquer garantia'''; inclusive, sem a garantia implícita da '''possibilidade de ser comercializado''' ou de '''adequação para qualquer finalidade específica'''.
+Consulte a licença GNU General Public License para mais detalhes.
+
+Em conjunto com este programa deve ter recebido <doclink href=Copying>uma cópia da licença GNU General Public License</doclink>; se não a recebeu, peça-a por escrito para Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA ou [http://www.gnu.org/copyleft/gpl.html leia-a na internet].",
+ 'config-sidebar' => '* [http://www.mediawiki.org/wiki/MediaWiki/pt Página principal do MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents/pt Ajuda]
+* [http://www.mediawiki.org/wiki/Manual:Contents/pt Manual técnico]
+* [http://www.mediawiki.org/wiki/Manual:FAQ FAQ]
+----
+* <doclink href=Readme>Leia-me</doclink>
+* <doclink href=ReleaseNotes>Notas de lançamento</doclink>
+* <doclink href=Copying>Cópia</doclink>
+* <doclink href=UpgradeDoc>Atualização</doclink>',
+ 'config-env-good' => 'O ambiente foi verificado.
+Pode instalar o MediaWiki.',
+ 'config-env-bad' => 'O ambiente foi verificado.
+Não pode instalar o MediaWiki.',
+ 'config-env-php' => 'O PHP $1 está instalado.',
+ 'config-env-php-toolow' => 'O PHP $1 está instalado.
+No entanto, o MediaWiki requer o PHP $2 ou superior.',
+ 'config-unicode-using-utf8' => 'A usar o utf8_normalize.so, por Brian Viper, para a normalização Unicode.',
+ 'config-unicode-using-intl' => 'A usar a [http://pecl.php.net/intl extensão intl PECL] para a normalização Unicode.',
+ 'config-unicode-pure-php-warning' => "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efectuar a normalização Unicode. Irá recorrer-se à implementação em PHP puro, que é mais lenta.
+Se o seu site tem alto volume de tráfego, devia informar-se um pouco sobre a [http://www.mediawiki.org/wiki/Unicode_normalization_considerations/pt normalização Unicode].",
+ 'config-unicode-update-warning' => "'''Aviso''': A versão instalada do wrapper de normalização Unicode usa uma versão mais antiga da biblioteca do [http://site.icu-project.org/ projecto ICU].
+Devia [http://www.mediawiki.org/wiki/Unicode_normalization_considerations actualizá-la] se tem quaisquer preocupações sobre o uso do Unicode.",
+ 'config-no-db' => "Não foi possível encontrar um controlador ''(driver)'' apropriado para a base de dados!",
+ 'config-no-db-help' => "Precisa de instalar um controlador ''(driver)'' de base de dados para o PHP.
+São suportadas as seguintes bases de dados: $1.
+
+Se o seu site está alojado num servidor partilhado, peça ao fornecedor do alojamento para instalar um controlador de base de dados apropriado.
+Se fez a compilação do PHP você mesmo, reconfigure-o com um cliente de base de dados activado, usando, por exemplo, <code>./configure --with-mysql</code>.
+Se instalou o PHP a partir de um pacote Debian ou Ubuntu, então precisa de instalar também o módulo php5-mysql.",
+ 'config-no-fts3' => "'''Aviso''': O SQLite foi compilado sem o módulo [http://sqlite.org/fts3.html FTS3]; as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
+ 'config-register-globals' => "'''Aviso: A opção <code>[http://php.net/register_globals register_globals]</code> do PHP está activada.'''
+'''Desactive-a, se puder.'''
+O MediaWiki funciona mesmo assim, mas o seu servidor está exposto a potenciais vulnerabilidades de segurança.",
+ 'config-magic-quotes-runtime' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está activada!'''
+Esta opção causa corrupção dos dados de entrada, de uma forma imprevisível.
+Não pode instalar ou usar o MediaWiki a menos que esta opção seja desactivada.",
+ 'config-magic-quotes-sybase' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está activada!'''
+Esta opção causa corrupção dos dados de entrada, de uma forma imprevisível.
+Não pode instalar ou usar o MediaWiki a menos que esta opção seja desactivada.",
+ 'config-mbstring' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está activada!'''
+Esta opção causa erros e pode corromper os dados de uma forma imprevisível.
+Não pode instalar ou usar o MediaWiki a menos que esta opção seja desactivada.",
+ 'config-ze1' => "'''Fatal: A opção [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] está activada!'''
+Esta opção causa problemas significativos no MediaWiki.
+Não pode instalar ou usar o MediaWiki a menos que esta opção seja desactivada.",
+ 'config-safe-mode' => "'''Aviso:''' O [http://www.php.net/features.safe-mode safe mode] do PHP está activo.
+Este modo pode causar problemas, especialmente no upload de ficheiros e no suporte a <code>math</code>.",
+ 'config-xml-bad' => 'Falta o módulo XML do PHP.
+O MediaWiki necessita de funções deste módulo e não funcionará com esta configuração.
+Se está a executar o Mandrake, instale o pacote php-xml.',
+ 'config-pcre' => 'Parece faltar o módulo de suporte PCRE.
+Para funcionar, o MediaWiki necessita das funções de expressões regulares compatíveis com Perl.',
+ 'config-pcre-no-utf8' => "'''Fatal''': O módulo PCRE do PHP parece ter sido compilado sem suporte PCRE_UTF8.
+O MediaWiki necessita do suporte UTF-8 para funcionar correctamente.",
+ 'config-memory-raised' => 'A configuração <code>memory_limit</code> do PHP era $1; foi aumentada para $2.',
+ 'config-memory-bad' => "'''Aviso:''' A configuração <code>memory_limit</code> do PHP é $1.
+Isto é provavelmente demasiado baixo.
+A instalação poderá falhar!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] instalada',
+ 'config-apc' => '[http://www.php.net/apc APC] instalada',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] instalado',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] instalada',
+ 'config-no-cache' => "'''Aviso:''' Não foram encontrados [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] nem [http://www.iis.net/download/WinCacheForPhp WinCache].
+A cache de objectos não será activada.",
+ 'config-diff3-bad' => 'O GNU diff3 não foi encontrado.',
+ 'config-imagemagick' => 'Foi encontrado o ImageMagick: <code>$1</code>.
+Se possibilitar uploads, a miniaturização de imagens será activada.',
+ 'config-gd' => 'Foi encontrada a biblioteca gráfica GD.
+Se possibilitar uploads, a miniaturização de imagens será activada.',
+ 'config-no-scaling' => 'Não foi encontrada a biblioteca gráfica GD nem o ImageMagick.
+A miniaturização de imagens será desactivada.',
+ 'config-no-uri' => "'''Erro:''' Não foi possível determinar a URI actual.
+A instalação foi abortada.",
+ 'config-uploads-not-safe' => "'''Aviso:''' O directório por omissão para uploads <code>$1</code>, está vulnerável à execução arbitrária de scripts.
+Embora o MediaWiki verifique a existência de ameaças de segurança em todos os ficheiros enviados, é altamente recomendado que [http://www.mediawiki.org/wiki/Manual:Security#Upload_security vede esta vulnerabilidade de segurança] antes de possibilitar uploads.",
+ 'config-brokenlibxml' => 'O seu sistema tem uma combinação de versões de PHP e libxml2 conhecida por ser problemática, podendo causar corrupção de dados no MediaWiki e outras aplicações da internet.
+Actualize para o PHP versão 5.2.9 ou posterior e libxml2 versão 2.7.3 ou posterior ([http://bugs.php.net/bug.php?id=45996 incidência reportada no PHP]).
+Instalação interrompida.',
+ 'config-using531' => 'O MediaWiki não pode ser usado com o PHP $1 devido a um problema que envolve parâmetros de referência para <code>__call()</code>.
+Para resolver este problema, actualize o PHP para a versão 5.3.2 ou posterior, ou reverta-o para a 5.3.0.
+Instalação interrompida.',
+ 'config-db-type' => 'Tipo da base de dados:',
+ 'config-db-host' => 'Servidor da base de dados:',
+ 'config-db-host-help' => 'Se a base de dados estiver num servidor separado, introduza aqui o nome ou o endereço IP desse servidor.
+
+Se estiver a usar um servidor partilhado, o fornecedor do alojamento deve ter-lhe fornecido o nome do servidor na documentação.
+
+Se está a fazer a instalação num servidor Windows com MySQL, usar como nome do servidor "localhost" poderá não funcionar. Se não funcionar, tente usar "127.0.0.1" como endereço IP local.',
+ 'config-db-host-oracle' => 'TNS (Transparent Network Substrate) da base de dados:',
+ 'config-db-host-oracle-help' => 'Introduza um [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Nome Local de Ligação] válido; tem de estar visível para esta instalação um ficheiro tnsnames.ora.<br />Se está a usar bibliotecas cliente versão 10g ou posterior, também pode usar o método [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Ligação Fácil] de atribuição do nome.',
+ 'config-db-wiki-settings' => 'Identifique esta wiki',
+ 'config-db-name' => 'Nome da base de dados:',
+ 'config-db-name-help' => 'Escolha um nome para identificar a sua wiki.
+O nome não deve conter espaços.
+
+Se estiver a usar um servidor partilhado, o fornecedor do alojamento deve poder fornecer-lhe o nome de uma base de dados que possa usar, ou permite-lhe criar bases de dados através de um painel de controle.',
+ 'config-db-name-oracle' => "Esquema ''(schema)'' da base de dados:",
+ 'config-db-account-oracle-warn' => "Há três cenários suportados na instalação do servidor de base de dados Oracle:
+
+Se pretende criar a conta de acesso pela internet na base de dados durante o processo de instalação, forneça como conta para a instalação uma conta com o papel de SYSDBA na base de dados e especifique as credenciais desejadas para a conta de acesso pela internet. Se não pretende criar a conta de acesso pela internet durante a instalação, pode criá-la manualmente e fornecer só essa conta para a instalação (se ela tiver as permissões necessárias para criar os objectos do esquema ''(schema)''). A terceira alternativa é fornecer duas contas diferentes; uma com privilégios de criação e outra com privilégios limitados para o acesso pela internet.
+
+Existe um script para criação de uma conta com os privilégios necessários no directório \"maintenance/oracle/\" desta instalação. Mantenha em mente que usar uma conta com privilégios limitados impossibilita todas as operações de manutenção com a conta padrão.",
+ 'config-db-install-account' => 'Conta do utilizador para a instalação',
+ 'config-db-username' => 'Nome do utilizador da base de dados:',
+ 'config-db-password' => 'Palavra-chave do utilizador da base de dados:',
+ 'config-db-password-empty' => 'Introduza a palavra-chave do novo utilizador da base de dados: $1.
+Embora seja possível criar utilizadores sem palavra-chave, fazê-lo não é seguro.',
+ 'config-db-install-username' => 'Introduza o nome de utilizador que será usado para aceder à base de dados durante o processo de instalação. Este utilizador não é o do MediaWiki; é o utilizador da base de dados.',
+ 'config-db-install-password' => 'Introduza a palavra-chave do utilizador que será usado para aceder à base de dados durante o processo de instalação. Esta palavra-chave não é a do utilizador do MediaWiki; é a palavra-chave do utilizador da base de dados.',
+ 'config-db-install-help' => 'Introduza o nome de utilizador e a palavra-chave que serão usados para aceder à base de dados durante o processo de instalação.',
+ 'config-db-account-lock' => 'Usar o mesmo nome de utilizador e palavra-chave durante a operação normal',
+ 'config-db-wiki-account' => 'Conta de utilizador para a operação normal',
+ 'config-db-wiki-help' => 'Introduza o nome de utilizador e a palavra-chave que serão usados para aceder à base de dados durante a operação normal da wiki.
+Se o utilizador não existir na base de dados, mas a conta de instalação tiver privilégios suficientes, o utilizador que introduzir será criado na base de dados com os privilégios mínimos necessários para a operação normal da wiki.',
+ 'config-db-prefix' => 'Prefixo para as tabelas da base de dados:',
+ 'config-db-prefix-help' => 'Se necessitar de partilhar uma só base de dados entre várias wikis, ou entre o MediaWiki e outra aplicação, pode escolher adicionar um prefixo ao nome de todas as tabelas desta instalação, para evitar conflitos.
+Não use espaços.
+
+Normalmente, este campo deve ficar vazio.',
+ 'config-db-charset' => 'Conjunto de caracteres da base de dados',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 backwards-compatible UTF-8',
+ 'config-charset-help' => "'''Aviso:''' Se usar '''backwards-compatible UTF-8''' (\"UTF-8 compatível com versões anteriores\") no MySQL 4.1+, e depois fizer cópias de segurança da base de dados usando <code>mysqldump</code>, poderá destruir todos os caracteres que não fazem parte do conjunto ASCII, corrompendo assim, de forma irreversível, as suas cópias de segurança!
+
+No modo '''binary''' (\"binário\"), o MediaWiki armazena o texto UTF-8 na base de dados em campos binários.
+Isto é mais eficiente do que o modo UTF-8 do MySQL e permite que sejam usados todos os caracteres Unicode.
+No modo '''UTF-8''', o MySQL saberá em que conjunto de caracteres os seus dados estão e pode apresentá-los e convertê-los da forma mais adequada,
+mas não lhe permitirá armazenar caracteres acima do [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Básico].",
+ 'config-mysql-old' => 'É necessário o MySQL $1 ou posterior; tem a versão $2.',
+ 'config-db-port' => 'Porta da base de dados:',
+ 'config-db-schema' => "Esquema ''(schema)'' do MediaWiki",
+ 'config-db-schema-help' => 'Normalmente, este esquema ("schema") estará correcto.
+Altere-o só se souber que precisa de o fazer.',
+ 'config-sqlite-dir' => 'Directório de dados do SQLite:',
+ 'config-sqlite-dir-help' => "O SQLite armazena todos os dados num único ficheiro.
+
+Durante a instalação, o servidor de internet precisa de ter permissão de escrita no directório que especificar.
+
+Este directório '''não''' deve poder ser acedido directamente da internet, por isso está a ser colocado onde estão os seus ficheiros PHP.
+
+Juntamente com o directório, o instalador irá criar um ficheiro <code>.htaccess</code>, mas se esta operação falhar é possível que alguém venha a ter acesso directo à base de dados.
+Isto inclui acesso aos dados dos utilizadores (endereços de correio electrónico, palavras-chave encriptadas), às revisões eliminadas e a outros dados de acesso restrito na wiki.
+
+Considere colocar a base de dados num local completamente diferente, como, por exemplo, em <code>/var/lib/mediawiki/asuawiki</code>.",
+ 'config-oracle-def-ts' => 'Tablespace padrão:',
+ 'config-oracle-temp-ts' => 'Tablespace temporário:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'O MediaWiki suporta as seguintes plataformas de base de dados:
+
+$1
+
+Se a plataforma que pretende usar não está listada abaixo, siga as instruções nos links acima para activar o suporte.',
+ 'config-support-mysql' => '* $1 é a plataforma primária do MediaWiki e a melhor suportada ([http://www.php.net/manual/en/mysql.installation.php como compilar PHP com suporte MySQL])',
+ 'config-support-postgres' => '* $1 é uma plataforma de base de dados comum, de fonte aberta, alternativa ao MySQL. ([http://www.php.net/manual/en/pgsql.installation.php como compilar PHP com suporte PostgreSQL])',
+ 'config-support-sqlite' => '* $1 é uma plataforma de base de dados ligeira muito bem suportada. ([http://www.php.net/manual/en/pdo.installation.php Como compilar PHP com suporte SQLite], usa PDO)',
+ 'config-support-oracle' => '* $1 é uma base de dados de uma empresa comercial. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
+ 'config-header-mysql' => 'Definições MySQL',
+ 'config-header-postgres' => 'Definições PostgreSQL',
+ 'config-header-sqlite' => 'Definições SQLite',
+ 'config-header-oracle' => 'Definições Oracle',
+ 'config-invalid-db-type' => 'O tipo de base de dados é inválido',
+ 'config-missing-db-name' => 'Tem de introduzir um valor para "Nome da base de dados"',
+ 'config-missing-db-host' => 'Tem de introduzir um valor para "Servidor da base de dados"',
+ 'config-missing-db-server-oracle' => 'Tem de introduzir um valor para "TNS da base de dados"',
+ 'config-invalid-db-server-oracle' => 'O TNS da base de dados, "$1", é inválido.
+Use só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e pontos (.) dos caracteres ASCII.',
+ 'config-invalid-db-name' => 'O nome da base de dados, "$1", é inválido.
+Use só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e hífens (-) dos caracteres ASCII.',
+ 'config-invalid-db-prefix' => 'O prefixo da base de dados, "$1", é inválido.
+Use só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e hífens (-) dos caracteres ASCII.',
+ 'config-connection-error' => '$1.
+
+Verifique o servidor, o nome do utilizador e a palavra-chave abaixo e tente novamente.',
+ 'config-invalid-schema' => "O esquema ''(schema)'' do MediaWiki, \"\$1\", é inválido.
+Use só letras (a-z, A-Z), algarismos (0-9) e sublinhados (_) dos caracteres ASCII.",
+ 'config-db-sys-create-oracle' => 'O instalador só permite criar uma conta nova usando uma conta SYSDBA.',
+ 'config-db-sys-user-exists-oracle' => 'A conta "$1" já existe. A conta SYSDBA só pode criar uma conta nova!',
+ 'config-postgres-old' => 'É necessário o PostgreSQL $1 ou posterior; tem a versão $2.',
+ 'config-sqlite-name-help' => 'Escolha o nome que identificará a sua wiki.
+Não use espaços ou hífens.
+Este nome será usado como nome do ficheiro de dados do SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Não é possível criar o directório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no directório que o contém <code><nowiki>$2</nowiki></code>.
+
+O instalador determinou em que nome de utilizador o seu servidor de internet está a correr.
+Para continuar, configure o directório <code><nowiki>$3</nowiki></code> para poder ser escrito por este utilizador.
+Para fazê-lo em sistemas Unix ou Linux, use:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Não é possível criar o directório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no directório que o contém <code><nowiki>$2</nowiki></code>.
+
+Não foi possível determinar em que nome de utilizador o seu servidor de internet está a correr.
+Para continuar, configure o directório <code><nowiki>$3</nowiki></code> para que este possa ser globalmente escrito por esse utilizador (e por outros!).
+Para fazê-lo em sistemas Unix ou Linux, use:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Ocorreu um erro ao criar o directório de dados "$1".
+Verifique a localização e tente novamente.',
+ 'config-sqlite-dir-unwritable' => 'Não foi possível escrever no directório "$1".
+Altere as permissões para que ele possa ser escrito pelo servidor de internet e tente novamente.',
+ 'config-sqlite-connection-error' => '$1.
+
+Verifique o directório de dados e o nome da base de dados abaixo e tente novamente.',
+ 'config-sqlite-readonly' => 'Não é possivel escrever no ficheiro <code>$1</code>.',
+ 'config-sqlite-cant-create-db' => 'Não foi possível criar o ficheiro da base de dados <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'O PHP não tem suporte FTS3; a reverter o esquema das tabelas para o anterior',
+ 'config-can-upgrade' => "Esta base de dados contém tabelas do MediaWiki.
+Para actualizá-las para o MediaWiki $1, clique '''Continuar'''.",
+ 'config-upgrade-done' => "Actualização terminada.
+
+Agora pode [$1 começar a usar a sua wiki].
+
+Se quiser regenerar o seu ficheiro <code>LocalSettings.php</code>, clique o botão abaixo.
+Esta operação '''não é recomendada''' a menos que esteja a ter problemas com a sua wiki.",
+ 'config-upgrade-done-no-regenerate' => 'Actualização terminada.
+
+Agora pode [$1 começar a usar a sua wiki].',
+ 'config-regenerate' => 'Regenerar o LocalSettings.php →',
+ 'config-show-table-status' => 'A consulta SHOW TABLE STATUS falhou!',
+ 'config-unknown-collation' => "'''Aviso:''' A base de dados está a utilizar uma colação ''(collation)'' desconhecida.",
+ 'config-db-web-account' => 'Conta na base de dados para acesso pela internet',
+ 'config-db-web-help' => 'Seleccione o nome de utilizador e a palavra-chave que o servidor de internet irá utilizar para aceder ao servidor da base de dados, durante a operação normal da wiki.',
+ 'config-db-web-account-same' => 'Usar a mesma conta usada na instalação',
+ 'config-db-web-create' => 'Criar a conta se ainda não existir',
+ 'config-db-web-no-create-privs' => 'A conta que especificou para a instalação não tem privilégios suficientes para criar uma conta.
+A conta que especificar aqui já tem de existir.',
+ 'config-mysql-engine' => 'Motor de armazenamento:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' é quase sempre a melhor opção, porque suporta bem acessos simultâneos ''(concurrency)''.
+
+'''MyISAM''' pode ser mais rápido no modo de utilizador único ou em instalações somente para leitura.
+As bases de dados MyISAM tendem a ficar corrompidas com maior frequência do que as bases de dados InnoDB.",
+ 'config-mysql-charset' => 'Conjunto de caracteres da base de dados:',
+ 'config-mysql-binary' => 'Binary',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "No modo '''binary''' (\"binário\"), o MediaWiki armazena o texto UTF-8 na base de dados em campos binários.
+Isto é mais eficiente do que o modo UTF-8 do MySQL e permite que sejam usados todos os caracteres Unicode.
+
+No modo '''UTF-8''', o MySQL saberá em que conjunto de caracteres os seus dados estão e pode apresentá-los e convertê-los da forma mais adequada,
+mas não lhe permitirá armazenar caracteres acima do [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Básico].",
+ 'config-site-name' => 'Nome da wiki:',
+ 'config-site-name-help' => 'Este nome aparecerá no título da janela do seu browser e em vários outros sítios.',
+ 'config-site-name-blank' => 'Introduza o nome do site.',
+ 'config-project-namespace' => 'Espaço nominal do projecto:',
+ 'config-ns-generic' => 'Projecto',
+ 'config-ns-site-name' => 'O mesmo que o nome da wiki: $1',
+ 'config-ns-other' => 'Outro (especifique)',
+ 'config-ns-other-default' => 'AMinhaWiki',
+ 'config-project-namespace-help' => 'Seguindo o exemplo da Wikipedia, muitas wikis mantêm as páginas das suas normas e políticas, separadas das páginas de conteúdo, num "\'\'\'espaço nominal do projecto\'\'\'".
+Todos os nomes das páginas neste espaço nominal começam com um determinado prefixo, que pode especificar aqui.
+Tradicionalmente, este prefixo deriva do nome da wiki, mas não pode conter caracteres de pontuação, como "#" ou ":".',
+ 'config-ns-invalid' => 'O espaço nominal especificado "<nowiki>$1</nowiki>" é inválido.
+Introduza um espaço nominal de projecto diferente.',
+ 'config-ns-conflict' => 'O espaço nominal que especificou, "<nowiki>$1</nowiki>", cria um conflito com um dos espaços nominais padrão do MediaWiki.
+Especifique um espaço nominal do projecto diferente.',
+ 'config-admin-box' => 'Conta de administrador',
+ 'config-admin-name' => 'O seu nome:',
+ 'config-admin-password' => 'Palavra-chave:',
+ 'config-admin-password-confirm' => 'Repita a palavra-chave:',
+ 'config-admin-help' => 'Introduza aqui o seu nome de utilizador preferido, por exemplo, "João Beltrão".
+Este é o nome que irá utilizar para entrar na wiki.',
+ 'config-admin-name-blank' => 'Introduza um nome de utilizador para administrador.',
+ 'config-admin-name-invalid' => 'O nome de utilizador especificado "<nowiki>$1</nowiki>" é inválido.
+Introduza um nome de utilizador diferente.',
+ 'config-admin-password-blank' => 'Introduza uma palavra-chave para a conta de administrador.',
+ 'config-admin-password-same' => 'A palavra-chave tem de ser diferente do nome de utilizador.',
+ 'config-admin-password-mismatch' => 'As duas palavras-chave que introduziu não coincidem.',
+ 'config-admin-email' => 'Correio electrónico:',
+ 'config-admin-email-help' => 'Introduza aqui um correio electrónico que lhe permita receber mensagens de outros utilizadores da wiki, reiniciar a sua palavra-chave e receber notificações de alterações às suas páginas vigiadas. Pode deixar o campo vazio.',
+ 'config-admin-error-user' => 'Ocorreu um erro interno ao criar um administrador com o nome "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Ocorreu um erro interno ao definir uma palavra-chave para o administrador "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Introduziu um correio electrónico inválido',
+ 'config-subscribe' => 'Subscreva a [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de divulgação de anúncios de lançamento].',
+ 'config-subscribe-help' => 'Esta é uma lista de divulgação de baixo volume para anúncios de lançamento de versões novas, incluindo anúncios de segurança importantes.
+Deve subscrevê-la e actualizar a sua instalação MediaWiki quando são lançadas versões novas.',
+ 'config-almost-done' => 'Está quase a terminar!
+Agora pode saltar as configurações restantes e instalar já a wiki.',
+ 'config-optional-continue' => 'Faz-me mais perguntas.',
+ 'config-optional-skip' => 'Já estou aborrecido, instala lá a wiki.',
+ 'config-profile' => 'Perfil de permissões:',
+ 'config-profile-wiki' => 'Wiki tradicional',
+ 'config-profile-no-anon' => 'Criação de conta exigida',
+ 'config-profile-fishbowl' => 'Somente utilizadores autorizados',
+ 'config-profile-private' => 'Wiki privada',
+ 'config-profile-help' => "As wikis funcionam melhor quando se deixa tantas pessoas editá-las quanto possível.
+No MediaWiki, é fácil rever as alterações recentes e reverter quaisquer estragos causados por utilizadores novatos ou maliciosos.
+
+No entanto, muitas pessoas consideram o MediaWiki útil de variadas formas e nem sempre é fácil convencer todas as pessoas dos benefícios desta filosofia wiki.
+Por isso pode optar.
+
+Uma '''{{int:config-profile-wiki}}''' permite que todos a editem, sem sequer necessitar de autenticação.
+Uma wiki com '''{{int:config-profile-no-anon}}''' atribui mais responsabilidade, mas pode afastar os colaboradores ocasionais.
+
+Um cenário '''{{int:config-profile-fishbowl}}''' permite que os utilizadores aprovados editem, mas que o público visione as páginas, incluindo o historial das mesmas.
+Uma '''{{int:config-profile-private}}''' só permite que os utilizadores aprovados visionem as páginas e as editem.
+
+Após a instalação, estarão disponíveis mais configurações de privilégios. Consulte [http://www.mediawiki.org/wiki/Manual:User_rights a entrada relevante no Manual].",
+ 'config-license' => 'Direitos de autor e licença:',
+ 'config-license-none' => 'Sem rodapé com a licença',
+ 'config-license-cc-by-sa' => 'Atribuição - Partilha nos Mesmos Termos, da Creative Commons',
+ 'config-license-cc-by-nc-sa' => 'Atribuição - Uso Não-Comercial - Partilha nos Mesmos Termos, da Creative Commons',
+ 'config-license-cc-0' => 'Creative Commons Zero',
+ 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
+ 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 ou posterior',
+ 'config-license-pd' => 'Domínio Público',
+ 'config-license-cc-choose' => 'Seleccione uma licença personalizada da Creative Commons',
+ 'config-license-help' => 'Muitas wikis de acesso público licenciam todas as colaborações com uma [http://freedomdefined.org/Definition licença livre].
+Isto ajuda a criar um sentido de propriedade da comunidade e encoraja as colaborações a longo prazo.
+Tal não é geralmente necessário nas wikis privadas ou corporativas.
+
+Se pretende que seja possível usar textos da Wikipédia na sua wiki e que seja possível a Wikipédia aceitar textos copiados da sua wiki, deve escolher a licença Atribuição - Partilha nos Mesmos Termos, da Creative Commons.
+
+A licença GNU Free Documentation License era a anterior licença da Wikipédia.
+Embora ainda seja uma licença válida, ela tem certas características que tornam o reuso e a interpretação difíceis.',
+ 'config-email-settings' => 'Definições do correio electrónico',
+ 'config-enable-email' => 'Activar mensagens electrónicas de saída',
+ 'config-enable-email-help' => 'Se quer que o correio electrónico funcione, as [http://www.php.net/manual/en/mail.configuration.php definições de correio electrónico do PHP] têm de estar configuradas correctamente.
+Se não pretende viabilizar qualquer funcionalidade de correio electrónico, pode desactivá-lo aqui.',
+ 'config-email-user' => 'Activar mensagens electrónicas entre utilizadores',
+ 'config-email-user-help' => 'Permitir que todos os utilizadores troquem entre si mensagens de correio electrónico, se tiverem activado esta funcionalidade nas suas preferências.',
+ 'config-email-usertalk' => 'Activar notificações de alterações à página de discussão dos utilizadores',
+ 'config-email-usertalk-help' => 'Permitir que os utilizadores recebam notificações de alterações à sua página de discussão, se tiverem activado esta funcionalidade nas suas preferências.',
+ 'config-email-watchlist' => 'Activar notificação de alterações às páginas vigiadas',
+ 'config-email-watchlist-help' => 'Permitir que os utilizadores recebam notificações de alterações às suas páginas vigiadas, se tiverem activado esta funcionalidade nas suas preferências.',
+ 'config-email-auth' => 'Activar autenticação do correio electrónico',
+ 'config-email-auth-help' => "Se esta opção for activada, os utilizadores têm de confirmar o seu endereço de correio electrónico usando um link que lhes é enviado sempre que o definirem ou alterarem.
+Só os endereços de correio electrónico autenticados podem receber mensagens electrónicas dos outros utilizadores ou alterar as mensagens de notificação.
+É '''recomendado''' que esta opção seja activada nas wikis de acesso público para impedir o uso abusivo das funcionalidades de correio electrónico.",
+ 'config-email-sender' => 'Endereço de correio electrónico de retorno:',
+ 'config-email-sender-help' => 'Introduza o endereço de correio electrónico que será usado como endereço de retorno nas mensagens electrónicas de saída.
+É para este endereço que serão enviadas as mensagens que não podem ser entregues.
+Muitos servidores de correio electrónico exigem que pelo menos a parte do nome do domínio seja válida. \\',
+ 'config-upload-settings' => 'Upload de imagens e ficheiros',
+ 'config-upload-enable' => 'Possibilitar o upload de ficheiros',
+ 'config-upload-help' => 'O upload de ficheiros expõe o seu servidor a riscos de segurança.
+Para mais informações, leia a [http://www.mediawiki.org/wiki/Manual:Security secção sobre segurança] do Manual Técnico.
+
+Para permitir o upload de ficheiros, altere as permissões do subdirectório <code>images</code> no directório de raiz do MediaWik para que o servidor de internet possa escrever nele.
+Depois active esta opção.',
+ 'config-upload-deleted' => 'Directório para os ficheiros apagados:',
+ 'config-upload-deleted-help' => 'Escolha um directório onde serão arquivados os ficheiros apagados.
+O ideal é que este directório não possa ser directamente acedido a partir da internet.',
+ 'config-logo' => 'URL do logótipo:',
+ 'config-logo-help' => 'O tema padrão do MediaWiki inclui espaço para um logótipo de 135x160 pixels no canto superior esquerdo.
+Faça o upload de uma imagem com estas dimensões e introduza aqui a URL dessa imagem.
+
+Se não pretende usar um logótipo, deixe este campo em branco.',
+ 'config-instantcommons' => 'Activar a funcionalidade Instant Commons',
+ 'config-instantcommons-help' => 'O [http://www.mediawiki.org/wiki/InstantCommons Instant Commons] é uma funcionalidade que permite que as wikis usem imagens, áudio e outros ficheiros multimédia disponíveis no site [http://commons.wikimedia.org/ Wikimedia Commons].
+Para poder usá-los, o MediaWiki necessita de acesso à internet.
+
+Para mais informações sobre esta funcionalidade, incluindo instruções sobre como configurá-la para usar outras wikis em vez da Wikimedia Commons, consulte o [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos Manual Técnico].',
+ 'config-cc-error' => 'O auxiliar de escolha de licenças da Creative Commons não produziu resultados.
+Introduza o nome da licença manualmente.',
+ 'config-cc-again' => 'Escolha outra vez...',
+ 'config-cc-not-chosen' => 'Escolha a licença da Creative Commons que pretende e clique "continuar".',
+ 'config-advanced-settings' => 'Configuração avançada',
+ 'config-cache-options' => 'Definições da cache de objectos:',
+ 'config-cache-help' => 'A cache de objectos é usada para melhorar o desempenho do MediaWiki. Armazena dados usados com frequência.
+Sites de tamanho médio ou grande são altamente encorajados a activar esta funcionalidade e os sites pequenos também terão alguns benefícios em fazê-lo.',
+ 'config-cache-none' => 'Sem cache (não é removida nenhuma funcionalidade, mas a velocidade de operação pode ser afectada nas wikis grandes)',
+ 'config-cache-accel' => 'Cache de objectos do PHP (APC, eAccelerator, XCache ou WinCache)',
+ 'config-cache-memcached' => 'Usar Memcached (requer instalação e configurações adicionais)',
+ 'config-memcached-servers' => 'Servidores Memcached:',
+ 'config-memcached-help' => 'Lista de endereços IP que serão usados para o Memcached.
+Deve-se colocar um por linha e indicar a porta a utilizar. Por exemplo:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Seleccionou o Memcached como tipo de chache, mas não especificou nenhum servidor.',
+ 'config-memcache-badip' => 'Introduziu um endereço IP inválido para o Memcached: $1.',
+ 'config-memcache-noport' => 'Não especificou a porta a usar para o servidor Memcached: $1.
+Se não sabe qual é a porta, a predefinida é a 11211.',
+ 'config-memcache-badport' => 'Os números das portas do Memcached devem estar entre $1 e $2.',
+ 'config-extensions' => 'Extensões',
+ 'config-extensions-help' => 'Foi detectada a existência das extensões listadas acima, no seu directório <code>./extensions</code>.
+
+Estas talvez necessitem de configurações adicionais, mas pode activá-las agora',
+ 'config-install-alreadydone' => "'''Aviso:''' Parece que já instalou o MediaWiki e está a tentar instalá-lo novamente.
+Passe para a próxima página, por favor.",
+ 'config-install-begin' => 'Ao clicar "{{int:config-continue}}", vai iniciar a instalação do MediaWiki.
+Se quiser fazer mais alterações, clique Voltar.',
+ 'config-install-step-done' => 'terminado',
+ 'config-install-step-failed' => 'falhou',
+ 'config-install-extensions' => 'A incluir as extensões',
+ 'config-install-database' => 'A preparar a base de dados',
+ 'config-install-pg-schema-not-exist' => "O esquema ''(schema)'' PostgreSQL não existe",
+ 'config-install-pg-schema-failed' => 'A criação das tabelas falhou.
+Certifique-se de que o utilizador "$1" pode escrever no esquema \'\'(schema)\'\' "$2".',
+ 'config-install-pg-commit' => 'A gravar as alterações',
+ 'config-install-pg-plpgsql' => 'A verificar a presença da linguagem PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'É preciso instalar a linguagem PL/pgSQL na base de dados $1',
+ 'config-pg-no-create-privs' => 'A conta que especificou para a instalação não tem privilégios suficientes para criar uma conta.',
+ 'config-install-user' => 'A criar o utilizador da base de dados',
+ 'config-install-user-alreadyexists' => 'O utilizador "$1" já existe',
+ 'config-install-user-create-failed' => 'A criação do utilizador "$1" falhou: $2',
+ 'config-install-user-grant-failed' => 'A atribuição das permissões ao utilizador "$1" falhou: $2',
+ 'config-install-tables' => 'A criar as tabelas',
+ 'config-install-tables-exist' => "'''Aviso''': As tabelas do MediaWiki parecem já existir.
+A criação das tabelas será saltada.",
+ 'config-install-tables-failed' => "'''Erro''': A criação das tabelas falhou com o seguinte erro: $1",
+ 'config-install-interwiki' => 'A preencher a tabela padrão de interwikis',
+ 'config-install-interwiki-list' => 'Não foi possível encontrar o ficheiro <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Aviso''': A tabela de interwikis parece já conter entradas.
+O preenchimento padrão desta tabela será saltado.",
+ 'config-install-stats' => 'A inicializar as estatísticas',
+ 'config-install-keys' => 'A gerar a chave secreta',
+ 'config-install-sysop' => 'A criar a conta de administrador',
+ 'config-install-subscribe-fail' => 'Não foi possível subscrever a lista mediawiki-announce',
+ 'config-install-mainpage' => 'A criar a página principal com o conteúdo padrão.',
+ 'config-install-extension-tables' => 'A criar as tabelas das extensões activadas',
+ 'config-install-mainpage-failed' => 'Não foi possível inserir a página principal: $1',
+ 'config-install-done' => "'''Parabéns!'''
+Terminou a instalação do MediaWiki.
+
+O instalador gerou um ficheiro <code>LocalSettings.php</code>.
+Este ficheiro contém todas as configurações.
+
+Precisa de fazer o download do ficheiro e colocá-lo no directório de raiz da sua instalação (o mesmo directório onde está o ficheiro index.php). Este download deverá ter sido iniciado automaticamente.
+
+Se o download não foi iniciado, ou se o cancelou, pode recomeçá-lo clicando o link abaixo:
+
+$3
+
+'''Nota''': Se não fizer isto agora, o ficheiro que foi gerado deixará de estar disponível quando sair do processo de instalação.
+
+Depois de terminar o passo anterior, pode '''[$2 entrar na wiki]'''.",
+ 'config-download-localsettings' => 'Download do LocalSettings.php',
+ 'config-help' => 'ajuda',
+);
+
+/** Brazilian Portuguese (Português do Brasil)
+ * @author Giro720
+ * @author Gustavo
+ * @author Marcionunes
+ */
+$messages['pt-br'] = array(
+ 'config-desc' => 'O instalador do MediaWiki',
+ 'config-title' => 'Instalação MediaWiki $1',
+ 'config-information' => 'Informações',
+ 'config-localsettings-upgrade' => "'''Aviso''': Foi detetada a existência de um arquivo <code>LocalSettings.php</code>.
+É possível atualizar o seu software.
+Mova o <code>LocalSettings.php</code> para um lugar seguro e execute o instalador novamente, por favor.",
+ 'config-localsettings-cli-upgrade' => 'Foi detectado um arquivo LocalSettings.php.
+Para atualizar esta instalação, por favor, use: --upgrade=yes.',
+ 'config-localsettings-key' => 'Chave de atualização:',
+ 'config-localsettings-badkey' => 'A senha inserida está incorreta.',
+ 'config-upgrade-key-missing' => 'Foi detectada uma instalação existente do MediaWiki.
+Para atualizar esta instalação, por favor, coloque a seguinte linha na parte inferior do seu LocalSettings.php:
+
+$ 1',
+ 'config-session-error' => 'Erro ao iniciar a sessão: $1',
+ 'config-session-expired' => 'Os seus dados de sessão parecem ter expirado.
+As sessões estão configuradas para uma duração de $1.
+Você pode aumentar esta duração configurando <code>session.gc_maxlifetime</code> no php.ini.
+Reinicie o processo de instalação.',
+ 'config-no-session' => 'Os seus dados de sessão foram perdidos!
+Verifique o seu php.ini e certifique-se de que em <code>session.save_path</code> está definido um diretório apropriado.',
+ 'config-your-language' => 'A sua língua:',
+ 'config-your-language-help' => 'Selecione a língua que será usada durante o processo de instalação.',
+ 'config-wiki-language' => 'Língua da wiki:',
+ 'config-wiki-language-help' => 'Selecione a língua que será predominante na wiki.',
+ 'config-back' => '← Voltar',
+ 'config-continue' => 'Continuar →',
+ 'config-page-language' => 'Língua',
+ 'config-page-welcome' => 'Bem-vindo(a) ao MediaWiki!',
+ 'config-page-dbconnect' => 'Ligar à base de dados',
+ 'config-page-upgrade' => 'Atualizar a instalação existente',
+ 'config-page-dbsettings' => 'Configurações da base de dados',
+ 'config-page-name' => 'Nome',
+ 'config-page-options' => 'Opções',
+ 'config-page-install' => 'Instalar',
+ 'config-page-complete' => 'Terminado!',
+ 'config-page-restart' => 'Reiniciar a instalação',
+ 'config-page-readme' => 'Leia-me',
+ 'config-page-releasenotes' => 'Notas de lançamento',
+ 'config-page-copying' => 'Copiando',
+ 'config-page-upgradedoc' => 'Atualizando',
+ 'config-help-restart' => 'Deseja limpar todos os dados salvos que você introduziu e reiniciar o processo de instalação?',
+ 'config-restart' => 'Sim, reiniciar',
+ 'config-welcome' => '=== Verificações do ambiente ===
+São realizadas verificações básicas para determinar se este ambiente é apropriado para instalação do MediaWiki.
+Você deverá fornecer os resultados destas verificações se você precisar de ajuda durante a instalação.',
+ 'config-copyright' => "=== Direitos autorais e Termos de uso ===
+
+$1
+
+Este programa é software livre; você pode redistribuí-lo e/ou modificá-lo nos termos da licença GNU General Public License, tal como publicada pela Free Software Foundation; tanto a versão 2 da Licença, como (por opção sua) qualquer versão posterior.
+
+Este programa é distribuído na esperança de que seja útil, mas '''sem qualquer garantia'''; inclusive, sem a garantia implícita da '''possibilidade de ser comercializado''' ou de '''adequação para qualquer finalidade específica'''.
+Consulte a licença GNU General Public License para mais detalhes.
+
+Em conjunto com este programa você deve ter recebido <doclink href=Copying>uma cópia da licença GNU General Public License</doclink>; se não a recebeu, peça-a por escrito para Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA ou [http://www.gnu.org/copyleft/gpl.html leia-a na internet].",
+ 'config-sidebar' => '* [http://www.mediawiki.org/wiki/MediaWiki/pt Página principal do MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents/pt Ajuda]
+* [http://www.mediawiki.org/wiki/Manual:Contents/pt Manual técnico]
+* [http://www.mediawiki.org/wiki/Manual:FAQ FAQ]',
+ 'config-env-good' => 'O ambiente foi verificado.
+Você pode instalar o MediaWiki.',
+ 'config-env-bad' => 'O ambiente foi verificado.
+Você não pode instalar o MediaWiki.',
+ 'config-env-php' => 'O PHP $1 está instalado.',
+ 'config-unicode-using-utf8' => 'A usar o utf8_normalize.so, de Brian Viper, para a normalização Unicode.',
+ 'config-unicode-using-intl' => 'Usando a [http://pecl.php.net/intl extensão intl PECL] para a normalização Unicode.',
+ 'config-unicode-pure-php-warning' => "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efetuar a normalização Unicode.
+Se o seu site tem um alto volume de tráfego, devia informar-se um pouco sobre a [http://www.mediawiki.org/wiki/Unicode_normalization_considerations normalização Unicode].",
+ 'config-no-db' => 'Não foi possível encontrar um driver de banco de dados adequado!',
+ 'config-no-db-help' => 'Você precisa instalar um driver de banco de dados para PHP.
+Os seguintes tipos de banco de dados são suportados: $1.
+
+Se você estiver em hospedagem compartilhada, pergunte ao seu provedor de hospedagem para instalar um driver de banco de dados apropriado.
+Se você compilou o PHP você mesmo, reconfigurá-lo com um cliente de banco de dados habilitado, por exemplo, usando <code>./configure --with-mysql</code>.
+Se você instalou o PHP de um Debian ou Ubuntu package, então você também precisa instalar o módulo php5-mysql.',
+ 'config-no-fts3' => "' ' 'Aviso' ' ': O SQLite foi compilado sem o módulo [http://sqlite.org/fts3.html FTS3]; as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
+ 'config-register-globals' => "' ' 'Aviso: A opção <code>[http://php.net/register_globals register_globals]</code> do PHP está ativada.'''
+' ' 'Desative-a, se puder.'''
+O MediaWiki funcionará mesmo assim, mas o seu servidor ficará exposto a potenciais vulnerabilidades de segurança.",
+ 'config-logo-help' => 'O tema padrão do MediaWiki inclui espaço para um logotipo de 135x160 pixels no canto superior esquerdo.
+Faça o upload de uma imagem com estas dimensões e introduza aqui a URL dessa imagem.
+
+Se você não pretende usar um logotipo, deixe este campo em branco.',
+);
+
+/** Romanian (Română)
+ * @author Stelistcristi
+ */
+$messages['ro'] = array(
+ 'config-session-error' => 'Eroare la pornirea sesiunii: $1',
+ 'config-your-language' => 'Limba ta:',
+ 'config-your-language-help' => 'Alege o limbă pentru a o utiliza în timpul procesului de instalare.',
+ 'config-wiki-language' => 'Limbă wiki:',
+ 'config-wiki-language-help' => 'Alege limba în care wiki-ul va fi scris predominant.',
+ 'config-back' => '← Înapoi',
+ 'config-continue' => 'Continuă →',
+ 'config-page-language' => 'Limbă',
+ 'config-page-welcome' => 'Bun venit la MediaWiki!',
+ 'config-page-dbconnect' => 'Conectează la baza de date',
+ 'config-page-upgrade' => 'Extinde instalarea existentă',
+ 'config-page-dbsettings' => 'Setări ale bazei de date',
+ 'config-page-name' => 'Nume',
+ 'config-page-options' => 'Opţiuni',
+ 'config-page-install' => 'Instalare',
+ 'config-page-restart' => 'Reporneşte instalarea',
+ 'config-page-readme' => 'Citeşte-mă',
+ 'config-page-releasenotes' => 'Note de lansare',
+ 'config-db-type' => 'Tipul bazei de date:',
+ 'config-db-host' => 'Gazdă bază de date:',
+ 'config-header-mysql' => 'Setările MySQL',
+ 'config-header-sqlite' => 'Setări SQLite',
+ 'config-header-oracle' => 'Setări Oracle',
+ 'config-missing-db-name' => 'Trebuie să introduci o valoare pentru „Numele bazei de date”',
+ 'config-ns-generic' => 'Proiect',
+ 'config-admin-password' => 'Parolă:',
+);
+
+/** Russian (Русский)
+ * @author DCamer
+ * @author Eleferen
+ * @author Krinkle
+ * @author MaxSem
+ * @author Yuriy Apostol
+ * @author Александр Сигачёв
+ * @author Сrower
+ */
+$messages['ru'] = array(
+ 'config-desc' => 'Инсталлятор MediaWiki',
+ 'config-title' => 'Установка MediaWiki $1',
+ 'config-information' => 'Информация',
+ 'config-localsettings-upgrade' => 'Обнаружен файл <code>LocalSettings.php</code>.
+Для обновления этой установки, пожалуйста, введите значение <code>$wgUpgradeKey</code>.
+Его можно найти в файле LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'Обнаружен файл LocalSettings.php.
+Для обновления этой установки, пожалуйста, запустите update.php',
+ 'config-localsettings-key' => 'Ключ обновления:',
+ 'config-localsettings-badkey' => 'Вы указали неправильный ключ',
+ 'config-upgrade-key-missing' => 'Обнаружена существующая установленная копия MediaWiki.
+Чтобы обновить обнаруженную установку, пожалуйста, добавьте следующую строку в конец вашего файла LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'Похоже, что существующий файл LocalSettings.php не является полными.
+Не установлена переменная $1.
+Пожалуйста, измените LocalSettings.php так, чтобы значение этой переменной было задано, затем нажмите «Продолжить».',
+ 'config-localsettings-connection-error' => 'Произошла ошибка при подключении к базе данных с помощью настроек, указанных в LocalSettings.php или AdminSettings.php. Пожалуйста, исправьте эти настройки и повторите попытку.
+
+$1',
+ 'config-session-error' => 'Ошибка при запуске сессии: $1',
+ 'config-session-expired' => 'Ваша сессия истекла.
+Сессии настроены на длительность $1.
+Вы её можете увеличить, изменив <code>session.gc_maxlifetime</code> в php.ini.
+Перезапустите процесс установки.',
+ 'config-no-session' => 'Данные сессии потеряны!
+Проверьте ваш php.ini и убедитесь, что <code>session.save_path</code> установлен в соответствующий каталог.',
+ 'config-your-language' => 'Ваш язык:',
+ 'config-your-language-help' => 'Выберите язык, на котором будет происходить процесс установки.',
+ 'config-wiki-language' => 'Язык, который будет использовать вики:',
+ 'config-wiki-language-help' => 'Выберите язык, на котором будут отображаться вики.',
+ 'config-back' => '← Назад',
+ 'config-continue' => 'Далее →',
+ 'config-page-language' => 'Язык',
+ 'config-page-welcome' => 'Добро пожаловать в MediaWiki!',
+ 'config-page-dbconnect' => 'Подключение к базе данных',
+ 'config-page-upgrade' => 'Обновление существующей установки',
+ 'config-page-dbsettings' => 'Настройки базы данных',
+ 'config-page-name' => 'Название',
+ 'config-page-options' => 'Настройки',
+ 'config-page-install' => 'Установка',
+ 'config-page-complete' => 'Готово!',
+ 'config-page-restart' => 'Начать установку заново',
+ 'config-page-readme' => 'Прочти меня',
+ 'config-page-releasenotes' => 'Информация о версии',
+ 'config-page-copying' => 'Лицензия',
+ 'config-page-upgradedoc' => 'Обновление',
+ 'config-page-existingwiki' => 'Существующая вики',
+ 'config-help-restart' => 'Вы хотите удалить все сохранённые данные, которые вы ввели, и запустить процесс установки заново?',
+ 'config-restart' => 'Да, начать заново',
+ 'config-welcome' => '=== Проверка окружения ===
+Проводятся базовые проверки с целью определить, подходит ли данная система для установки MediaWiki.
+Укажите результаты этих проверок при обращении за помощью с установкой.',
+ 'config-copyright' => "=== Авторские права и условия ===
+
+$1
+
+MediaWiki является свободным программным обеспечением, которое вы можете распространять и/или изменять в соответствии с условиями лицензии GNU General Public License, опубликованной фондом свободного программного обеспечения; второй версии, либо любой более поздней версии.
+
+MediaWiki распространяется в надежде, что она будет полезной, но '''без каких-либо гарантий''', даже без подразумеваемых гарантий '''коммерческой ценности''' или '''пригодности для определённой цели'''. См. лицензию GNU General Public License для более подробной информации.
+
+Вы должны были получить <doclink href=Copying>копию GNU General Public License</doclink> вместе с этой программой, если нет, то напишите Free Software Foundation, Inc., по адресу: 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA или [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html прочтите её онлайн].",
+ 'config-sidebar' => '* [http://www.mediawiki.org Сайт MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents/ru Справка для пользователей]
+* [http://www.mediawiki.org/wiki/Manual:Contents/ru Справка для администраторов]
+* [http://www.mediawiki.org/wiki/Manual:FAQ/ru FAQ]
+----
+* <doclink href=Readme>Readme-файл</doclink>
+* <doclink href=ReleaseNotes>Информация о выпуске</doclink>
+* <doclink href=Copying>Лицензия</doclink>
+* <doclink href=UpgradeDoc>Обновление</doclink>',
+ 'config-env-good' => 'Проверка внешней среды была успешно проведена.
+Вы можете установить MediaWiki.',
+ 'config-env-bad' => 'Была проведена проверка внешней среды.
+Вы не можете установить MediaWiki.',
+ 'config-env-php' => 'Установленная версия PHP: $1.',
+ 'config-env-php-toolow' => 'Найден PHP $1, тогда как MediaWiki требуется PHP версии $2 или выше.',
+ 'config-unicode-using-utf8' => 'Использовать Brion Vibber utf8_normalize.so для нормализации Юникода.',
+ 'config-unicode-using-intl' => 'Будет использовано [http://pecl.php.net/intl расширение «intl» для PECL] для нормализации Юникода.',
+ 'config-unicode-pure-php-warning' => "'''Внимание!''': [http://pecl.php.net/intl международное расширение PECL] недоступно для нормализации Юникода, будет использоваться медленная реализация на чистом PHP.
+Если ваш сайт работает под высокой нагрузкой, вам следует больше узнать о [http://www.mediawiki.org/wiki/Unicode_normalization_considerations нормализации Юникода].",
+ 'config-unicode-update-warning' => "'''Предупреждение''': установленная версия обёртки нормализации Юникода использует старую версию библиотеки [http://site.icu-project.org/ проекта ICU].
+Вы должны [http://www.mediawiki.org/wiki/Unicode_normalization_considerations обновить версию], если хотите полноценно использовать Юникод.",
+ 'config-no-db' => 'Не найдено поддержки баз данных!',
+ 'config-no-db-help' => 'Вам необходимо установить драйвера базы данных для PHP.
+Поддерживаются следующие типы баз данных: $1.
+
+Если вы используете виртуальный хостинг, обратитесь к своему хостинг-провайдеру с просьбой установить подходящий драйвер базы данных.
+Если вы скомпилировали PHP сами, сконфигурируйте его снова с включенным клиентом базы данных, например, с помощью <code>./configure --with-mysql</code>.
+Если вы установили PHP из пакетов Debian или Ubuntu, то вам также необходимо установить модуль php5-mysql.',
+ 'config-no-fts3' => "'''Внимание''': SQLite собран без модуля [http://sqlite.org/fts3.html FTS3] — поиск не будет работать для этой базы данных.",
+ 'config-register-globals' => "'''Внимание: PHP-опция <code>[http://php.net/register_globals register_globals]</code> включена.'''
+'''Отключите её, если это возможно.'''
+MediaWiki будет работать, но это снизит безопасность сервера и увеличит риск проникновения извне.",
+ 'config-magic-quotes-runtime' => "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''
+Это приводит к непредсказуемой порче вводимых данных.
+Установка и использование MediaWiki без выключения этой опции невозможно.",
+ 'config-magic-quotes-sybase' => "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''
+Это приводит к непредсказуемой порче вводимых данных.
+Установка и использование MediaWiki без выключения этой опции невозможно.",
+ 'config-mbstring' => "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''
+Это приводит к ошибкам и непредсказуемой порче вводимых данных.
+Установка и использование MediaWiki без выключения этой опции невозможно.",
+ 'config-ze1' => "'''Проблема: включена опция PHP [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]!'''
+Это приводит к катастрофическим сбоям в MediaWiki.
+Установка и использование MediaWiki без выключения этой опции невозможно.",
+ 'config-safe-mode' => "'''Предупреждение:''' PHP работает в [http://www.php.net/features.safe-mode «безопасном режиме»].
+Это может привести к проблемам, особенно с загрузкой файлов и вставкой математических формул.",
+ 'config-xml-bad' => 'XML-модуль РНР отсутствует.
+MediaWiki не будет работать в этой конфигурации, так как требуется функционал этого модуля.
+Если вы работаете в Mandrake, установите PHP XML-пакет.',
+ 'config-pcre' => 'Модуль поддержки PCRE не найден.
+Для работы MediaWiki требуется поддержка Perl-совместимых регулярных выражений.',
+ 'config-pcre-no-utf8' => "'''Фатальная ошибка'''. Модуль PCRE для PHP, похоже, собран без поддержки PCRE_UTF8.
+MediaWiki требует поддержки UTF-8 для корректной работы.",
+ 'config-memory-raised' => 'Ограничение на доступную PHP память (<code>memory_limit</code>) поднято с $1 до $2.',
+ 'config-memory-bad' => "'''Внимание:''' размер PHP <code>memory_limit</code> составляет $1.
+Вероятно, этого слишком мало.
+Установка может потерпеть неудачу!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] установлен',
+ 'config-apc' => '[http://www.php.net/apc APC] установлен',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] установлен',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] установлен',
+ 'config-no-cache' => "'''Внимание:''' Не найдены [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
+Кэширование объектов будет отключено.",
+ 'config-diff3-bad' => 'GNU diff3 не найден.',
+ 'config-imagemagick' => 'Обнаружен ImageMagick: <code>$1</code>.
+Возможно отображение миниатюр изображений, если вы разрешите закачки файлов.',
+ 'config-gd' => 'Найдена встроенная графическая библиотека GD.
+Возможность использования миниатюр изображений будет включена, если вы включите их загрузку.',
+ 'config-no-scaling' => 'Не удалось найти встроенную библиотеку GD или ImageMagick.
+Возможность использования миниатюр изображений будет отключена.',
+ 'config-no-uri' => "'''Ошибка:''' Не могу определить текущий URI.
+Установка прервана.",
+ 'config-uploads-not-safe' => "'''Внимание:''' директория, используемая по умолчанию для загрузок (<code>$1</code>) уязвима к выполнению произвольных скриптов.
+Хотя MediaWiki проверяет все загружаемые файлы на наличие угроз, настоятельно рекомендуется [http://www.mediawiki.org/wiki/Manual:Security#Upload_security закрыть данную уязвимость] перед включением загрузки файлов.",
+ 'config-brokenlibxml' => 'В вашей системе имеется сочетание версий PHP и libxml2, могущее привести к скрытым повреждениям данных в MediaWiki и других веб-приложениях.
+Обновите PHP до версии 5.2.9 или старше и libxml2 до 2.7.3 или старше ([http://bugs.php.net/bug.php?id=45996 сведения об ошибке]).
+Установка прервана.',
+ 'config-using531' => 'PHP $1 не совместим с MediaWiki из-за ошибки с параметрами-ссылками при вызовах <code>__call()</code>.
+Обновитесь до PHP 5.3.2 и выше, или откатитесь до PHP 5.3.0, чтобы избежать этой проблемы.
+Установка прервана.',
+ 'config-db-type' => 'Тип базы данных:',
+ 'config-db-host' => 'Хост базы данных:',
+ 'config-db-host-help' => 'Если сервер базы данных находится на другом сервере, введите здесь его имя хоста или IP-адрес.
+
+Если вы используете виртуальный хостинг, ваш провайдер должен указать правильное имя хоста в своей документации.
+
+Если вы устанавливаете систему на сервере под Windows и используете MySQL, имя сервера «localhost» может не работать. В этом случае попробуйте указать «127.0.0.1».',
+ 'config-db-host-oracle' => 'TNS базы данных:',
+ 'config-db-host-oracle-help' => 'Введите действительный [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; файл tnsnames.ora должен быть видимым для этой инсталляции. <br />При использовании клиентских библиотек версии 10g и старше также возможно использовать метод именования [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Идентификация этой вики',
+ 'config-db-name' => 'Имя базы данных:',
+ 'config-db-name-help' => 'Выберите название-идентификатор для вашей вики.
+Оно не должно содержать пробелов.
+
+Если вы используете виртуальный хостинг, провайдер или выдаст вам конкретное имя базы данных, или позволит создавать базы данных с помощью панели управления.',
+ 'config-db-name-oracle' => 'Схема базы данных:',
+ 'config-db-account-oracle-warn' => 'Поддерживаются три сценария установки Oracle в качестве базы данных:
+
+Если вы хотите создать учётную запись базы данных в процессе установки, пожалуйста, укажите учётную запись роли SYSDBA для установки и укажите желаемые полномочия учётной записи с веб-доступом. вы также можете учётную запись с веб-доступом вручную и указать только её (если у неё есть необходимые разрешения на создание объектов схемы) или указать две учётные записи, одну с правами создания объектов, а другую с ограничениями для веб-доступа.
+
+Сценарий для создания учётной записи с необходимыми привилегиями можно найти в папке «maintenance/oracle/» этой программы установки. Имейте в виду, что использование ограниченной учётной записи приведёт к отключению всех возможностей обслуживания с учётной записи по умолчанию.',
+ 'config-db-install-account' => 'Учётная запись для установки',
+ 'config-db-username' => 'Имя пользователя базы данных:',
+ 'config-db-password' => 'Пароль базы данных:',
+ 'config-db-password-empty' => 'Пожалуйста, введите пароль для нового пользователя базы данных «$1».
+Хотя и возможно создание пользователей без паролей, это небезопасно.',
+ 'config-db-install-username' => 'Введите имя пользователя, которое будет использоваться для подключения к базе данных в процессе установки.
+Это не имя пользователя MediaWiki, это имя пользователя для базы данных.',
+ 'config-db-install-password' => 'Введите пароль, который будет использоваться для подключения к базе данных в процессе установки.
+Это не пароль пользователя MediaWiki, это пароль для базы данных.',
+ 'config-db-install-help' => 'Введите имя пользователя и пароль, которые будут использоваться для подключения к базе данных во время процесса установки.',
+ 'config-db-account-lock' => 'Использовать то же имя пользователя и пароль для обычной работы',
+ 'config-db-wiki-account' => 'Учётная запись для обычной работы',
+ 'config-db-wiki-help' => 'Введите имя пользователя и пароль, которые будут использоваться для подключения к базе данных во время обычной работы вики.
+Если такой учётной записи не существует, а установочная учётная запись имеет достаточно привилегий, то обычная учётная запись будет создана с минимально необходимыми для работы вики привилегиями.',
+ 'config-db-prefix' => 'Префикс таблиц базы данных:',
+ 'config-db-prefix-help' => 'Если вам нужно делить одну базу данных между несколькими вики, или между MediaWiki и другими веб-приложениями, вы можете добавить префикс для всех имён таблиц.
+Не используйте пробелы.
+
+Это поле обычно остаётся пустым.',
+ 'config-db-charset' => 'Набор символов базы данных',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 бинарная',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 обратно совместимая с UTF-8',
+ 'config-charset-help' => "'''Внимание.''' Если вы используете '''обратно совместый UTF-8''' на MySQL 4.1+ и создаёте резервные копии базы данных с помощью <code>mysqldump</code>, то все не-ASCII символы могут быть искажены, а резервная копия окажется негодной!
+
+В '''бинарном режиме''' MediaWiki хранит юникодный текст в базе в виде двоичных полей.
+Это более эффективно, чем MySQL в режиме UTF-8, позволяет использовать полный набор символов Юникода.
+В '''режиме UTF-8''' MySQL будет знать к какому набору символу относятся ваши данные, сможет представлять и преобразовать их надлежащим образом (буква Ё окажется при сортировке после буквы Е, а не после буквы Я, как в бинарном режиме),
+но не позволит вам сохранять символы, выходящие за пределы [http://ru.wikipedia.org/wiki/Символы,_представленные_в_Юникоде#.D0.91.D0.B0.D0.B7.D0.BE.D0.B2.D0.B0.D1.8F_.D0.BC.D0.BD.D0.BE.D0.B3.D0.BE.D1.8F.D0.B7.D1.8B.D0.BA.D0.BE.D0.B2.D0.B0.D1.8F_.D0.BF.D0.BB.D0.BE.D1.81.D0.BA.D0.BE.D1.81.D1.82.D1.8C BMP].",
+ 'config-mysql-old' => 'Необходим MySQL $1 или более поздняя версия. У вас установлен MySQL $2.',
+ 'config-db-port' => 'Порт базы данных:',
+ 'config-db-schema' => 'Схема для MediaWiki',
+ 'config-db-schema-help' => 'Эта схема обычно работают хорошо.
+Изменяйте её только если знаете, что вам это нужно.',
+ 'config-sqlite-dir' => 'Директория данных SQLite:',
+ 'config-sqlite-dir-help' => "SQLite хранит все данные в одном файле.
+
+Директория, которую вы должны указать, должна быть доступна для записи веб-сервером во время установки.
+
+Она '''не должна''' быть доступна через Интернет, поэтому не должна совпадать с той, где хранятся PHP файлы.
+
+Установщик запишет в эту директорию файл <code>.htaccess</code>, но если это не сработает, кто-нибудь может получить доступ ко всей базе данных.
+В этой базе находится в том числе и информация о пользователях (адреса электронной почты, хэши паролей), а также удалённые страницы и другие секретные данные о вики.
+
+По возможности, расположите базу данных где-нибудь в стороне, например, в <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Пространство таблиц по умолчанию:',
+ 'config-oracle-temp-ts' => 'Временное пространство таблиц:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-support-info' => 'MediaWiki поддерживает следующие СУБД:
+
+$1
+
+Если вы не видите своей системы хранения данных в этом списке, следуйте инструкциям, на которые есть ссылка выше, чтобы получить поддержку.',
+ 'config-support-mysql' => '* $1 — основная база данных для MediaWiki, и лучше поддерживается ([http://www.php.net/manual/en/mysql.installation.php инструкция, как собрать PHP с поддержкой MySQL])',
+ 'config-support-postgres' => '* $1 — популярная открытая СУБД, альтернатива MySQL ([http://www.php.net/manual/en/pgsql.installation.php инструкция, как собрать PHP с поддержкой PostgreSQL]). Могут встречаться небольшие неисправленные ошибки, не рекомендуется для использования в рабочей системе.',
+ 'config-support-sqlite' => '* $1 — это легковесная система баз данных, имеющая очень хорошую поддержку. ([http://www.php.net/manual/en/pdo.installation.php инструкция, как собрать PHP с поддержкой SQLite], работающей посредством PDO)',
+ 'config-support-oracle' => '* $1 — это коммерческая база данных масштаба предприятия. ([http://www.php.net/manual/en/oci8.installation.php Как собрать PHP с поддержкой OCI8])',
+ 'config-header-mysql' => 'Настройки MySQL',
+ 'config-header-postgres' => 'Настройки PostgreSQL',
+ 'config-header-sqlite' => 'Настройки SQLite',
+ 'config-header-oracle' => 'Настройки Oracle',
+ 'config-invalid-db-type' => 'Неверный тип базы данных',
+ 'config-missing-db-name' => 'Вы должны ввести значение параметра «Имя базы данных»',
+ 'config-missing-db-host' => 'Необходимо ввести значение параметра «Сервер базы данных»',
+ 'config-missing-db-server-oracle' => 'Вы должны заполнить поле «TNS базы данных»',
+ 'config-invalid-db-server-oracle' => 'Неверное имя TNS базы данных «$1».
+Используйте только символы ASCII (a-z, A-Z), цифры (0-9), знаки подчёркивания (_) и точки (.).',
+ 'config-invalid-db-name' => 'Неверное имя базы данных «$1».
+Используйте только ASCII-символы (a-z, A-Z), цифры (0-9), знак подчёркивания (_) и дефис(-).',
+ 'config-invalid-db-prefix' => 'Неверный префикс базы данных «$1».
+Используйте только буквы ASCII (a-z, A-Z), цифры (0-9), знак подчёркивания (_) и дефис (-).',
+ 'config-connection-error' => '$1.
+
+Проверьте хост, имя пользователя и пароль и попробуйте ещё раз.',
+ 'config-invalid-schema' => 'Неправильная схема для MediaWiki «$1».
+Используйте только ASCII символы (a-z, A-Z), цифры(0-9) и знаки подчёркивания(_).',
+ 'config-db-sys-create-oracle' => 'Программа установки поддерживает только использование SYSDBA для создания новой учётной записи.',
+ 'config-db-sys-user-exists-oracle' => 'Учётная запись «$1». SYSDBA может использоваться только для создания новой учётной записи!',
+ 'config-postgres-old' => 'Необходим PostgreSQL $1 или более поздняя версия. У вас установлен PostgreSQL $2.',
+ 'config-sqlite-name-help' => 'Выберите имя-идентификатор для вашей вики.
+Не используйте дефисы и пробелы.
+Эта строка будет использоваться в имени файла SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Не удалось создать директорию данных <nowiki><code>$1</code></nowiki>, так как у веб-сервера нет прав записи в родительскую директорию <nowiki><code>$2</code></nowiki>.
+
+Установщик определил пользователя, под которым работает веб-сервер.
+Сделайте директорию <nowiki><code>$3</code></nowiki> доступной для записи и продолжите.
+В Unix/Linux системе выполните:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Не удалось создать директорию для данных <code><nowiki>$1</nowiki></code>, так как у веб-сервера нет прав на запись в родительскую директорию <code><nowiki>$2</nowiki></code>.
+
+Программа установки не смогла определить пользователя, под которым работает веб-сервер.
+Для продолжения сделайте каталог <code><nowiki>$3</nowiki></code> глобально доступным для записи серверу (и другим).
+В Unix/Linux сделайте:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Ошибка при создании директории для данных «$1».
+Проверьте расположение и повторите попытку.',
+ 'config-sqlite-dir-unwritable' => 'Невозможно произвести запись в каталог «$1».
+Измените настройки доступа так, чтобы веб-сервер мог записывать в этот каталог, и попробуйте ещё раз.',
+ 'config-sqlite-connection-error' => '$1.
+
+Проверьте название базы данных и директорию с данными и попробуйте ещё раз.',
+ 'config-sqlite-readonly' => 'Файл <code>$1</code> недоступен для записи.',
+ 'config-sqlite-cant-create-db' => 'Не удаётся создать файл базы данных <code>$1</code> .',
+ 'config-sqlite-fts3-downgrade' => 'У PHP отсутствует поддержка FTS3 — сбрасываем таблицы',
+ 'config-can-upgrade' => "В базе данных найдены таблицы MediaWiki.
+Чтобы обновить их до MediaWiki $1, нажмите на кнопку '''«Продолжить»'''.",
+ 'config-upgrade-done' => "Обновление завершено.
+
+Теперь вы можете [$1 начать использовать вики].
+
+Если вы хотите повторно создать файл <code>LocalSettings.php</code>, нажмите на кнопку ниже.
+Это действие '''не рекомендуется''', если у вас не возникло проблем при установке.",
+ 'config-upgrade-done-no-regenerate' => 'Обновление завершено.
+
+Теперь вы можете [$1 начать работу с вики].',
+ 'config-regenerate' => 'Создать LocalSettings.php заново →',
+ 'config-show-table-status' => 'Запрос «SHOW TABLE STATUS» не выполнен!',
+ 'config-unknown-collation' => "'''Внимание:''' База данных использует нераспознанные правила сортировки.",
+ 'config-db-web-account' => 'Учётная запись для доступа к базе данных из веб-сервера',
+ 'config-db-web-help' => 'Выберите имя пользователя и пароль, которые веб-сервер будет использовать для подключения к серверу базы данных при обычной работе вики.',
+ 'config-db-web-account-same' => 'Использовать ту же учётную запись, что и для установки',
+ 'config-db-web-create' => 'Создать учётную запись, если она ещё не существует',
+ 'config-db-web-no-create-privs' => 'Учётная запись, указанная вами для установки, не обладает достаточными правами для создания учётной записи.
+Указанная здесь учётная запись уже должна существовать.',
+ 'config-mysql-engine' => 'Движок базы данных:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' почти всегда предпочтительнее, так как он лучше справляется с параллельным доступом.
+
+'''MyISAM''' может оказаться быстрее для вики с одним пользователем или с минимальным количеством поступающих правок, однако базы данных на нём портятся чаще, чем на InnoDB.",
+ 'config-mysql-charset' => 'Набор символов (кодовая таблица) базы данных:',
+ 'config-mysql-binary' => 'Двоичный',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "В '''двоичном режиме''' MediaWiki хранит UTF-8 текст в бинарных полях базы данных.
+Это более эффективно, чем ''UTF-8 режим'' MySQL, и позволяет использовать полный набор символов Unicode.
+
+В '''режиме UTF-8''' MySQL будет знать в какой кодировке находятся Ваши данные и может отображать и преобразовывать их соответствующим образом, но это не позволит вам хранить символы выше [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базовой Многоязыковой Плоскости].",
+ 'config-site-name' => 'Название вики:',
+ 'config-site-name-help' => 'Название будет отображаться в заголовке окна браузера и в некоторых других местах вики.',
+ 'config-site-name-blank' => 'Введите название сайта.',
+ 'config-project-namespace' => 'Пространство имён проекта:',
+ 'config-ns-generic' => 'Проект',
+ 'config-ns-site-name' => 'То же, что имя вики: $1',
+ 'config-ns-other' => 'Другое (укажите)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-project-namespace-help' => "Следуя примеру Википедии, многие вики хранят свои страницы правил отдельно от страниц основного содержания, в так называемом '''«пространстве имён проекта»'''.
+Все названия страниц в этом пространстве имён начинается с определённого префикса, который вы можете задать здесь.
+Обычно, этот префикс происходит от имени вики, но он не может содержать знаки препинания, символы «#» или «:».",
+ 'config-ns-invalid' => 'Указанное пространство имён <nowiki>$1</nowiki> недопустимо.
+Укажите другое пространство имён проекта.',
+ 'config-ns-conflict' => 'Указанное пространство имён «<nowiki>$1</nowiki>» конфликтует со стандартным пространством имён MediaWiki.
+Укажите другое пространство имён проекта.',
+ 'config-admin-box' => 'Учётная запись администратора',
+ 'config-admin-name' => 'Имя:',
+ 'config-admin-password' => 'Пароль:',
+ 'config-admin-password-confirm' => 'Пароль ещё раз:',
+ 'config-admin-help' => 'Введите ваше имя пользователя здесь, например, «Иван Иванов».
+Это имя будет использоваться для входа в вики.',
+ 'config-admin-name-blank' => 'Введите имя пользователя администратора.',
+ 'config-admin-name-invalid' => 'Указанное имя пользователя «<nowiki>$1</nowiki>» недопустимо.
+Укажите другое имя пользователя.',
+ 'config-admin-password-blank' => 'Введите пароль для учётной записи администратора.',
+ 'config-admin-password-same' => 'Пароль не должен быть таким же, как имя пользователя.',
+ 'config-admin-password-mismatch' => 'Введённые вами пароли не совпадают.',
+ 'config-admin-email' => 'Адрес электронной почты:',
+ 'config-admin-email-help' => 'Введите адрес электронной почты, чтобы получать сообщения от других пользователей вики, иметь возможность восстановить пароль, а также получать уведомления об изменениях страниц из списка наблюдения. Вы можете оставить это поле пустым.',
+ 'config-admin-error-user' => 'Внутренняя ошибка при создании учётной записи администратора с именем «<nowiki>$1</nowiki>».',
+ 'config-admin-error-password' => 'Внутренняя ошибка при установке пароля для учётной записи администратора «<nowiki>$1</nowiki>»: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Вы ввели неправильный адрес электронной почты',
+ 'config-subscribe' => 'Подписаться на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce рассылку новостей о появлении новых версий MediaWiki].',
+ 'config-subscribe-help' => 'Это список рассылки с малым числом сообщений, используется для анонса новых выпусков и сообщений о проблемах с безопасностью.
+Вам следует подписаться на него и обновлять движок MediaWiki, по мере выхода новых версий.',
+ 'config-almost-done' => 'Вы почти у цели!
+Остальные настройки можно пропустить и приступить к установке вики.',
+ 'config-optional-continue' => 'Произвести тонкую настройку',
+ 'config-optional-skip' => 'Хватит, установить вики',
+ 'config-profile' => 'Профиль прав прользователей:',
+ 'config-profile-wiki' => 'Традиционная вики',
+ 'config-profile-no-anon' => 'Требуется создание учётной записи',
+ 'config-profile-fishbowl' => 'Только для авторизованных редакторов',
+ 'config-profile-private' => 'Закрытая вики',
+ 'config-profile-help' => "Вики-технология лучше всего работает, когда вы позволяете редактировать сайт максимально широкому кругу лиц.
+В MediaWiki легко просмотреть последних изменений и, при необходимости, откатить любой ущерб сделанный злоумышленниками или наивными пользователями.
+
+Однако, движок MediaWiki можно использовать и иными способами, и не далеко не всех удаётся убедить в преимуществах открытой вики-работы.
+Так что в вас есть выбор.
+
+Конфигурация '''«{{int:config-profile-wiki}}»''' позволяет всем править страницы даже не регистрируясь на сайте. Конфигурация '''{{int:config-profile-no-anon}}''' обеспечивает дополнительный учёт, но может отсечь случайных участников.
+
+Сценарий '''«{{int:config-profile-fishbowl}}»''' разрешает редактирование только определённым участникам, но общедоступным остаётся просмотр страниц, в том числе просмотр истории изменения. В режиме '''«{{int:config-profile-private}}»''' просмотр страниц разрешён только определённым пользователям, какая-то их часть может иметь также права на редактирование.
+
+Более сложные схемы разграничения прав можно настроить после установки, см. [http://www.mediawiki.org/wiki/Manual:User_rights соответствующее руководство].",
+ 'config-license' => 'Авторские права и лицензии:',
+ 'config-license-none' => 'Не указывать лицензию в колонтитуле внизу страницы',
+ 'config-license-cc-by-sa' => 'Creative Commons атрибуция — с сохранением условий',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
+ 'config-license-cc-0' => 'Creative Commons Zero',
+ 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
+ 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 или более поздней версии',
+ 'config-license-pd' => 'Общественное достояние',
+ 'config-license-cc-choose' => 'Выберите одну из лицензий Creative Commons',
+ 'config-license-help' => "Многие общедоступные вики разрешают использовать свои материалы на условиях [http://freedomdefined.org/Definition/Ru свободных лицензий].
+Это помогает созданию чувства общности, стимулирует долгосрочное участие.
+Но в этом нет необходимости для частных или корпоративных вики.
+
+Если вы хотите использовать тексты из Википедии или хотите, что в Википедию можно было копировать тексты из вашей вики, вам следует выбрать '''Creative Commons Attribution Share Alike'''.
+
+GNU Free Documentation License раньше была основной лицензией Википедии.
+Она все ещё используется, однако, она имеет некоторые особенности, осложняющие повторное использование и интерпретацию её материалов.",
+ 'config-email-settings' => 'Настройки электронной почты',
+ 'config-enable-email' => 'Включить исходящие e-mail',
+ 'config-enable-email-help' => 'Если вы хотите, чтобы электронная почта работала, необходимо выполнить [http://www.php.net/manual/en/mail.configuration.php соответствующие настройки PHP].
+Если вы не хотите использовать возможности электронной почты в вики, вы можете её отключить.',
+ 'config-email-user' => 'Включить электронную почту от участника к участнику',
+ 'config-email-user-help' => 'Разрешить всем пользователям отправлять друг другу электронные письма, если выставлена соответствующая настройка в профиле.',
+ 'config-email-usertalk' => 'Включить уведомления пользователей о сообщениях на их странице обсуждения',
+ 'config-email-usertalk-help' => 'Разрешить пользователям получать уведомления об изменениях своих страниц обсуждения, если они разрешат это в своих настройках.',
+ 'config-email-watchlist' => 'Включить уведомление на электронную почту об изменении списка наблюдения',
+ 'config-email-watchlist-help' => 'Разрешить пользователям получать уведомления об отслеживаемых ими страницах, если они разрешили это в своих настройках.',
+ 'config-email-auth' => 'Включить аутентификацию через электронную почту',
+ 'config-email-auth-help' => "Если эта опция включена, пользователи должны подтвердить свой адрес электронной почты перейдя по ссылке, которая отправляется на e-mail. Подтверждение требуется каждый раз при смене электронного ящика в настройках пользователя.
+Только прошедшие проверку подлинности адреса электронной почты, могут получать электронные письма от других пользователей или изменять уведомления, отправляемые по электронной почте.
+Включение этой опции '''рекомендуется''' для открытых вики в целях пресечения потенциальных злоупотреблений возможностями электронной почты.",
+ 'config-email-sender' => 'Обратный адрес электронной почты:',
+ 'config-email-sender-help' => 'Введите адрес электронной почты для использования в качестве обратного адреса исходящей электронной почты.
+На него будут отправляться отказы.
+Многие почтовые серверы требуют, чтобы по крайней мере доменное имя в нём было правильным.',
+ 'config-upload-settings' => 'Загрузка изображений и файлов',
+ 'config-upload-enable' => 'Разрешить загрузку файлов',
+ 'config-upload-help' => 'Разрешение загрузки файлов, потенциально, может привести к угрозе безопасности сервера.
+Для получения дополнительной информации, прочтите в руководстве [http://www.mediawiki.org/wiki/Manual:Security раздел, посвящённый безопасности].
+
+Чтобы разрешить загрузку файлов, необходимо изменить права на каталог <code>images</code>, в корневой директории MediaWiki так, чтобы веб-сервер мог записывать в него файлы.
+Затем включите эту опцию.',
+ 'config-upload-deleted' => 'Директория для удалённых файлов:',
+ 'config-upload-deleted-help' => 'Выберите каталог, в котором будут храниться архивы удалённых файлов.
+В идеальном случае, в этот каталог не должно быть доступа из сети Интернет.',
+ 'config-logo' => 'URL логотипа:',
+ 'config-logo-help' => 'Тема по умолчанию для MediaWiki включает пространство для логотипа размером 135x160 в левом верхнем углу.
+Загрузите изображение соответствующего размера, и введите его URL здесь.
+
+Если вам не нужен логотип, оставьте это поле пустым.',
+ 'config-instantcommons' => 'Включить Instant Commons',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] — это функция, позволяющая использовать изображения, звуки и другие медиафайлы с Викисклада ([http://commons.wikimedia.org/ Wikimedia Commons]).
+Для работы этой функции MediaWiki необходим доступ к Интернету.
+
+Дополнительную информацию об Instant Commons, в том числе указания о том, как её настроить для других вики, отличных от Викисклада, можно найти в [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos руководстве].',
+ 'config-cc-error' => 'Механизм выбора лицензии Creative Commons не вернул результата.
+Введите название лицензии вручную.',
+ 'config-cc-again' => 'Выберите ещё раз…',
+ 'config-cc-not-chosen' => 'Выберите, какую лицензию Creative Commons Вы хотите использовать, и нажмите кнопку "Продолжить".',
+ 'config-advanced-settings' => 'Дополнительные настройки',
+ 'config-cache-options' => 'Параметры кэширования объектов:',
+ 'config-cache-help' => 'Кэширование объектов используется для повышения скорости MediaWiki путем кэширования часто используемых данных.
+Для средних и больших сайтов кеширование настоятельно рекомендуется включать, а для небольших сайтов кеширование может показать преимущество.',
+ 'config-cache-none' => 'Без кэширования (никакой функционал не теряется, но крупные вики-сайты могут работать медленнее)',
+ 'config-cache-accel' => 'PHP кэширование объектов (APC, eAccelerator, XCache или WinCache)',
+ 'config-cache-memcached' => 'Использовать Memcached (требует дополнительной настройки)',
+ 'config-memcached-servers' => 'Сервера Memcached:',
+ 'config-memcached-help' => 'Список IP-адресов, используемых Memcached.
+Перечислите по одному адресу на строку с указанием портов. Например:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Вы выбрали тип кэширования Memcached, но не задали адреса серверов.',
+ 'config-memcache-badip' => 'Вы ввели неверный IP-адрес для Memcached: $1.',
+ 'config-memcache-noport' => 'Не указан порт для сервера Memcached: $1.
+Если вы не знаете порт, по умолчанию используется 11211.',
+ 'config-memcache-badport' => 'Номера портов Memcached должны лежать в пределах от $1 до $2.',
+ 'config-extensions' => 'Расширения',
+ 'config-extensions-help' => 'Расширения MediaWiki, перечисленные выше, были найдены в каталоге <code>./extensions</code>.
+
+Они могут потребовать дополнительные настройки, но их можно включить прямо сейчас',
+ 'config-install-alreadydone' => "'''Предупреждение:''' Вы, кажется, уже устанавливали MediaWiki и пытаетесь произвести повторную установку.
+Пожалуйста, перейдите на следующую страницу.",
+ 'config-install-begin' => 'Нажав «{{int:config-continue}}», вы начнёте установку MediaWiki.
+Если вы хотите внести изменения, нажмите «Назад».',
+ 'config-install-step-done' => 'выполнено',
+ 'config-install-step-failed' => 'не удалось',
+ 'config-install-extensions' => 'В том числе расширения',
+ 'config-install-database' => 'Настройка базы данных',
+ 'config-install-pg-schema-not-exist' => 'Схемы PostgreSQL не существует',
+ 'config-install-pg-schema-failed' => 'Не удалось создать таблицы.
+Убедитесь в том, что пользователь «$1» может писать в схему «$2».',
+ 'config-install-pg-commit' => 'Внесение изменений',
+ 'config-install-pg-plpgsql' => 'Проверка языка PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Вам необходимо установить поддержку языка PL/pgSQL для базы данных $1',
+ 'config-pg-no-create-privs' => 'Учётная запись, указанная для установки, не обладает достаточными привилегиями для создания учётной записи.',
+ 'config-install-user' => 'Создание базы данных пользователей',
+ 'config-install-user-alreadyexists' => 'Участник «$1» уже существует',
+ 'config-install-user-create-failed' => 'Не получилось создать участника «$1»: $2',
+ 'config-install-user-grant-failed' => 'Ошибка предоставления прав пользователю «$1»: $2',
+ 'config-install-tables' => 'Создание таблиц',
+ 'config-install-tables-exist' => "'''Предупреждение''': таблицы MediaWiki, возможно, уже существуют.
+Пропуск повторного создания.",
+ 'config-install-tables-failed' => "'''Ошибка''': Таблица не может быть создана из-за ошибки: $1",
+ 'config-install-interwiki' => 'Заполнение таблицы интервики значениями по умолчанию',
+ 'config-install-interwiki-list' => 'Не удалось найти файл <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Предупреждение''': в интервики-таблице, кажется, уже есть записи.
+Создание стандартного списка, пропущено.",
+ 'config-install-stats' => 'Статистика инициализации',
+ 'config-install-keys' => 'Создание секретного ключа',
+ 'config-insecure-keys' => "'''Предупреждение.''' {{PLURAL:$2|Ключ безопасности $1, созданный во время установки, недостаточно надёжен|Ключи безопасности $1, созданные во время установки, недостаточно надёжны}}. Рассмотрите возможность {{PLURAL:$2|его|их}} изменения вручную.",
+ 'config-install-sysop' => 'Создание учётной записи администратора',
+ 'config-install-subscribe-fail' => 'Не удаётся подписаться на mediawiki-announce',
+ 'config-install-mainpage' => 'Создание главной страницы с содержимым по умолчанию',
+ 'config-install-extension-tables' => 'Создание таблиц для включённых расширений',
+ 'config-install-mainpage-failed' => 'Не удаётся вставить главную страницу: $1',
+ 'config-install-done' => "'''Поздравляем!'''
+Вы успешно установили MediaWiki.
+
+Во время установки был создан файл <code>LocalSettings.php</code>.
+Он содержит всю конфигурации вики.
+
+Вам необходимо скачать его и положить в корневую директорию вашей вики (ту же директорию, где находится файл index.php). Его загрузка должна начаться автоматически.
+
+Если автоматическая загрузка не началась или вы её отменили, вы можете скачать по ссылке ниже:
+
+$3
+
+'''Примечание''': Если вы не сделаете этого сейчас, то сгенерированный файл конфигурации не будет доступен вам в дальнейшем, если вы выйдете из установки, не скачивая его.
+
+По окончании действий, описанных выше, вы сможете '''[$2 войти в вашу вики]'''.",
+ 'config-download-localsettings' => 'Загрузить LocalSettings.php',
+ 'config-help' => 'справка',
+);
+
+/** Slovenian (Slovenščina)
+ * @author Dbc334
+ */
+$messages['sl'] = array(
+ 'config-desc' => 'Namestitveni program za MediaWiki',
+ 'config-title' => 'Namestitev MediaWiki $1',
+ 'config-information' => 'Informacije',
+ 'config-your-language' => 'Vaš jezik:',
+ 'config-back' => '← Nazaj',
+ 'config-continue' => 'Nadaljuj →',
+ 'config-page-language' => 'Jezik',
+ 'config-page-welcome' => 'Dobrodošli na MediaWiki!',
+ 'config-page-name' => 'Ime',
+ 'config-page-options' => 'Možnosti',
+ 'config-page-install' => 'Namesti',
+ 'config-page-complete' => 'Končano!',
+ 'config-page-readme' => 'Beri me',
+ 'config-page-copying' => 'Kopiranje',
+ 'config-page-upgradedoc' => 'Nadgrajevanje',
+ 'config-db-name' => 'Ime zbirke podatkov:',
+ 'config-db-username' => 'Uporabniško ime zbirke podatkov:',
+ 'config-db-password' => 'Geslo zbirke podatkov:',
+ 'config-admin-password' => 'Geslo:',
+);
+
+/** Serbian Cyrillic ekavian (‪Српски (ћирилица)‬) */
+$messages['sr-ec'] = array(
+ 'config-continue' => 'Настави →',
+ 'config-page-language' => 'Језик',
+);
+
+/** Swedish (Svenska)
+ * @author WikiPhoenix
+ */
+$messages['sv'] = array(
+ 'config-desc' => 'Installationsprogram för MediaWiki',
+ 'config-title' => 'Installation av MediaWiki $1',
+ 'config-information' => 'Information',
+ 'config-localsettings-key' => 'Uppgraderingsnyckel:',
+ 'config-localsettings-badkey' => 'Nyckeln du angav är inkorrekt.',
+ 'config-session-error' => 'Fel vid uppstart av session: $1',
+ 'config-your-language' => 'Ditt språk:',
+ 'config-your-language-help' => 'Välj ett språk som ska användas under installationen.',
+ 'config-wiki-language' => 'Wikispråk:',
+ 'config-wiki-language-help' => 'Välj det språk som wikin främst kommer att skrivas i.',
+ 'config-back' => '← Tillbaka',
+ 'config-continue' => 'Fortsätt →',
+ 'config-page-language' => 'Språk',
+ 'config-page-welcome' => 'Välkommen till MediaWiki!',
+ 'config-page-dbconnect' => 'Anslut till databas',
+ 'config-page-upgrade' => 'Uppgradera existerande installation',
+ 'config-page-dbsettings' => 'Databasinställningar',
+ 'config-page-name' => 'Namn',
+ 'config-page-options' => 'Alternativ',
+ 'config-page-install' => 'Installera',
+ 'config-page-complete' => 'Slutfört!',
+ 'config-page-restart' => 'Starta om installationen',
+ 'config-page-readme' => 'Läs mig',
+ 'config-page-releasenotes' => 'Utgivningsanteckningar',
+ 'config-page-copying' => 'Kopiering',
+ 'config-page-upgradedoc' => 'Uppgradering',
+ 'config-help-restart' => 'Vill du rensa all sparad data som du har skrivit in och starta om installationen?',
+ 'config-restart' => 'Ja, starta om',
+ 'config-sidebar' => '* [http://www.mediawiki.org MediaWikis hemsida]
+* [http://www.mediawiki.org/wiki/Help:Contents Användarguide]
+* [http://www.mediawiki.org/wiki/Manual:Contents Administratörguide]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Frågor och svar]
+----
+* <doclink href=Readme>Läs mig</doclink>
+* <doclink href=ReleaseNotes>Utgivningsanteckningar</doclink>
+* <doclink href=Copying>Kopiering</doclink>
+* <doclink href=UpgradeDoc>Uppgradering</doclink>',
+ 'config-env-good' => 'Miljön har kontrollerats.
+Du kan installera MediaWiki.',
+ 'config-env-bad' => 'Miljön har kontrollerats.
+Du kan inte installera MediaWiki.',
+ 'config-env-php' => 'PHP $1 är installerad.',
+ 'config-env-php-toolow' => 'PHP $1 är installerad.
+MediaWiki kräver PHP $2 eller högre.',
+ 'config-header-mysql' => 'MySQL-inställningar',
+ 'config-header-postgres' => 'PostgreSQL-inställningar',
+ 'config-header-sqlite' => 'SQLite-inställningar',
+ 'config-header-oracle' => 'Oracle-inställningar',
+ 'config-invalid-db-type' => 'Ogiltig databastyp',
+ 'config-missing-db-name' => 'Du måste ange ett värde för "Databasnamn"',
+ 'config-missing-db-host' => 'Du måste ange ett värde för "Databasvärd"',
+ 'config-invalid-db-name' => '"$1" är ett ogiltigt databasnamn.
+Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).',
+ 'config-invalid-db-prefix' => '"$1" är ett ogiltigt databasprefix.
+Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).',
+ 'config-connection-error' => '$1.
+
+Kontrollera värden, användarnamnet och lösenordet nedan och försök igen',
+ 'config-invalid-schema' => '"$1" är ett ogiltigt schema för MediaWiki.
+Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).',
+);
+
+/** Tamil (தமிழ்)
+ * @author TRYPPN
+ */
+$messages['ta'] = array(
+ 'config-information' => 'தகவல்',
+ 'config-your-language' => 'தங்களது மொழி:',
+ 'config-back' => '← முந்தைய',
+ 'config-continue' => 'தொடரவும் →',
+ 'config-page-language' => 'மொழி',
+ 'config-page-name' => 'பெயர்',
+ 'config-page-options' => 'விருப்பத்தேர்வுகள்',
+);
+
+/** Telugu (తెలుగు)
+ * @author Veeven
+ */
+$messages['te'] = array(
+ 'config-desc' => 'మీడియావికీ కొరకై స్థాపకి',
+ 'config-title' => 'మీడియావికీ $1స్థాపన',
+ 'config-information' => 'సమాచారం',
+ 'config-your-language' => 'మీ భాష:',
+ 'config-wiki-language' => 'వికీ భాష:',
+ 'config-back' => '← వెనక్కి',
+ 'config-continue' => 'కొనసాగించు →',
+ 'config-page-language' => 'భాష',
+ 'config-page-welcome' => 'మీడియావికీకి స్వాగతం!',
+ 'config-page-dbsettings' => 'డాటాబేసు అమరికలు',
+ 'config-page-name' => 'పేరు',
+ 'config-page-options' => 'ఎంపికలు',
+ 'config-page-install' => 'స్థాపించు',
+ 'config-page-complete' => 'పూర్తయ్యింది!',
+ 'config-page-readme' => 'నన్ను చదవండి',
+ 'config-page-releasenotes' => 'విడుదల విశేషాలు',
+ 'config-db-type' => 'డాటాబేసు రకం:',
+ 'config-db-name' => 'డాటాబేసు పేరు:',
+ 'config-db-install-account' => 'స్థాపనకి వాడుకరి ఖాతా',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-header-mysql' => 'MySQL అమరికలు',
+ 'config-header-postgres' => 'PostgreSQL అమరికలు',
+ 'config-header-sqlite' => 'SQLite అమరికలు',
+ 'config-header-oracle' => 'Oracle అమరికలు',
+ 'config-invalid-db-type' => 'తప్పుడు డాటాబేసు రకం',
+ 'config-connection-error' => '$1.
+
+క్రింది హోస్టు, వాడుకరిపేరు మరియు సంకేతపదాలను ఒకసారి సరిచూసుకుని అప్పుడు ప్రయత్నించండి.',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'వికీ యొక్క పేరు:',
+ 'config-ns-other' => 'ఇతర (ఇవ్వండి)',
+ 'config-admin-name' => 'మీ పేరు:',
+ 'config-admin-password' => 'సంకేతపదం:',
+ 'config-admin-password-confirm' => 'సంకేతపదం మళ్ళీ:',
+ 'config-admin-email' => 'ఈ-మెయిలు చిరునామా:',
+ 'config-profile-wiki' => 'సంప్రదాయ వికీ',
+ 'config-profile-no-anon' => 'ఖాతా సృష్టింపు తప్పనిసరి',
+ 'config-profile-private' => 'అంతరంగిక వికీ',
+ 'config-license' => 'కాపీహక్కులు మరియు లైసెన్సు:',
+ 'config-license-pd' => 'సార్వజనీనం',
+ 'config-email-settings' => 'ఈ-మెయిల్ అమరికలు',
+ 'config-upload-deleted' => 'తొలగించిన దస్త్రాల కొరకు సంచయం:',
+ 'config-install-step-done' => 'పూర్తయింది',
+ 'config-install-step-failed' => 'విఫలమైంది',
+);
+
+/** Tagalog (Tagalog)
+ * @author AnakngAraw
+ * @author Sky Harbor
+ */
+$messages['tl'] = array(
+ 'config-desc' => 'Ang instalador para sa MediaWiki',
+ 'config-title' => 'Instalasyong $1 ng MediaWiki',
+ 'config-information' => 'Kabatiran',
+ 'config-localsettings-key' => 'Susi ng pagsasapanahon:',
+ 'config-localsettings-badkey' => 'Hindi tama ang susing ibinigay mo.',
+ 'config-session-error' => 'Kamalian sa pagsisimula ng sesyon: $1',
+ 'config-no-session' => 'Nawala ang iyong datos ng sesyon!
+Suriin ang iyong php.ini at tiyakin na ang <code>session.save_path</code> ay nakatakda sa angkop na direktoryo.',
+ 'config-your-language' => 'Ang wika mo:',
+ 'config-your-language-help' => 'Pumili ng isang wikang gagamitin habang isinasagawa ang pagtatalaga.',
+ 'config-wiki-language' => 'Wika ng Wiki:',
+ 'config-wiki-language-help' => 'Piliin ang wika kung saan mangingibabaw na isusulat ang wiki.',
+ 'config-back' => '← Bumalik',
+ 'config-continue' => 'Magpatuloy →',
+ 'config-page-language' => 'Wika',
+ 'config-page-welcome' => 'Maligayang pagdating sa MediaWiki!',
+ 'config-page-dbconnect' => 'Umugnay sa kalipunan ng datos',
+ 'config-page-upgrade' => 'Itaas ng uri ang umiiral na pagkakatalaga',
+ 'config-page-dbsettings' => 'Mga katakdaan ng kalipunan ng datos',
+ 'config-page-name' => 'Pangalan',
+ 'config-page-options' => 'Mga mapipili',
+ 'config-page-install' => 'Italaga',
+ 'config-page-complete' => 'Buo na!',
+ 'config-page-restart' => 'Simulan muli ang pag-iinstala',
+ 'config-page-readme' => 'Basahin ako',
+ 'config-page-releasenotes' => 'Pakawalan ang mga tala',
+ 'config-page-copying' => 'Kinokopya',
+ 'config-page-upgradedoc' => 'Itinataas ang uri',
+ 'config-page-existingwiki' => 'Umiiral na wiki',
+ 'config-help-restart' => 'Nais mo bang hawiin ang lahat ng nasagip na datong ipinasok mo at muling simulan ang proseso ng pagluluklok?',
+ 'config-restart' => 'Oo, muling simulan ito',
+ 'config-welcome' => '=== Pagsusuring pangkapaligiran ===
+Isinasagawa ang payak na mga pagsusuri upang makita kung ang kapaligirang ito ay angkop para sa pagluluklok ng MediaWiki.
+Dapat mong ibigay ang mga kinalabasan ng mga pagsusuring ito kung kailangan mo ng tulong habang nagluluklok.',
+ 'config-copyright' => "=== Karapatang-ari at Tadhana ===
+
+$1
+
+Ang programang ito ay malayang software; maaari mo itong ipamahagi at/o baguhin sa ilalim ng mga tadhana ng Pangkalahatang Pampublikong Lisensiyang GNU ayon sa pagkakalathala ng Free Software Foundation; na maaaring bersyong 2 ng Lisensiya, o (kung nais mo) anumang susunod na bersyon.
+
+Ipinamamahagi ang programang ito na umaasang magiging gamitin, subaliut '''walang anumang katiyakan'''; na walang pahiwatig ng '''pagiging mabenta''' o '''kaangkupan para sa isang tiyak na layunin'''.
+Tingnan ang Pangkalahatang Pampublikong Lisensiyang GNU para sa mas maraming detalye.
+
+Dapat nakatanggap ka ng <doclink href=Copying>isang sipi ng Pangkalahatang Pampublikong Lisensiyang GNU</doclink> kasama ng programang ito; kung hindi, sumulat sa Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, o [http://www.gnu.org/licenses//gpl.html basahin ito sa Internet].",
+ 'config-sidebar' => '* [http://www.mediawiki.org Tahanan ng MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents Gabay ng Tagagamit]
+* [http://www.mediawiki.org/wiki/Manual:Contents Gabay ng Tagapangasiwa]
+* [http://www.mediawiki.org/wiki/Manual:FAQ Mga Malimit Itanong]
+----
+* <doclink href=Readme>Basahin ako</doclink>
+* <doclink href=ReleaseNotes>Mga tala ng paglalabas</doclink>
+* <doclink href=Copying>Pagkopya</doclink>
+* <doclink href=UpgradeDoc>Pagsasapanahon</doclink>',
+ 'config-env-good' => 'Nasuri na ang kapaligiran.
+Mailuluklok mo ang MediaWiki.',
+ 'config-env-bad' => 'Nasuri na ang kapaligiran.
+Hindi mo mailuklok ang MediaWiki.',
+ 'config-env-php' => 'Naitalaga ang PHP na $1.',
+ 'config-env-php-toolow' => 'Naitalaga ang PHP $1.
+Subalit, nangangailangan ang MediaWiki ng PHP $2 o mas mataas pa.',
+ 'config-unicode-using-utf8' => 'Ginagamit ang utf8_normalize.so ni Brion Vibber para sa pagpapanormal ng Unikodigo.',
+ 'config-unicode-using-intl' => 'Ginagamit ang [http://pecl.php.net/intl intl dugtong na PECL] para sa pagsasanormal ng Unikodigo.',
+ 'config-no-db' => 'Hindi matagpuan ang isang angkop na drayber ng kalipunan ng datos!',
+ 'config-memory-raised' => 'Ang <code>hangganan_ng_alaala</code> ng PHP ay $1, itinaas sa $2.',
+ 'config-memory-bad' => "'''Babala:''' Ang <code>hangganan_ng_alaala</code> ng PHP ay $1.
+Ito ay maaaring napakababa.
+Maaaring mabigo ang pagluluklok!",
+ 'config-xcache' => 'Ininstala na ang [http://trac.lighttpd.net/xcache/ XCache]',
+ 'config-apc' => 'Ininstala na ang [http://www.php.net/apc APC]',
+ 'config-eaccel' => 'Ininstala na ang [http://eaccelerator.sourceforge.net/ eAccelerator]',
+ 'config-wincache' => 'Ininstala na ang [http://www.iis.net/download/WinCacheForPhp WinCache]',
+ 'config-no-cache' => "'''Babala:''' Hindi mahanap ang [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://trac.lighttpd.net/xcache/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
+Hindi pinapagana ang pagbabaon ng mga bagay.",
+ 'config-diff3-bad' => 'Hindi natagpuan ang GNU diff3.',
+ 'config-imagemagick' => 'Natagpuan ang ImageMagick: <code>$1</code>.
+Papaganahin ang pagkakagyat ng larawan kapag pinagana mo ang mga pagkakargang paitaas.',
+ 'config-no-scaling' => 'Hindi matagpuan ang aklatang GD o ImageMagick.
+Hindi papaganahin ang pagkakagyat ng larawan.',
+ 'config-no-uri' => "'''Kamalian:''' Hindi matukoy ang kasalukuyang URI.
+Pinigilan ang pag-iinstala.",
+ 'config-db-type' => 'Uri ng kalipunan ng datos:',
+ 'config-db-host' => 'Tagapagpasinaya ng kalipunan ng datos:',
+ 'config-db-host-oracle' => 'TNS ng kalipunan ng dato:',
+ 'config-db-wiki-settings' => 'Kilalanin ang wiking ito',
+ 'config-db-name' => 'Pangalan ng kalipunan ng dato:',
+ 'config-db-install-account' => 'Akawnt ng tagagamit para sa pagluluklok',
+ 'config-db-username' => 'Pangalang pangtagagamit ng kalipunan ng dato:',
+ 'config-db-password' => 'Hudyat sa kalipunan ng dato:',
+ 'config-db-install-help' => 'Ipasok ang pangalan ng tagagamit at hudyat na gagamitin upang umugnay sa kalipunan ng dato habang isinasagawa ang pagluluklok.',
+ 'config-db-account-lock' => 'Gamitin ang gayun ding pangalan ng tagagamit at hudyat habang nasa normal na operasyon',
+ 'config-db-wiki-account' => 'Akawnt ng tagagamit para sa pangkaraniwang pagpapaandar',
+ 'config-db-prefix' => 'Unlapi ng talahanayan ng kalipunan ng dato:',
+ 'config-db-charset' => 'Pangkat ng panitik ng kalipunan ng dato',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binaryo',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 paurong-kabagay UTF-8',
+ 'config-mysql-old' => 'Hindi kailangan ang MySQL na $1 o mas bago, mayroon kang $2.',
+ 'config-db-port' => 'Daungan ng kalipunan ng dato:',
+ 'config-db-schema' => 'Panukala para sa MediaWiki',
+ 'config-db-schema-help' => 'Ang nasa itaas na panukala ay pangkaraniwang magiging maayos.
+Baguhin lamang ito kung alam mong kinakailangan.',
+ 'config-sqlite-dir' => 'Direktoryo ng dato ng SQLite:',
+ 'config-oracle-def-ts' => 'Likas na nakatakdang puwang ng talahanayan:',
+ 'config-oracle-temp-ts' => 'Pansamantalang puwang ng talahanayan:',
+ 'config-header-mysql' => 'Mga katakdaan ng MySQL',
+ 'config-header-postgres' => 'Mga katakdaan ng PostgreSQL',
+ 'config-header-sqlite' => 'Mga katakdaan ng SQLite',
+ 'config-header-oracle' => 'Mga katakdaan ng Oracle',
+ 'config-invalid-db-type' => 'Hindi tanggap na uri ng kalipunan ng dato',
+ 'config-missing-db-name' => 'Dapat kang magpasok ng isang halaga para sa "pangalan ng Kalipunan ng Dao"',
+ 'config-invalid-db-name' => 'Hindi tanggap na pangalan ng kalipunan ng dato na "$1".
+Gumamit lamang ng mga titik ng ASCII (a-z, A-Z), mga bilang (0-9), mga salangguhit (_) at mga gitling (-).',
+ 'config-invalid-db-prefix' => 'Hindi tanggap na unlapi ng kalipunan ng dato na "$1".
+Gamitin lamang ang mga titik na ASCII (a-z, A-Z), mga bilang (0-9), mga salangguhit (_) at mga gitling (-).',
+ 'config-postgres-old' => 'Kailangan ang PostgreSQL $1 o mas bago, mayroon kang $2.',
+ 'config-sqlite-readonly' => 'Ang talaksang <code>$1</code> ay hindi maisusulat.',
+ 'config-sqlite-cant-create-db' => 'Hindi malikha ang talaksang <code>$1</code> ng kalipunan ng dato.',
+ 'config-sqlite-fts3-downgrade' => 'Nawawala ang suportang FTS3 ng PHP, ibinababa ang uri ng mga talahanayan',
+ 'config-regenerate' => 'Muling likhain ang LocalSettings.php →',
+ 'config-show-table-status' => 'Nabigo ang pagtatanong na IPAKITA ANG KALAGAYAN NG TALAHANAYAN!',
+ 'config-db-web-account' => 'Akawnt ng kalipunan ng dato para sa pagpunta sa web',
+ 'config-db-web-help' => 'Piliin ang pangalan ng tagagamit at hudyat na gagamitin ng tagapaghain ng web upang umugnay sa tagapaghain ng kalipunan ng dato, habang nasa pangkaraniwang pagtakbo ng wiki.',
+ 'config-db-web-account-same' => 'Gamitin ang gayun din akawnt katulad ng sa pagluluklok',
+ 'config-db-web-create' => 'Likhain ang akawnt kung hindi pa ito umiiral',
+ 'config-mysql-engine' => 'Makinang imbakan:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => 'Pangkat ng panitik ng kalipunan ng dato:',
+ 'config-mysql-binary' => 'Binaryo',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Pangalan ng wiki:',
+ 'config-site-name-help' => "Lilitaw ito sa bareta ng pamagat ng pantingin-tingin at sa samu't saring ibang mga lugar.",
+ 'config-site-name-blank' => 'Magpasok ng isang pangalan ng sityo.',
+ 'config-project-namespace' => 'Puwang na pampangalan ng proyekto:',
+ 'config-ns-generic' => 'Proyekto',
+ 'config-ns-site-name' => 'Katulad ng sa pangalan ng wiki: $1',
+ 'config-ns-other' => 'Iba pa (tukuyin)',
+ 'config-ns-other-default' => 'Wiki Ko',
+ 'config-admin-box' => 'Akawnt ng tagapangasiwa',
+ 'config-admin-name' => 'Pangalan mo:',
+ 'config-admin-password' => 'Hudyat:',
+ 'config-admin-password-confirm' => 'Hudyat uli:',
+ 'config-admin-name-blank' => 'Magpasok ng isang pangalan ng tagagamit na tagapangasiwa.',
+ 'config-admin-name-invalid' => 'Ang tinukoy na pangalan ng tagagamit na "<nowiki>$1</nowiki>" ay hindi tanggap.
+Tumukoy ng ibang pangalan ng tagagamit.',
+ 'config-admin-password-blank' => 'Magpasok ng isang hudyat para sa akawnt ng tagapangasiwa.',
+ 'config-admin-password-same' => 'Ang hudyat ay hindi dapat na katulad ng pangalan ng tagagamit.',
+ 'config-admin-password-mismatch' => 'Hindi magkatugma ang ipinasok mong dalawang mga hudyat.',
+ 'config-admin-email' => 'Tirahan ng e-liham:',
+ 'config-admin-error-user' => 'Panloob na kamalian kapag nililikha ang isang tagapangasiwa na may pangalang "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Panloob na kamalian kapag nagtatakda ng isang hudyat na para sa tagapangasiwang "<nowiki>$1</nowiki>": <pre>$2</pre>',
+ 'config-subscribe' => 'Tumanggap mula sa [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce talaan ng mga pinadadalhan ng mga nilalabas na mga pabatid].',
+ 'config-almost-done' => 'Halos tapos ka na!
+Maaari mo ngayong laktawan ang natitira pang pag-aayos at iluklok na ang wiki ngayon.',
+ 'config-optional-continue' => 'Magtanong sa akin ng marami pang mga tanong.',
+ 'config-optional-skip' => 'Naiinip na ako, basta iluklok na lang ang wiki.',
+ 'config-profile' => 'Balangkas ng mga karapatan ng tagagamit:',
+ 'config-profile-wiki' => 'Tradisyonal na wiki',
+ 'config-profile-no-anon' => 'Kailangan ang paglikha ng akawnt',
+ 'config-profile-fishbowl' => 'Pinahintulutang mga patnugot lamang',
+ 'config-profile-private' => 'Pribadong wiki',
+ 'config-license' => 'Karapatang-ari at lisensiya:',
+ 'config-license-none' => 'Walang talababa ng lisensiya',
+ 'config-license-cc-by-sa' => 'Malikhaing Pangkaraniwang Pagtukoy Pamamahaging Magkatulad',
+ 'config-license-cc-by-nc-sa' => 'Malikhaing Pangkaraniwang Pagtukoy Hindi-Pangkalakal Pamamahaging Magkatulad',
+ 'config-license-gfdl-old' => 'Lisensiya ng Malayang Dokumenstasyon 1.2 ng GNU',
+ 'config-license-gfdl-current' => 'Lisensiya ng Malayang Dokumenstasyon 1.3 ng GNU o mas bago',
+ 'config-license-pd' => 'Nasasakupan ng Madla',
+ 'config-license-cc-choose' => 'Pumili ng isang pasadyang Lisensiya ng Malikhaing mga Pangkaraniwan',
+ 'config-email-settings' => 'Mga katakdaan ng e-liham',
+ 'config-enable-email' => 'Paganahin ang palabas na e-liham',
+ 'config-email-user' => 'Paganahin ang tagagamit-sa-tagagamit na e-liham',
+ 'config-email-user-help' => 'Payagan ang lahat ng mga tagagamit na magpadala ng e-liham sa bawat isa kapag pinagana nila ito sa kanilang mga nais.',
+ 'config-email-usertalk' => 'Paganahin ang pabatid na pampahina ng usapan ng tagagamit',
+ 'config-email-usertalk-help' => 'Payagan ang mga tagagamit na tumanggap ng mga pabatid sa mga pagbabago ng pahina ng usapan ng tagagamit, kapag pinagana nila ito sa kanilang mga nais.',
+ 'config-email-watchlist' => 'Paganahin ang pabatid ng talaan ng bantayan',
+ 'config-email-watchlist-help' => 'Payagan ang mga tagagamit na tumanggap ng mga pabatid tungkol sa kanilang binabantayang mga pahina kapag pinagana nila ito sa kanilang mga nais.',
+ 'config-email-auth' => 'Paganahin ang pagpapatunay ng e-liham',
+ 'config-email-sender' => 'Pabalik na tirahan ng e-liham:',
+ 'config-upload-settings' => 'Mga pagkakarga ng mga larawan at talaksan',
+ 'config-upload-enable' => 'Paganahin ang pagkakarga ng talaksan',
+ 'config-upload-deleted' => 'Direktoryo para sa binurang mga talaksan:',
+ 'config-upload-deleted-help' => 'Pumili ng isang direktoryong pagsusupnayan ng naburang mga talaksan.
+Ideyal na dapat itong hindi mapupuntahan mula sa web.',
+ 'config-logo' => 'URL ng logo:',
+ 'config-instantcommons' => 'Paganahin ang Mga Pangkaraniwang Biglaan',
+ 'config-cc-error' => 'Hindi nagbigay ng resulta ang pampili ng lisensiya ng Malikhaing Pangkaraniwan.
+Ipasok na kinakamay ang pangalan ng lisensiya.',
+ 'config-cc-again' => 'Pumili uli...',
+ 'config-cc-not-chosen' => 'Piliin kung anong lisensiya ng Malikhaing mga Pangkaraniwan ang nais mo at pindutin ang "magpatuloy".',
+ 'config-advanced-settings' => 'Mas masulong na pagkakaayos',
+ 'config-cache-options' => 'Mga katakdaan para sa pagtatago ng bagay:',
+ 'config-memcached-servers' => 'Mga tagapaghaing itinago sa alaala:',
+ 'config-memcache-needservers' => 'Pinili mo ang Memcached bilang uri mo ng taguan ngunit hindi tumukoy ng anumang mga tagapaghain.',
+ 'config-memcache-badip' => 'Nagpasok ka ng isang hindi tanggap na tirahan ng IP para sa Memcached: $1.',
+ 'config-memcache-noport' => 'Hindi ka tumukoy ng isang daungan na gagamitin para sa tagapaghain ng Memcached: $1.
+Kung hindi mo alam ang daungan, ang likas na nakatakda ay 11211.',
+ 'config-memcache-badport' => 'Ang bilang ng daungan ng Memcached ay dapat na nasa pagitan ng $1 at $2.',
+ 'config-extensions' => 'Mga dugtong',
+ 'config-install-step-done' => 'nagawa na',
+ 'config-install-step-failed' => 'nabigo',
+ 'config-install-extensions' => 'Isinasama ang mga karugtong',
+ 'config-install-database' => 'Inihahanda ang kalipunan ng dato',
+ 'config-install-pg-schema-failed' => 'Nabigo ang paglikha ng mga talahanayan.
+Tiyakin na ang tagagamit na "$1" ay maaaring makasulat sa balangkas na "$2".',
+ 'config-install-pg-commit' => 'Isinasagawa ang mga pagbabago',
+ 'config-install-pg-plpgsql' => 'Sumusuri ng wikang PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Kailangan mong magtalaga ng wikang PL/pgSQL sa loob ng kalipunan ng datong $1',
+ 'config-pg-no-create-privs' => 'Ang tinukoy mong akawnt para sa pagtatalaga ay walang sapat na mga pribilehiyo upang makalikha ng isang akawnt.',
+ 'config-install-user' => 'Nililikha ang tagagamit ng kalipunan ng dato',
+ 'config-install-user-alreadyexists' => 'Umiiral na ang tagagamit na "$1"',
+ 'config-install-user-create-failed' => 'Nabigo ang paglikha ng tagagamit na "$1": $2',
+ 'config-install-user-grant-failed' => 'Nabigo ang pagbibigay ng pahintulot sa tagagamit na "$1": $2',
+ 'config-install-tables' => 'Nililikha ang mga talahanayan',
+ 'config-install-tables-exist' => "'''Babala''': Tila umiiral na ang mga talahanayan ng MediaWiki.
+Nilalaktawan ang paglikha.",
+ 'config-install-tables-failed' => "'''Kamalian''': Nabigo ang paglikha ng talahanayan na may sumusunod na kamalian: $1",
+ 'config-install-interwiki' => 'Nilalagyan ng laman ang likas na nakatakdang talahanayan ng interwiki',
+ 'config-install-interwiki-list' => 'Hindi matagpuan ang talaksang <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Babala''': Tila may mga laman na ang talahanayan ng interwiki.
+Nilalaktawan ang likas na nakatakdang talaan.",
+ 'config-install-stats' => 'Sinisimulan ang estadistika',
+ 'config-install-keys' => 'Ginagawa ang lihim na susi',
+ 'config-install-sysop' => 'Nililikha ang akawnt ng tagagamit na tagapangasiwa',
+ 'config-install-subscribe-fail' => 'Hindi nagawang sumipi mula sa mediawiki-announce',
+ 'config-install-mainpage' => 'Nililikha ang pangunahing pahina na may likas na nakatakdang nilalaman',
+ 'config-install-extension-tables' => 'Nililikha ang mga talahanayan para sa pinagaganang mga dugtong',
+ 'config-install-mainpage-failed' => 'Hindi maisingit ang pangunahing pahina: $1',
+ 'config-install-done' => "'''Maligayang bati!'''
+Matagumpay mong nailuklok ang MediaWiki.
+
+Ang tagapagluklok ay nakagawa ng isang talaksan ng <code>LocalSettings.php</code>.
+Naglalaman ito ng lahat ng iyong mga pagsasaayos.
+
+Kailangan mo itong ikargang paibaba at ilagay ito sa lipon ng iyong pagluluklok ng wiki (katulad ng direktoryo ng index.php). Ang pagkakargang paibaba ay dapat na kusang magsimula.
+
+Kung ang pagkakargang paibaba ay hindi inialok, o kung hindi mo ito itinuloy, maaari mong muling simulan ang pagkakargang paibaba sa pamamagitan ng pagpindot sa kawing na nasa ibaba:
+
+$3
+
+'''Paunawa''': Kapag hindi mo ito ginawa ngayon, ang nagawang talaksang ito ng pagkakaayos ay hindi mo na makukuha mamaya kapag lumabas ka mula sa pagluluklok na hindi ikinakarga itong paibaba.
+
+Kapag nagawa na iyan, maaari ka nang '''[$2 pumasok sa wiki mo]'''.",
+ 'config-download-localsettings' => 'Ikargang paibaba ang LocalSettings.php',
+ 'config-help' => 'saklolo',
+);
+
+/** Ukrainian (Українська)
+ * @author Ahonc
+ * @author Alex Khimich
+ * @author Diemon.ukr
+ * @author Тест
+ */
+$messages['uk'] = array(
+ 'config-desc' => 'Інсталятор MediaWiki',
+ 'config-title' => 'Встановлення MediaWiki $1',
+ 'config-information' => 'Інформація',
+ 'config-localsettings-upgrade' => "'''Увага''': було виявлено файл <code>LocalSettings.php</code>.
+Ваше програмне забезпечення може бути оновлено.
+Будь-ласка, перемістіть файл <code>LocalSettings.php</code> в іншу безпечну директорію, а потім знову запустіть програму установки.",
+ 'config-session-error' => 'Помилка початку сесії: $1',
+ 'config-your-language' => 'Ваша мова:',
+ 'config-your-language-help' => 'Оберіть мову для використання в процесі установки.',
+ 'config-wiki-language' => 'Мова для вікі:',
+ 'config-wiki-language-help' => 'Виберіть мову, якою буде відображатися вікі.',
+ 'config-back' => '← Назад',
+ 'config-continue' => 'Далі →',
+ 'config-page-language' => 'Мова',
+ 'config-page-welcome' => 'Ласкаво просимо на MediaWiki!',
+ 'config-page-dbconnect' => 'Підключення до бази даних',
+ 'config-page-upgrade' => 'Оновлення існуючої установки',
+ 'config-page-dbsettings' => 'Налаштування бази даних',
+ 'config-page-name' => 'Назва',
+ 'config-page-options' => 'Параметри',
+ 'config-page-install' => 'Установка',
+ 'config-page-complete' => 'Готово!',
+ 'config-page-restart' => 'Перезапустити установку',
+ 'config-page-readme' => 'Прочитай мене',
+ 'config-page-releasenotes' => 'Інформація про версію',
+ 'config-page-copying' => 'Копіювання',
+ 'config-page-upgradedoc' => 'Оновлення',
+ 'config-help-restart' => 'Ви бажаєте видалити всі введені та збережені вами дані і запустити процес установки спочатку?',
+ 'config-restart' => 'Так, перезапустити установку',
+ 'config-welcome' => '=== Перевірка оточення ===
+Проводяться базові перевірки, щоб виявити, чи можлива установка MediaWiki у даній системі.
+Вкажіть результати цих перевірок при зверненні за допомогою під час установки.',
+ 'config-sidebar' => '* [http://www.mediawiki.org Сайт MediaWiki]
+* [http://www.mediawiki.org/wiki/Help:Contents/uk Керівництво користувача]
+* [http://www.mediawiki.org/wiki/Manual:Contents/uk Керівництво адміністратора]
+* [http://www.mediawiki.org/wiki/Manual:FAQ/uk FAQ]',
+ 'config-env-good' => 'Перевірку середовища успішно завершено.
+Ви можете встановити MediaWiki.',
+ 'config-env-bad' => 'Було проведено перевірку середовища. Ви не можете встановити MediaWiki.',
+ 'config-env-php' => 'Встановлено версію PHP: $1.',
+ 'config-unicode-using-utf8' => 'Використовувати utf8_normalize.so Брайона Віббера для нормалізації Юнікоду.',
+ 'config-unicode-using-intl' => 'Використовувати [http://pecl.php.net/intl міжнародне розширення PECL] для нормалізації Юнікоду.',
+ 'config-unicode-pure-php-warning' => "'''Увага''': [http://pecl.php.net/intl міжнародне розширення PECL] не може провести нормалізацію Юнікоду.
+Якщо ваш сайт має високий трафік, вам варто почитати про [http://www.mediawiki.org/wiki/Unicode_normalization_considerations нормалізацію Юнікоду].",
+ 'config-no-db' => 'Не вдалося знайти відповідний драйвер бази даних!',
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] встановлено',
+ 'config-apc' => '[http://www.php.net/apc APC] встановлено',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] встановлено',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] встановлено',
+ 'config-db-type' => 'Тип бази даних:',
+ 'config-db-host' => 'Хост бази даних:',
+ 'config-db-name' => 'Назва бази даних:',
+ 'config-db-password' => 'Пароль бази даних:',
+ 'config-db-charset' => 'Кодування бази даних',
+ 'config-db-port' => 'Порт бази даних:',
+ 'config-invalid-db-type' => 'Невірний тип бази даних',
+ 'config-invalid-db-name' => 'Неприпустима назва бази даних "$1".
+Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).',
+ 'config-invalid-db-prefix' => 'Неприпустимий префікс бази даних "$1".
+Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).',
+ 'config-sqlite-cant-create-db' => 'Не вдалося створити файл бази даних <code>$1</code>.',
+ 'config-db-web-create' => 'Створити обліковий запис, якщо його ще не існує',
+ 'config-mysql-charset' => 'Кодування бази даних:',
+ 'config-mysql-binary' => 'Двійкове',
+ 'config-site-name' => 'Назва вікі:',
+ 'config-site-name-blank' => 'Введіть назву сайту.',
+ 'config-project-namespace' => 'Простір назв проекту:',
+ 'config-ns-generic' => 'Проект',
+ 'config-admin-name' => "Ваше ім'я:",
+ 'config-admin-password' => 'Пароль:',
+ 'config-admin-password-confirm' => 'Пароль ще раз:',
+ 'config-admin-password-mismatch' => 'Два введені вами паролі не збігаються.',
+ 'config-admin-email' => 'Адреса електронної пошти:',
+ 'config-license' => 'Авторські права і ліцензія:',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
+ 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
+ 'config-email-settings' => 'Налаштування електронної пошти',
+ 'config-upload-enable' => 'Дозволити завантаження файлів',
+ 'config-upload-deleted' => 'Каталог для вилучених файлів:',
+ 'config-cc-again' => 'Виберіть знову ...',
+ 'config-extensions' => 'Розширення',
+ 'config-install-step-done' => 'виконано',
+ 'config-install-step-failed' => 'не вдалося',
+ 'config-install-interwiki-list' => 'Не вдалося знайти файл <code>interwiki.list</code>.',
+);
+
+/** Yiddish (ייִדיש)
+ * @author פוילישער
+ */
+$messages['yi'] = array(
+ 'config-admin-name' => 'אײַער נאָמען:',
+);
+
+/** Simplified Chinese (‪中文(简体)‬)
+ * @author Hydra
+ * @author PhiLiP
+ * @author 阿pp
+ */
+$messages['zh-hans'] = array(
+ 'config-desc' => 'MediaWiki安装程序',
+ 'config-title' => 'MediaWiki $1配置',
+ 'config-information' => '信息',
+ 'config-localsettings-upgrade' => '已检测到<code>LocalSettings.php</code>文件。要升级该配置,请在下面的框中输入<code>$wgUpgradeKey</code>的值。您可以在LocalSettings.php中找到它。',
+ 'config-localsettings-cli-upgrade' => '已检测到LocalSettings.php文件。要升级该配置,请使用--upgrade=yes选项。',
+ 'config-localsettings-key' => '升级密钥:',
+ 'config-localsettings-badkey' => '您提供的密钥不正确。',
+ 'config-upgrade-key-missing' => '检测到MediaWiki的配置已经存在。若要升级该配置,请将下面一行文本添加到LocalSettings.php的底部:
+
+$1',
+ 'config-localsettings-incomplete' => '当前的LocalSettings.php可能并不完整,因为变量$1没有设置。请在LocalSettings.php设置该变量,并单击“继续”。',
+ 'config-localsettings-connection-error' => '在使用LocalSettings.php或AdminSettings.php中指定的设置连接数据库时发生错误。请修复相应设置并重试。
+
+$1',
+ 'config-session-error' => '启动会话出错:$1',
+ 'config-session-expired' => '您的会话数据可能已经过期,当前会话的使用期限被设定为$1。您可以在php.ini中设置<code>session.gc_maxlifetime</code>来延长此期限,并重新启动本配置程序。',
+ 'config-no-session' => '您的会话数据丢失了!请检查php.ini并确保<code>session.save_path</code>被设置为适当的目录。',
+ 'config-your-language' => '您使用的语言:',
+ 'config-your-language-help' => '选择在安装过程中使用的语言。',
+ 'config-wiki-language' => 'Wiki使用的语言:',
+ 'config-wiki-language-help' => '选择将要安装的wiki在多数情况下使用的语言。',
+ 'config-back' => '← 后退',
+ 'config-continue' => '继续 →',
+ 'config-page-language' => '语言',
+ 'config-page-welcome' => '欢迎使用MediaWiki!',
+ 'config-page-dbconnect' => '连接到数据库',
+ 'config-page-upgrade' => '升级当前配置',
+ 'config-page-dbsettings' => '数据库设置',
+ 'config-page-name' => '名称',
+ 'config-page-options' => '选项',
+ 'config-page-install' => '安装',
+ 'config-page-complete' => '完成!',
+ 'config-page-restart' => '重新开始安装',
+ 'config-page-readme' => '自述',
+ 'config-page-releasenotes' => '发布说明',
+ 'config-page-copying' => '复制',
+ 'config-page-upgradedoc' => '更新',
+ 'config-page-existingwiki' => '已有wiki',
+ 'config-help-restart' => '是否要清除所有已输入且保存的数据,并重新启动安装过程吗?',
+ 'config-restart' => '是的,重启吧',
+ 'config-welcome' => '=== 环境检查 ===
+对当前环境是否适合安装MediaWiki作基本的检查。如果您在安装过程中需要帮助,请提供这些检查的结果。',
+ 'config-copyright' => "=== 版权和条款 ===
+
+\$1
+
+本程序为自由软件;您可依据自由软件基金会所发表的GNU通用公共授权条款规定,就本程序再为发布与/或修改;无论您依据的是本授权的第二版或(您自行选择的)任一日后发行的版本。
+
+本程序是基于使用目的而加以发布,然而'''不负任何担保责任''';亦无对'''适售性'''或'''特定目的适用性'''所为的默示性担保。详情请参照GNU通用公共授权。
+
+您应已收到附随于本程序的<doclink href=\"Copying\">GNU通用公共授权的副本</doclink>;如果没有,请写信至自由软件基金会:59 Temple Place - Suite 330, Boston, Ma 02111-1307, USA,或[http://www.gnu.org/copyleft/gpl.html 在线阅读]。",
+ 'config-sidebar' => '* [http://www.mediawiki.org/wiki/MediaWiki/zh-hans MediaWiki首页]
+* [http://www.mediawiki.org/wiki/Help:Contents/zh-hans 用户帮助]
+* [http://www.mediawiki.org/wiki/Manual:Contents 管理员帮助]
+* [http://www.mediawiki.org/wiki/Manual:FAQ/zh-hans 常见问题解答]',
+ 'config-env-good' => '环境检查已经完成。您可以安装MediaWiki。',
+ 'config-env-bad' => '环境检查已经完成。您不能安装MediaWiki。',
+ 'config-env-php' => 'PHP $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' => "'''警告''':[http://pecl.php.net/intl intl PECL扩展]无法处理Unicode正常化,故只能退而采用运行较慢的纯PHP实现的方法。如果您运行着一个高流量的站点,请参阅[http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode正常化]一文。",
+ 'config-unicode-update-warning' => "'''警告''':Unicode正常化封装器的已安装版本使用了旧版本的[http://site.icu-project.org/ ICU项目]库。如果您需要使用Unicode,请将其[http://www.mediawiki.org/wiki/Unicode_normalization_considerations 升级]。",
+ 'config-no-db' => '找不到合适的数据库驱动!',
+ 'config-no-db-help' => '您需要为PHP安装数据库驱动。MediaWiki支持下列数据库类型:$1
+
+如果您正在使用共享主机,请让您的主机提供商为您安装适当的数据库驱动。
+如果PHP由您自行编译,请将其重新配置以启用数据库客户端,例如使用<code>./configure --with-mysql</code>。
+如果PHP是您通过Debian或Ubuntu包安装的,那么您还需要安装php5-mysql模块。',
+ 'config-no-fts3' => "'''警告''':已编译的SQLite不包含[http://sqlite.org/fts3.html FTS3模块],后台搜索功能将不可用。",
+ 'config-register-globals' => "'''警告:PHP的<code>[http://php.net/register_globals register_globals]</code>选项被启用。请尽量禁用该功能,'''虽然不会影响MediaWiki的运行,但您的服务器会被暴露给潜在的安全漏洞。",
+ 'config-magic-quotes-runtime' => "'''致命错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]被启用!'''
+此选项会无法预测地破坏输入的数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
+ 'config-magic-quotes-sybase' => "'''致命错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase]被启用!'''
+此选项会无法预测地破坏输入的数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
+ 'config-mbstring' => "'''致命错误:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]被启用!'''
+此选项会导致错误并不可预测地破坏数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
+ 'config-ze1' => "'''致命错误:[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]被启用!'''
+此选项将导致MediaWiki出现极其严重的故障,请将其禁用,否则您将不能安装或使用MediaWiki。",
+ 'config-safe-mode' => "'''警告:'''PHP的[http://www.php.net/features.safe-mode 安全模式]已启用。它可能会导致一些问题,尤其在对文件上传和数学公式<code>math</code>的支持方面。",
+ 'config-xml-bad' => '缺少PHP的XML模块。MediaWiki需要使用该模块提供的函数,在当前配置下将无法工作。如果您正在使用Mandrake Linux,请安装php-xml包。',
+ 'config-pcre' => '可能缺少PCRE的支持模块。MediaWiki的运行需要兼容于Perl的正则表达式函数。',
+ 'config-pcre-no-utf8' => "'''致命错误''':PHP的PCRE模块在编译时可能没有包含PCRE_UTF8支持。MediaWiki需要UTF-8支持才能正常工作。",
+ 'config-memory-raised' => 'PHP的内存使用上限<code>memory_limit</code>为$1,自动提升到$2。',
+ 'config-memory-bad' => "'''警告:'''PHP的内存使用上限<code>memory_limit</code>为$1。该设定可能过低,并导致安装失败!",
+ 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache]已安装',
+ 'config-apc' => '[http://www.php.net/apc APC]已安装',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator]已安装',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache]已安装',
+ 'config-no-cache' => "'''警告:'''找不到[http://eaccelerator.sourceforge.net eAccelerator]、[http://www.php.net/apc APC]、[http://trac.lighttpd.net/xcache/ XCache]或[http://www.iis.net/download/WinCacheForPhp WinCache],无法启用对象缓存。
+Object caching is not enabled.",
+ 'config-diff3-bad' => '找不到GNU diff3。',
+ 'config-imagemagick' => '已找到ImageMagick:<code>$1</code>。如果你启用了上传功能,缩略图功能也将被启用。',
+ 'config-gd' => '已找到内建的GD图形库。如果你启用了上传功能,缩略图功能也将被启用。',
+ 'config-no-scaling' => '找不到GD库或ImageMagick。缩略图功能将不可用。',
+ 'config-no-uri' => "'''错误:'''无法确定当前的URI。安装已中断。",
+ 'config-uploads-not-safe' => "'''警告:'''您的默认上传目录<code>$1</code>存在允许执行任意脚本的漏洞。尽管MediaWiki会对所有已上传的文件进行安全检查,但我们仍然强烈建议您在启用上传功能前[http://www.mediawiki.org/wiki/Manual:Security#Upload_security 关闭该安全漏洞]。",
+ 'config-brokenlibxml' => '您的系统安装的PHP和libxml2版本组合存在故障,并可能在MediaWiki和其他web应用程序中造成隐藏的数据损坏。请将PHP升级到5.2.9或以上,libxml2升级到2.7.3或以上([http://bugs.php.net/bug.php?id=45996 PHP的故障报告])。安装已中断。',
+ 'config-using531' => '由于函数<code>__call()</code>的引用参数存在故障,PHP $1和MediaWiki无法兼容。请升级到PHP 5.3.2或以上版本,或降级到PHP 5.3.0以修复该问题([http://bugs.php.net/bug.php?id=50394 PHP的故障报告])。安装已中断。',
+ 'config-db-type' => '数据库类型:',
+ 'config-db-host' => '数据库主机:',
+ 'config-db-host-help' => '如果您的数据库位于另一台服务器上,在此输入主机名或IP地址。
+
+如果您使用的是共享web主机,您的主机提供商应会在他们的文档中给出正确的主机名称。
+
+如果您使用了Windows服务器和MySQL数据库,使用“localhost”可能无法识别到本地服务器。如果是这样的话,请尝试指定本地服务器的IP地址为“127.0.0.1”。',
+ 'config-db-host-oracle' => '数据库透明网络底层(TNS):',
+ 'config-db-host-oracle-help' => '请输入合法的[http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 本地连接名],并确保tnsnames.ora文件对本安装程序可见。<br />如果您使用的客户端库为10g或更新的版本,您还可以使用[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 简单连接名方法](easy connect naming method)。',
+ 'config-db-wiki-settings' => '标识本wiki',
+ 'config-db-name' => '数据库名称:',
+ 'config-db-name-help' => '请输入一个可以标识您的wiki的名称。请勿使用空格。
+
+如果您正在使用共享web主机,您的主机提供商或会给您指定一个数据库名称,或会让您通过控制面板创建数据库。',
+ 'config-db-name-oracle' => '数据库模式:',
+ 'config-db-install-account' => '用于安装的用户帐号',
+ 'config-db-username' => '数据库用户名:',
+ 'config-db-password' => '数据库密码:',
+ 'config-db-install-username' => '请输入在安装过程中用于连接数据库的用户名。请勿输入MediaWiki帐号的用户名,请输入您数据库的用户名。',
+ 'config-db-install-password' => '请输入在安装过程中用于连接数据库的密码。请勿输入MediaWiki帐号的密码,请输入您数据库的密码。',
+ 'config-db-install-help' => '请输入在安装过程中用于连接数据库的用户名和密码。',
+ 'config-db-account-lock' => '在普通操作中使用相同的用户名和密码',
+ 'config-db-wiki-account' => '用于普通操作的用户帐号',
+ 'config-db-wiki-help' => '输入在普通的wiki操作中(安装完成后)将用于连接数据库的用户名和密码。如果该帐号并不存在,而安装帐号具有足够的权限,该用户帐号会被自动创建,并被赋予足以运行此wiki的最低权限。',
+ 'config-db-prefix' => '数据库表前缀:',
+ 'config-db-prefix-help' => '如果您需要在多个wiki之间(或在MediaWiki与其他web应用程序之间)共享一个数据库,您可以通过添加前缀的方式来避免出现表名称的冲突。请勿使用空格。
+
+此字段通常可留空。',
+ 'config-db-charset' => '数据库字符集',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 二进制',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 UTF-8(向后兼容)',
+ 'config-charset-help' => "'''警告:'''如果您在MySQL 4.1+中使用'''向后兼容的UTF-8'''字符集,并在之后使用<code>mysqldump</code>备份了数据库,则可能损坏所有的非ASCII字符,从而不可逆地破坏您的备份!
+
+在'''二进制模式'''下,MediaWiki会将UTF-8编码的文本存于数据库的二进制字段中。相对于MySQL的UTF-8模式,这种方法效率更高,并允许您使用全范围的Unicode字符。
+
+在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[http://zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
+ 'config-mysql-old' => '需要MySQL $1或更新的版本,您的版本为$2。',
+ 'config-db-port' => '数据库端口:',
+ 'config-db-schema' => 'MediaWiki的数据库模式',
+ 'config-db-schema-help' => '上述数据库模式的设置通常是正确的。请在有此需求时才更改它们。',
+ 'config-sqlite-dir' => 'SQLite数据目录:',
+ 'config-sqlite-dir-help' => "SQLite会将所有的数据存储于单一文件中。
+
+您所提供的目录必须在安装过程中对网页服务器可写。
+
+该目录'''不应'''允许通过web访问,因此我们不会将数据文件和PHP文件放在一起。
+
+安装程序在创建数据文件时,亦会在相同目录下创建<code>.htaccess</code>以控制权限。假若此等控制失效,则可能会将您的数据文件暴露于公共空间,让他人可以获取用户数据(电子邮件地址、杂凑后的密码)、被删除的版本以及其他在wiki上被限制访问的数据。
+
+请考虑将数据库统一放置在某处,如<code>/var/lib/mediawiki/yourwiki</code>下。",
+ 'config-oracle-def-ts' => '默认表空间:',
+ 'config-oracle-temp-ts' => '临时表空间:',
+ 'config-support-info' => 'MediaWiki支持以下数据库系统:
+
+$1
+
+如果您在下面列出的数据库系统中没有找到您希望使用的系统,请根据上方链向的指引启用支持。',
+ 'config-support-mysql' => '* $1是MediaWiki的首选数据库,对它的支持最为完备([http://www.php.net/manual/en/mysql.installation.php 如何将对MySQL的支持编译进PHP中])',
+ 'config-support-postgres' => '* $1是一种流行的开源数据库系统,可作为MySQL的替代([http://www.php.net/manual/en/pgsql.installation.php 如何将对PostgreSQL的支持编译进PHP中])',
+ 'config-support-sqlite' => '* $1是一种轻量级的数据库系统,能被良好地支持。([http://www.php.net/manual/en/pdo.installation.php 如何将对SQLite的支持编译进PHP中],须使用PDO)',
+ 'config-support-oracle' => '* $1是一种商用企业级的数据库。([http://www.php.net/manual/en/oci8.installation.php 如何将对OCI8的支持编译进PHP中])',
+ 'config-header-mysql' => 'MySQL设置',
+ 'config-header-postgres' => 'PostgreSQL设置',
+ 'config-header-sqlite' => 'SQLite设置',
+ 'config-header-oracle' => 'Oracle设置',
+ 'config-invalid-db-type' => '无效的数据库类型',
+ 'config-missing-db-name' => '您必须为“数据库名称”输入内容',
+ 'config-missing-db-host' => '您必须为“数据库主机”输入内容',
+ 'config-missing-db-server-oracle' => '您必须为“数据库透明网络底层(TNS)”输入内容',
+ 'config-invalid-db-server-oracle' => '无效的数据库TNS“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)、下划线(_)和点号(.)。',
+ 'config-invalid-db-name' => '无效的数据库名称“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)、下划线(_)和连字号(-)。',
+ 'config-invalid-db-prefix' => '无效的数据库前缀“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)、下划线(_)和连字号(-)。',
+ 'config-connection-error' => '$1。
+
+请检查下列的主机、用户名和密码设置后重试。',
+ 'config-invalid-schema' => '无效的MediaWiki数据库模式“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)和下划线(_)。',
+ 'config-postgres-old' => '需要PostgreSQL $1或更新的版本,您的版本为$2。',
+ 'config-sqlite-name-help' => '请为您的wiki指定一个用于标识的名称。请勿使用空格或连字号,该名称将被用作SQLite的数据文件名。',
+ 'config-sqlite-parent-unwritable-group' => '由于父目录<code><nowiki>$2</nowiki></code>对网页服务器不可写,无法创建数据目录<code><nowiki>$1</nowiki></code>。
+
+安装程序已确定您网页服务器所使用的用户。请将<code><nowiki>$3</nowiki></code>目录设为对该用户可写以继续安装过程。在Unix/Linux系统中,您可以逐行输入下列命令:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => '由于父目录<code><nowiki>$2</nowiki></code>对网页服务器不可写,无法创建数据目录<code><nowiki>$1</nowiki></code>。
+
+安装程序无法确定您网页服务器所使用的用户。请将<code><nowiki>$3</nowiki></code>目录设为全局可写(对所有用户)以继续安装过程。在Unix/Linux系统中,您可以逐行输入下列命令:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => '创建数据目录“$1”时发生错误。请检查路径后重试。',
+ 'config-sqlite-dir-unwritable' => '无法写入目录“$1”。请修改该目录的权限,使其对网页服务器可写后重试。',
+ 'config-sqlite-connection-error' => '$1。
+
+请检查下列的数据目录和数据库名称后重试。',
+ 'config-sqlite-readonly' => '文件<code>$1</code>不可写。',
+ 'config-sqlite-cant-create-db' => '无法创建数据文件<code>$1</code>。',
+ 'config-sqlite-fts3-downgrade' => 'PHP缺少FTS3支持,正在降级数据表',
+ 'config-can-upgrade' => "在数据库中发现了MediaWiki的数据表。要将它们升级至MediaWiki $1,请点击'''继续'''。",
+ 'config-upgrade-done' => "升级完成。
+
+现在您可以[$1 开始使用您的wiki]了。
+
+如果您需要重新生成<code>LocalSettings.php</code>文件,请点击下面的按钮。除非您的wiki出现了问题,我们'''不推荐'''您执行此操作。",
+ 'config-upgrade-done-no-regenerate' => '升级完成。
+
+现在您可以[$1 开始使用您的wiki]了。',
+ 'config-regenerate' => '重新生成LocalSettings.php →',
+ 'config-show-table-status' => '查询SHOW TABLE STATUS失败!',
+ 'config-unknown-collation' => "'''警告:'''数据库使用了无法识别的整理。",
+ 'config-db-web-account' => '供网页访问使用的数据库帐号',
+ 'config-db-web-help' => '请指定在wiki执行普通操作时,网页服务器用于连接数据库服务器的用户名和密码。',
+ 'config-db-web-account-same' => '使用和安装程序相同的帐号',
+ 'config-db-web-create' => '如果帐号不存在,则自动创建',
+ 'config-db-web-no-create-privs' => '您指定给安装程序的帐号缺少创建帐号的权限,因此您指定的帐号必须已经存在。',
+ 'config-mysql-engine' => '存储引擎:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB'''通常是最佳选项,因为它对并发操作有着良好的支持。
+
+'''MyISAM'''在单用户或只读环境下可能会有更快的性能表现。但MyISAM数据库出错的概率一般要大于InnoDB数据库。",
+ 'config-mysql-charset' => '数据库字符集:',
+ 'config-mysql-binary' => '二进制',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "在'''二进制模式'''下,MediaWiki会将UTF-8编码的文本存于数据库的二进制字段中。相对于MySQL的UTF-8模式,这种方法效率更高,并允许您使用全范围的Unicode字符。
+
+在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[http://zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
+ 'config-site-name' => 'Wiki的名称:',
+ 'config-site-name-help' => '填入的内容会出现在浏览器的标题栏以及其他多处位置中。',
+ 'config-site-name-blank' => '输入网站的名称。',
+ 'config-project-namespace' => '项目名字空间:',
+ 'config-ns-generic' => '项目',
+ 'config-ns-site-name' => '与wiki名称相同:$1',
+ 'config-ns-other' => '其他(自定义)',
+ 'config-ns-other-default' => '我的Wiki',
+ 'config-project-namespace-help' => "依循维基百科形成的惯例,许多wiki将他们的方针页面存放在与内容页面不同的“'''项目名字空间'''”中。所有位于该名字空间下的页面标题都会被冠以固定的前缀,您可以在此处指定这一前缀。传统上,这一前缀应与wiki的命名保持一致,但请勿在其中使用标点符号,如“#”或“:”。",
+ 'config-ns-invalid' => '指定的名字空间“<nowiki>$1</nowiki>”无效,请为项目名字空间指定其他名称。',
+ 'config-admin-box' => '管理员帐号',
+ 'config-admin-name' => '您的名字:',
+ 'config-admin-password' => '密码:',
+ 'config-admin-password-confirm' => '确认密码:',
+ 'config-admin-help' => '在此输入您想使用的用户名,例如“乔帮主”。您将使用该名称登录本wiki。',
+ 'config-admin-name-blank' => '输入管理员的用户名。',
+ 'config-admin-name-invalid' => '指定的用户名“<nowiki>$1</nowiki>”无效,请指定其他用户名。',
+ 'config-admin-password-blank' => '输入管理员帐号的密码。',
+ 'config-admin-password-same' => '密码不能和用户名相同。',
+ 'config-admin-password-mismatch' => '两次输入的密码并不相同。',
+ 'config-admin-email' => '电子邮件地址:',
+ 'config-admin-email-help' => '在此输入电子邮件地址,这样您将可以收到本wiki上的其他用户发来的电子邮件,可以重置您的密码,并能在监视列表中的页面被更改时收到邮件通知。',
+ 'config-admin-error-user' => '在创建用户名为“<nowiki>$1</nowiki>”的管理员帐号时发生内部错误。',
+ 'config-admin-error-password' => '在为管理员“<nowiki>$1</nowiki>”设置密码时发生内部错误:<pre>$2</pre>',
+ 'config-admin-error-bademail' => '您输入了无效的电子邮件地址。',
+ 'config-subscribe' => '订阅[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 发行公告邮件列表]。',
+ 'config-subscribe-help' => '此低流量的邮件列表仅用于发行公告,其中包括重要安全公告。请订阅该列表以便在新的版本推出时升级您的MediaWiki。',
+ 'config-almost-done' => '您几乎已经完成了!现在您可以跳过剩下的配置流程并立即安装wiki。',
+ 'config-optional-continue' => '多问我一些问题吧。',
+ 'config-optional-skip' => '我已经不耐烦了,赶紧安装我的wiki。',
+ 'config-profile' => '用户权限配置:',
+ 'config-profile-wiki' => '传统wiki',
+ 'config-profile-no-anon' => '需要注册帐号',
+ 'config-profile-fishbowl' => '编辑受限',
+ 'config-profile-private' => '非公开wiki',
+ 'config-profile-help' => "如果您允许尽量多的人编写wiki,网站上的内容会更加丰富。在MediaWiki中,您可以轻松地审查最近更改,并轻易回退掉新手或破坏者造成的损害。
+
+然而,许多人觉得让MediaWiki存在多种角色将更加好用;同时,要说服所有人都愿以wiki的方式作贡献并非一件易事。因此,您可以有以下选择:
+
+'''{{int:config-profile-wiki}}'''允许包括未登录用户在内的所有人编辑。'''{{int:config-profile-no-anon}}'''的wiki需要额外的注册流程,这有可能会阻碍随意贡献者。
+
+'''{{int:config-profile-fishbowl}}'''模式只允许获批准的用户编辑,但对公众开放页面浏览(包括历史记录)。'''{{int:config-profile-private}}'''则只允许获批准的用户浏览、编辑页面。
+
+安装完成后,您还可以对用户权限进行更多、更复杂的配置,参见[http://www.mediawiki.org/wiki/Manual:User_rights 相关的使用手册]。",
+ 'config-license' => '版权和许可证:',
+ 'config-license-none' => '页脚无许可证',
+ 'config-license-cc-by-sa' => '知识共享署名-相同方式分享',
+ 'config-license-cc-by-nc-sa' => '知识共享署名-非商业性使用-相同方式共享',
+ 'config-license-gfdl-old' => 'GNU自由文档许可证 1.2',
+ 'config-license-gfdl-current' => 'GNU自由文档许可证 1.3或更高版本',
+ 'config-license-pd' => '公有领域',
+ 'config-license-cc-choose' => '选择自定义的知识共享许可证',
+ 'config-license-help' => "许多公共wiki会以[http://freedomdefined.org/Definition 自由许可证]的方式释放出编者的所有贡献。这有助于构建社区的主人翁意识,并能鼓励长期贡献。对于非公共wiki或公司wiki,这并非必要条件。
+
+如果您希望使用来自维基百科的内容,并希望维基百科能接受复制自您的wiki的内容,请选择'''知识共享署名-相同方式共享'''。
+
+GNU自由文档许可证是维基百科曾经使用过的许可证,并迄今有效。然而,该许可证的一些特性会增加重用或演绎内容的难度。",
+ 'config-email-settings' => '电子邮件设置',
+ 'config-enable-email' => '启用出站电子邮件',
+ 'config-enable-email-help' => '如果您希望使用电子邮件功能,请正确配置[http://www.php.net/manual/en/mail.configuration.php PHP的邮件设定]。如果您不需要任何电子邮件功能,请在此处禁用它。',
+ 'config-email-user' => '启用用户到用户的电子邮件',
+ 'config-email-user-help' => '允许所有用户互发邮件,假若他们启用了该功能。',
+ 'config-email-usertalk' => '启用用户讨论页通知',
+ 'config-email-usertalk-help' => '允许用户收到用户讨论页被修改的通知,假若他们启用了该功能。',
+ 'config-email-watchlist' => '启用监视列表通知',
+ 'config-email-watchlist-help' => '允许用户收到与其监视列表有关的通知,假若他们启用了该功能。',
+ 'config-email-auth' => '启用电子邮件身份验证',
+ 'config-email-auth-help' => "如果启用此选项,在用户设置或修改电子邮件地址时,就会收到一封邮件,内含确认电子地址的链接。只有经过身份验证的电子邮件地址,才能收到来自其他用户的电子邮件,或任何修改通知的邮件。'''建议'''公开wiki启用本选项,以防对电子邮件功能的滥用。",
+ 'config-email-sender' => '回复电子邮件地址:',
+ 'config-email-sender-help' => '输入要用来发送出站电子邮件的地址,该地址将会收到被拒收的邮件。许多邮件服务器要求域名部分必须有效。',
+ 'config-upload-settings' => '图像和文件上传',
+ 'config-upload-enable' => '启用文件上传',
+ 'config-upload-help' => '文件上传可能会将您的服务器暴露在安全风险下。有关更多的信息,请参阅手册的[http://www.mediawiki.org/wiki/Manual:Security 安全部分]。
+
+要启用文件上传,请先将MediaWiki根目录下的<code>images</code>子目录更改为对web服务器可写,然后再启用此选项。',
+ 'config-upload-deleted' => '已删除文件的目录:',
+ 'config-upload-deleted-help' => '指定用于存放被删除文件的目录。理想情况下,该目录不应能通过web访问。',
+ 'config-logo' => '标志URL:',
+ 'config-logo-help' => '在MediaWiki的默认外观中,左上角部位有一块135x160像素的区域可用于展示站点的标志。请上传一幅相应大小的图像,并在此输入URL。
+
+如果您不希望使用标志,请将本处留空。',
+ 'config-instantcommons' => '启用即时共享资源',
+ 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons 即时共享资源]可以让wiki使用来自[http://commons.wikimedia.org/ 维基共享资源]网站的图像、音频和其他媒体文件。要启用该功能,MediaWiki必须能够访问互联网。
+
+有关此功能的详细信息,包括如何将其他wiki网站设为具有类似共享功能的方法,请参考[http://mediawiki.org/wiki/Manual:$wgForeignFileRepos 手册]。',
+ 'config-cc-error' => '知识共享许可证挑选器无法找到结果,请手动输入许可证的名称。',
+ 'config-cc-again' => '重新挑选……',
+ 'config-cc-not-chosen' => '选择您希望使用的知识共享许可证,并点击“继续”。',
+ 'config-advanced-settings' => '高级设置',
+ 'config-cache-options' => '对象缓存设置:',
+ 'config-cache-help' => '对象缓存可通过缓存频繁使用的数据来提高MediaWiki的速度。高度推荐中到大型的网站启用该功能,小型网站亦能从其中受益。',
+ 'config-cache-none' => '无缓存(不影响功能,但对较大型的wiki网站会有速度影响)',
+ 'config-cache-accel' => 'PHP对象缓存(APC、eAccelerator、XCache或WinCache)',
+ 'config-cache-memcached' => '使用Memcached(需要另外安装并配置)',
+ 'config-memcached-servers' => 'Memcached服务器:',
+ 'config-memcached-help' => '用于Memcached的IP地址列表。请以半角逗号分割,并指定要使用的端口(例如:127.0.0.1:11211, 192.168.1.25:11211)。',
+ 'config-extensions' => '扩展',
+ 'config-extensions-help' => '已在您的<code>./extensions</code>目录中发现下列扩展。
+
+您可能要对它们进行额外的配置,但您现在可以启用它们。',
+ 'config-install-alreadydone' => "'''警告:'''您似乎已经安装了MediaWiki,并试图重新安装它。请前往下一个页面。",
+ 'config-install-begin' => '点击继续后,您将开始安装MediaWiki。如果您还想对配置作一些修改,请点击后退。',
+ 'config-install-step-done' => '完成',
+ 'config-install-step-failed' => '失败',
+ 'config-install-extensions' => '正在启用扩展',
+ 'config-install-database' => '正在配置数据库',
+ 'config-install-pg-schema-not-exist' => 'PostgreSQL 架构不存在',
+ 'config-install-pg-schema-failed' => '创建数据表失败。请确保用户“$1”拥有写入模式“$2”的权限。',
+ 'config-install-pg-commit' => '正在提交更改',
+ 'config-install-pg-plpgsql' => '正在检查PL/pgSQL语言',
+ 'config-pg-no-plpgsql' => '您需要为数据库$1安装PL/pgSQL语言',
+ 'config-pg-no-create-privs' => '为安装程序指定的帐号缺少创建帐号的权限。',
+ 'config-install-user' => '正在创建数据库用户',
+ 'config-install-user-grant-failed' => '授予用户“$1”权限失败:$2',
+ 'config-install-tables' => '正在创建数据表',
+ 'config-install-tables-exist' => "'''警告''':MediaWiki的数据表似乎已经存在,跳过创建。",
+ 'config-install-tables-failed' => "'''错误''':创建数据表出错,下为错误信息:$1",
+ 'config-install-interwiki' => '正在填充默认的跨wiki数据表',
+ 'config-install-interwiki-list' => '找不到文件<code>interwiki.list</code>。',
+ 'config-install-interwiki-exists' => "'''警告''':跨wiki数据表似乎已有内容,跳过默认列表。",
+ 'config-install-stats' => '初始化统计',
+ 'config-install-keys' => '正在生成密钥',
+ 'config-install-sysop' => '正在创建管理员用户帐号',
+ 'config-install-subscribe-fail' => '无法订阅mediawiki-announce',
+ 'config-install-mainpage' => '正在创建显示默认内容的首页',
+ 'config-install-extension-tables' => '正在为已启用扩展创建数据表',
+ 'config-install-mainpage-failed' => '无法插入首页:$1',
+ 'config-install-done' => "'''恭喜!'''
+您已经成功地安装了MediaWiki。
+
+安装程序已经生成了<code>LocalSettings.php</code>文件,其中包含了您所有的配置。
+
+您需要下载该文件,并将其放在您wiki的根目录(index.php的同级目录)中。稍后下载将自动开始。
+
+如果浏览器没有提示您下载,或者您取消了下载,您可以点击下面的链接重新开始下载:
+
+$3
+
+'''注意''':如果您现在不完成本步骤,而是没有下载便退出了安装过程,此后您将无法获得自动生成的配置文件。
+
+当本步骤完成后,您可以 '''[$2 进入您的wiki]'''。",
+ 'config-download-localsettings' => '下载LocalSettings.php',
+ 'config-help' => '帮助',
+);
+
+/** Traditional Chinese (‪中文(繁體)‬)
+ * @author Mark85296341
+ */
+$messages['zh-hant'] = array(
+ 'config-information' => '資訊',
+ 'config-your-language' => '您的語言:',
+ 'config-your-language-help' => '選擇一個要使用的語言在安裝過程中。',
+ 'config-wiki-language' => 'Wiki 語言:',
+ 'config-back' => '←返回',
+ 'config-continue' => '繼續→',
+ 'config-page-language' => '語言',
+ 'config-page-welcome' => '歡迎您來到 MediaWiki!',
+ 'config-page-dbconnect' => '連接到資料庫',
+ 'config-page-upgrade' => '升級現有的安裝',
+ 'config-page-dbsettings' => '資料庫設定',
+ 'config-page-name' => '名稱',
+ 'config-page-options' => '選項',
+ 'config-page-install' => '安裝',
+ 'config-page-complete' => '完成!',
+ 'config-page-restart' => '重新安裝',
+ 'config-page-readme' => '讀我',
+ 'config-page-copying' => '複製',
+ 'config-page-upgradedoc' => '升級',
+ 'config-restart' => '是的,重新啟動',
+ 'config-db-type' => '資料庫類型:',
+ 'config-db-host' => '資料庫主機:',
+ 'config-db-host-oracle' => '資料庫的 TNS:',
+ 'config-db-wiki-settings' => '識別這個 Wiki',
+ 'config-db-name' => '資料庫名稱:',
+ 'config-db-name-oracle' => '資料庫架構:',
+ 'config-db-username' => '資料庫使用者名稱:',
+ 'config-db-password' => '資料庫密碼:',
+ 'config-sqlite-dir' => 'SQLite 的資料目錄:',
+ 'config-header-mysql' => 'MySQL 的設定',
+ 'config-header-sqlite' => 'SQLite 的設定',
+ 'config-header-oracle' => '甲骨文設定',
+ 'config-invalid-db-type' => '無效的資料庫類型',
+ 'config-db-web-create' => '建立帳號,如果它不存在',
+ 'config-mysql-charset' => '資料庫字符集:',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name-blank' => '輸入站點名稱。',
+ 'config-ns-other' => '其他(請註明)',
+ 'config-admin-password' => '密碼:',
+ 'config-admin-password-confirm' => '再次輸入密碼:',
+ 'config-admin-name-blank' => '輸入管理員的使用者名稱。',
+ 'config-admin-password-blank' => '輸入管理員帳號密碼。',
+ 'config-admin-password-same' => '密碼不能與使用者名稱相同。',
+ 'config-admin-email' => 'E-mail 地址:',
+ 'config-admin-error-bademail' => '你輸入了一個無效的電子郵件地址。',
+ 'config-license' => '版權和許可證:',
+ 'config-license-pd' => '公共領域',
+ 'config-email-settings' => 'E-mail 設定',
+ 'config-email-auth' => '啟用電子郵件認證',
+ 'config-email-sender' => '返回電子郵件地址:',
+ 'config-upload-settings' => '圖片和檔案上傳',
+ 'config-upload-enable' => '啟用檔案上傳',
+ 'config-cc-again' => '重新選取......',
+ 'config-advanced-settings' => '進階配置',
+ 'config-extensions' => '擴充套件',
+ 'config-install-step-done' => '完成',
+ 'config-install-step-failed' => '失敗',
+ 'config-install-pg-commit' => '提交更改',
+ 'config-help' => '說明',
+);
+
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
new file mode 100644
index 00000000..6da4f100
--- /dev/null
+++ b/includes/installer/Installer.php
@@ -0,0 +1,1511 @@
+<?php
+/**
+ * Base code for MediaWiki installer.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * This documentation group collects source code files with deployment functionality.
+ *
+ * @defgroup Deployment Deployment
+ */
+
+/**
+ * Base installer class.
+ *
+ * This class provides the base for installation and update functionality
+ * for both MediaWiki core and extensions.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+abstract class Installer {
+
+ // This is the absolute minimum PHP version we can support
+ const MINIMUM_PHP_VERSION = '5.2.3';
+
+ /**
+ * @var array
+ */
+ protected $settings;
+
+ /**
+ * Cached DB installer instances, access using getDBInstaller().
+ *
+ * @var array
+ */
+ protected $dbInstallers = array();
+
+ /**
+ * Minimum memory size in MB.
+ *
+ * @var integer
+ */
+ protected $minMemorySize = 50;
+
+ /**
+ * Cached Title, used by parse().
+ *
+ * @var Title
+ */
+ protected $parserTitle;
+
+ /**
+ * Cached ParserOptions, used by parse().
+ *
+ * @var ParserOptions
+ */
+ protected $parserOptions;
+
+ /**
+ * Known database types. These correspond to the class names <type>Installer,
+ * and are also MediaWiki database types valid for $wgDBtype.
+ *
+ * To add a new type, create a <type>Installer class and a Database<type>
+ * class, and add a config-type-<type> message to MessagesEn.php.
+ *
+ * @var array
+ */
+ protected static $dbTypes = array(
+ 'mysql',
+ 'postgres',
+ 'oracle',
+ 'sqlite',
+ );
+
+ /**
+ * A list of environment check methods called by doEnvironmentChecks().
+ * These may output warnings using showMessage(), and/or abort the
+ * installation process by returning false.
+ *
+ * @var array
+ */
+ protected $envChecks = array(
+ 'envCheckDB',
+ 'envCheckRegisterGlobals',
+ 'envCheckBrokenXML',
+ 'envCheckPHP531',
+ 'envCheckMagicQuotes',
+ 'envCheckMagicSybase',
+ 'envCheckMbstring',
+ 'envCheckZE1',
+ 'envCheckSafeMode',
+ 'envCheckXML',
+ 'envCheckPCRE',
+ 'envCheckMemory',
+ 'envCheckCache',
+ 'envCheckDiff3',
+ 'envCheckGraphics',
+ 'envCheckPath',
+ 'envCheckExtension',
+ 'envCheckShellLocale',
+ 'envCheckUploadsDirectory',
+ 'envCheckLibicu',
+ 'envCheckSuhosinMaxValueLength',
+ );
+
+ /**
+ * MediaWiki configuration globals that will eventually be passed through
+ * to LocalSettings.php. The names only are given here, the defaults
+ * typically come from DefaultSettings.php.
+ *
+ * @var array
+ */
+ protected $defaultVarNames = array(
+ 'wgSitename',
+ 'wgPasswordSender',
+ 'wgLanguageCode',
+ 'wgRightsIcon',
+ 'wgRightsText',
+ 'wgRightsUrl',
+ 'wgMainCacheType',
+ 'wgEnableEmail',
+ 'wgEnableUserEmail',
+ 'wgEnotifUserTalk',
+ 'wgEnotifWatchlist',
+ 'wgEmailAuthentication',
+ 'wgDBtype',
+ 'wgDiff3',
+ 'wgImageMagickConvertCommand',
+ 'IP',
+ 'wgScriptPath',
+ 'wgScriptExtension',
+ 'wgMetaNamespace',
+ 'wgDeletedDirectory',
+ 'wgEnableUploads',
+ 'wgLogo',
+ 'wgShellLocale',
+ 'wgSecretKey',
+ 'wgUseInstantCommons',
+ 'wgUpgradeKey',
+ 'wgDefaultSkin',
+ 'wgResourceLoaderMaxQueryLength',
+ );
+
+ /**
+ * Variables that are stored alongside globals, and are used for any
+ * configuration of the installation process aside from the MediaWiki
+ * configuration. Map of names to defaults.
+ *
+ * @var array
+ */
+ protected $internalDefaults = array(
+ '_UserLang' => 'en',
+ '_Environment' => false,
+ '_CompiledDBs' => array(),
+ '_SafeMode' => false,
+ '_RaiseMemory' => false,
+ '_UpgradeDone' => false,
+ '_InstallDone' => false,
+ '_Caches' => array(),
+ '_InstallPassword' => '',
+ '_SameAccount' => true,
+ '_CreateDBAccount' => false,
+ '_NamespaceType' => 'site-name',
+ '_AdminName' => '', // will be set later, when the user selects language
+ '_AdminPassword' => '',
+ '_AdminPassword2' => '',
+ '_AdminEmail' => '',
+ '_Subscribe' => false,
+ '_SkipOptional' => 'continue',
+ '_RightsProfile' => 'wiki',
+ '_LicenseCode' => 'none',
+ '_CCDone' => false,
+ '_Extensions' => array(),
+ '_MemCachedServers' => '',
+ '_UpgradeKeySupplied' => false,
+ '_ExistingDBSettings' => false,
+ );
+
+ /**
+ * The actual list of installation steps. This will be initialized by getInstallSteps()
+ *
+ * @var array
+ */
+ private $installSteps = array();
+
+ /**
+ * Extra steps for installation, for things like DatabaseInstallers to modify
+ *
+ * @var array
+ */
+ protected $extraInstallSteps = array();
+
+ /**
+ * Known object cache types and the functions used to test for their existence.
+ *
+ * @var array
+ */
+ protected $objectCaches = array(
+ 'xcache' => 'xcache_get',
+ 'apc' => 'apc_fetch',
+ 'eaccel' => 'eaccelerator_get',
+ 'wincache' => 'wincache_ucache_get'
+ );
+
+ /**
+ * User rights profiles.
+ *
+ * @var array
+ */
+ public $rightsProfiles = array(
+ 'wiki' => array(),
+ 'no-anon' => array(
+ '*' => array( 'edit' => false )
+ ),
+ 'fishbowl' => array(
+ '*' => array(
+ 'createaccount' => false,
+ 'edit' => false,
+ ),
+ ),
+ 'private' => array(
+ '*' => array(
+ 'createaccount' => false,
+ 'edit' => false,
+ 'read' => false,
+ ),
+ ),
+ );
+
+ /**
+ * License types.
+ *
+ * @var array
+ */
+ public $licenses = array(
+ 'cc-by-sa' => array(
+ 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
+ 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
+ ),
+ 'cc-by-nc-sa' => array(
+ 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
+ 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
+ ),
+ 'cc-0' => array(
+ 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
+ 'icon' => '{$wgStylePath}/common/images/cc-0.png',
+ ),
+ 'pd' => array(
+ 'url' => 'http://creativecommons.org/licenses/publicdomain/',
+ 'icon' => '{$wgStylePath}/common/images/public-domain.png',
+ ),
+ 'gfdl-old' => array(
+ 'url' => 'http://www.gnu.org/licenses/old-licenses/fdl-1.2.html',
+ 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
+ ),
+ 'gfdl-current' => array(
+ 'url' => 'http://www.gnu.org/copyleft/fdl.html',
+ 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
+ ),
+ 'none' => array(
+ 'url' => '',
+ 'icon' => '',
+ 'text' => ''
+ ),
+ 'cc-choose' => array(
+ // Details will be filled in by the selector.
+ 'url' => '',
+ 'icon' => '',
+ 'text' => '',
+ ),
+ );
+
+ /**
+ * URL to mediawiki-announce subscription
+ */
+ protected $mediaWikiAnnounceUrl = 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
+
+ /**
+ * Supported language codes for Mailman
+ */
+ protected $mediaWikiAnnounceLanguages = array(
+ 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu',
+ 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru',
+ 'sl', 'sr', 'sv', 'tr', 'uk'
+ );
+
+ /**
+ * UI interface for displaying a short message
+ * The parameters are like parameters to wfMsg().
+ * The messages will be in wikitext format, which will be converted to an
+ * output format such as HTML or text before being sent to the user.
+ */
+ public abstract function showMessage( $msg /*, ... */ );
+
+ /**
+ * Same as showMessage(), but for displaying errors
+ */
+ public abstract function showError( $msg /*, ... */ );
+
+ /**
+ * Show a message to the installing user by using a Status object
+ * @param $status Status
+ */
+ public abstract function showStatusMessage( Status $status );
+
+ /**
+ * Constructor, always call this from child classes.
+ */
+ public function __construct() {
+ global $wgExtensionMessagesFiles, $wgUser, $wgHooks;
+
+ // Disable the i18n cache and LoadBalancer
+ Language::getLocalisationCache()->disableBackend();
+ LBFactory::disableBackend();
+
+ // Load the installer's i18n file.
+ $wgExtensionMessagesFiles['MediawikiInstaller'] =
+ dirname( __FILE__ ) . '/Installer.i18n.php';
+
+ // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
+ $wgUser = User::newFromId( 0 );
+
+ $this->settings = $this->internalDefaults;
+
+ foreach ( $this->defaultVarNames as $var ) {
+ $this->settings[$var] = $GLOBALS[$var];
+ }
+
+ foreach ( self::getDBTypes() as $type ) {
+ $installer = $this->getDBInstaller( $type );
+
+ if ( !$installer->isCompiled() ) {
+ continue;
+ }
+
+ $defaults = $installer->getGlobalDefaults();
+
+ foreach ( $installer->getGlobalNames() as $var ) {
+ if ( isset( $defaults[$var] ) ) {
+ $this->settings[$var] = $defaults[$var];
+ } else {
+ $this->settings[$var] = $GLOBALS[$var];
+ }
+ }
+ }
+
+ $this->parserTitle = Title::newFromText( 'Installer' );
+ $this->parserOptions = new ParserOptions; // language will be wrong :(
+ $this->parserOptions->setEditSection( false );
+ }
+
+ /**
+ * Get a list of known DB types.
+ */
+ public static function getDBTypes() {
+ return self::$dbTypes;
+ }
+
+ /**
+ * Do initial checks of the PHP environment. Set variables according to
+ * the observed environment.
+ *
+ * It's possible that this may be called under the CLI SAPI, not the SAPI
+ * that the wiki will primarily run under. In that case, the subclass should
+ * initialise variables such as wgScriptPath, before calling this function.
+ *
+ * Under the web subclass, it can already be assumed that PHP 5+ is in use
+ * and that sessions are working.
+ *
+ * @return Status
+ */
+ public function doEnvironmentChecks() {
+ $phpVersion = phpversion();
+ if( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
+ $this->showMessage( 'config-env-php', $phpVersion );
+ $good = true;
+ } else {
+ $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION );
+ $good = false;
+ }
+
+ if( $good ) {
+ foreach ( $this->envChecks as $check ) {
+ $status = $this->$check();
+ if ( $status === false ) {
+ $good = false;
+ }
+ }
+ }
+
+ $this->setVar( '_Environment', $good );
+
+ return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
+ }
+
+ /**
+ * Set a MW configuration variable, or internal installer configuration variable.
+ *
+ * @param $name String
+ * @param $value Mixed
+ */
+ public function setVar( $name, $value ) {
+ $this->settings[$name] = $value;
+ }
+
+ /**
+ * Get an MW configuration variable, or internal installer configuration variable.
+ * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
+ * Installer variables are typically prefixed by an underscore.
+ *
+ * @param $name String
+ * @param $default Mixed
+ *
+ * @return mixed
+ */
+ public function getVar( $name, $default = null ) {
+ if ( !isset( $this->settings[$name] ) ) {
+ return $default;
+ } else {
+ return $this->settings[$name];
+ }
+ }
+
+ /**
+ * Get an instance of DatabaseInstaller for the specified DB type.
+ *
+ * @param $type Mixed: DB installer for which is needed, false to use default.
+ *
+ * @return DatabaseInstaller
+ */
+ public function getDBInstaller( $type = false ) {
+ if ( !$type ) {
+ $type = $this->getVar( 'wgDBtype' );
+ }
+
+ $type = strtolower( $type );
+
+ if ( !isset( $this->dbInstallers[$type] ) ) {
+ $class = ucfirst( $type ). 'Installer';
+ $this->dbInstallers[$type] = new $class( $this );
+ }
+
+ return $this->dbInstallers[$type];
+ }
+
+ /**
+ * Determine if LocalSettings.php exists. If it does, return its variables,
+ * merged with those from AdminSettings.php, as an array.
+ *
+ * @return Array
+ */
+ public static function getExistingLocalSettings() {
+ global $IP;
+
+ wfSuppressWarnings();
+ $_lsExists = file_exists( "$IP/LocalSettings.php" );
+ wfRestoreWarnings();
+
+ if( !$_lsExists ) {
+ return false;
+ }
+ unset($_lsExists);
+
+ require( "$IP/includes/DefaultSettings.php" );
+ require( "$IP/LocalSettings.php" );
+ if ( file_exists( "$IP/AdminSettings.php" ) ) {
+ require( "$IP/AdminSettings.php" );
+ }
+ return get_defined_vars();
+ }
+
+ /**
+ * Get a fake password for sending back to the user in HTML.
+ * This is a security mechanism to avoid compromise of the password in the
+ * event of session ID compromise.
+ *
+ * @param $realPassword String
+ *
+ * @return string
+ */
+ public function getFakePassword( $realPassword ) {
+ return str_repeat( '*', strlen( $realPassword ) );
+ }
+
+ /**
+ * Set a variable which stores a password, except if the new value is a
+ * fake password in which case leave it as it is.
+ *
+ * @param $name String
+ * @param $value Mixed
+ */
+ public function setPassword( $name, $value ) {
+ if ( !preg_match( '/^\*+$/', $value ) ) {
+ $this->setVar( $name, $value );
+ }
+ }
+
+ /**
+ * On POSIX systems return the primary group of the webserver we're running under.
+ * On other systems just returns null.
+ *
+ * This is used to advice the user that he should chgrp his mw-config/data/images directory as the
+ * webserver user before he can install.
+ *
+ * Public because SqliteInstaller needs it, and doesn't subclass Installer.
+ *
+ * @return mixed
+ */
+ public static function maybeGetWebserverPrimaryGroup() {
+ if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
+ # I don't know this, this isn't UNIX.
+ return null;
+ }
+
+ # posix_getegid() *not* getmygid() because we want the group of the webserver,
+ # not whoever owns the current script.
+ $gid = posix_getegid();
+ $getpwuid = posix_getpwuid( $gid );
+ $group = $getpwuid['name'];
+
+ return $group;
+ }
+
+ /**
+ * Convert wikitext $text to HTML.
+ *
+ * This is potentially error prone since many parser features require a complete
+ * installed MW database. The solution is to just not use those features when you
+ * write your messages. This appears to work well enough. Basic formatting and
+ * external links work just fine.
+ *
+ * But in case a translator decides to throw in a #ifexist or internal link or
+ * whatever, this function is guarded to catch the attempted DB access and to present
+ * some fallback text.
+ *
+ * @param $text String
+ * @param $lineStart Boolean
+ * @return String
+ */
+ public function parse( $text, $lineStart = false ) {
+ global $wgParser;
+
+ try {
+ $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
+ $html = $out->getText();
+ } catch ( DBAccessError $e ) {
+ $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
+
+ if ( !empty( $this->debug ) ) {
+ $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
+ }
+ }
+
+ return $html;
+ }
+
+ public function getParserOptions() {
+ return $this->parserOptions;
+ }
+
+ public function disableLinkPopups() {
+ $this->parserOptions->setExternalLinkTarget( false );
+ }
+
+ public function restoreLinkPopups() {
+ global $wgExternalLinkTarget;
+ $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
+ }
+
+ /**
+ * Install step which adds a row to the site_stats table with appropriate
+ * initial values.
+ */
+ public function populateSiteStats( DatabaseInstaller $installer ) {
+ $status = $installer->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $status->value->insert( 'site_stats', array(
+ 'ss_row_id' => 1,
+ 'ss_total_views' => 0,
+ 'ss_total_edits' => 0,
+ 'ss_good_articles' => 0,
+ 'ss_total_pages' => 0,
+ 'ss_users' => 0,
+ 'ss_admins' => 0,
+ 'ss_images' => 0 ),
+ __METHOD__, 'IGNORE' );
+ return Status::newGood();
+ }
+
+ /**
+ * Exports all wg* variables stored by the installer into global scope.
+ */
+ public function exportVars() {
+ foreach ( $this->settings as $name => $value ) {
+ if ( substr( $name, 0, 2 ) == 'wg' ) {
+ $GLOBALS[$name] = $value;
+ }
+ }
+ }
+
+ /**
+ * Environment check for DB types.
+ */
+ protected function envCheckDB() {
+ global $wgLang;
+
+ $compiledDBs = array();
+ $allNames = array();
+
+ foreach ( self::getDBTypes() as $name ) {
+ $db = $this->getDBInstaller( $name );
+ $readableName = wfMsg( 'config-type-' . $name );
+
+ if ( $db->isCompiled() ) {
+ $compiledDBs[] = $name;
+ }
+ $allNames[] = $readableName;
+ }
+
+ $this->setVar( '_CompiledDBs', $compiledDBs );
+
+ if ( !$compiledDBs ) {
+ $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
+ // FIXME: this only works for the web installer!
+ return false;
+ }
+
+ // Check for FTS3 full-text search module
+ $sqlite = $this->getDBInstaller( 'sqlite' );
+ if ( $sqlite->isCompiled() ) {
+ if( DatabaseSqlite::getFulltextSearchModule() != 'FTS3' ) {
+ $this->showMessage( 'config-no-fts3' );
+ }
+ }
+ }
+
+ /**
+ * Environment check for register_globals.
+ */
+ protected function envCheckRegisterGlobals() {
+ if( wfIniGetBool( "magic_quotes_runtime" ) ) {
+ $this->showMessage( 'config-register-globals' );
+ }
+ }
+
+ /**
+ * Some versions of libxml+PHP break < and > encoding horribly
+ */
+ protected function envCheckBrokenXML() {
+ $test = new PhpXmlBugTester();
+ if ( !$test->ok ) {
+ $this->showError( 'config-brokenlibxml' );
+ return false;
+ }
+ }
+
+ /**
+ * Test PHP (probably 5.3.1, but it could regress again) to make sure that
+ * reference parameters to __call() are not converted to null
+ */
+ protected function envCheckPHP531() {
+ $test = new PhpRefCallBugTester;
+ $test->execute();
+ if ( !$test->ok ) {
+ $this->showError( 'config-using531', phpversion() );
+ return false;
+ }
+ }
+
+ /**
+ * Environment check for magic_quotes_runtime.
+ */
+ protected function envCheckMagicQuotes() {
+ if( wfIniGetBool( "magic_quotes_runtime" ) ) {
+ $this->showError( 'config-magic-quotes-runtime' );
+ return false;
+ }
+ }
+
+ /**
+ * Environment check for magic_quotes_sybase.
+ */
+ protected function envCheckMagicSybase() {
+ if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
+ $this->showError( 'config-magic-quotes-sybase' );
+ return false;
+ }
+ }
+
+ /**
+ * Environment check for mbstring.func_overload.
+ */
+ protected function envCheckMbstring() {
+ if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
+ $this->showError( 'config-mbstring' );
+ return false;
+ }
+ }
+
+ /**
+ * Environment check for zend.ze1_compatibility_mode.
+ */
+ protected function envCheckZE1() {
+ if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
+ $this->showError( 'config-ze1' );
+ return false;
+ }
+ }
+
+ /**
+ * Environment check for safe_mode.
+ */
+ protected function envCheckSafeMode() {
+ if ( wfIniGetBool( 'safe_mode' ) ) {
+ $this->setVar( '_SafeMode', true );
+ $this->showMessage( 'config-safe-mode' );
+ }
+ }
+
+ /**
+ * Environment check for the XML module.
+ */
+ protected function envCheckXML() {
+ if ( !function_exists( "utf8_encode" ) ) {
+ $this->showError( 'config-xml-bad' );
+ return false;
+ }
+ }
+
+ /**
+ * Environment check for the PCRE module.
+ */
+ protected function envCheckPCRE() {
+ if ( !function_exists( 'preg_match' ) ) {
+ $this->showError( 'config-pcre' );
+ return false;
+ }
+ wfSuppressWarnings();
+ $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
+ wfRestoreWarnings();
+ if ( $regexd != '--' ) {
+ $this->showError( 'config-pcre-no-utf8' );
+ return false;
+ }
+ }
+
+ /**
+ * Environment check for available memory.
+ */
+ protected function envCheckMemory() {
+ $limit = ini_get( 'memory_limit' );
+
+ if ( !$limit || $limit == -1 ) {
+ return true;
+ }
+
+ $n = wfShorthandToInteger( $limit );
+
+ if( $n < $this->minMemorySize * 1024 * 1024 ) {
+ $newLimit = "{$this->minMemorySize}M";
+
+ if( ini_set( "memory_limit", $newLimit ) === false ) {
+ $this->showMessage( 'config-memory-bad', $limit );
+ } else {
+ $this->showMessage( 'config-memory-raised', $limit, $newLimit );
+ $this->setVar( '_RaiseMemory', true );
+ }
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Environment check for compiled object cache types.
+ */
+ protected function envCheckCache() {
+ $caches = array();
+ foreach ( $this->objectCaches as $name => $function ) {
+ if ( function_exists( $function ) ) {
+ $caches[$name] = true;
+ }
+ }
+
+ if ( !$caches ) {
+ $this->showMessage( 'config-no-cache' );
+ }
+
+ $this->setVar( '_Caches', $caches );
+ }
+
+ /**
+ * Search for GNU diff3.
+ */
+ protected function envCheckDiff3() {
+ $names = array( "gdiff3", "diff3", "diff3.exe" );
+ $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
+
+ $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
+
+ if ( $diff3 ) {
+ $this->setVar( 'wgDiff3', $diff3 );
+ } else {
+ $this->setVar( 'wgDiff3', false );
+ $this->showMessage( 'config-diff3-bad' );
+ }
+ }
+
+ /**
+ * Environment check for ImageMagick and GD.
+ */
+ protected function envCheckGraphics() {
+ $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
+ $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) );
+
+ $this->setVar( 'wgImageMagickConvertCommand', '' );
+ if ( $convert ) {
+ $this->setVar( 'wgImageMagickConvertCommand', $convert );
+ $this->showMessage( 'config-imagemagick', $convert );
+ return true;
+ } elseif ( function_exists( 'imagejpeg' ) ) {
+ $this->showMessage( 'config-gd' );
+ return true;
+ } else {
+ $this->showMessage( 'config-no-scaling' );
+ }
+ }
+
+ /**
+ * Environment check for setting $IP and $wgScriptPath.
+ */
+ protected function envCheckPath() {
+ global $IP;
+ $IP = dirname( dirname( dirname( __FILE__ ) ) );
+
+ $this->setVar( 'IP', $IP );
+
+ // 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
+ if ( !empty( $_SERVER['PHP_SELF'] ) ) {
+ $path = $_SERVER['PHP_SELF'];
+ } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
+ $path = $_SERVER['SCRIPT_NAME'];
+ } elseif ( $this->getVar( 'wgScriptPath' ) ) {
+ // Some kind soul has set it for us already (e.g. debconf)
+ return true;
+ } else {
+ $this->showError( 'config-no-uri' );
+ return false;
+ }
+
+ $uri = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path );
+ $this->setVar( 'wgScriptPath', $uri );
+ }
+
+ /**
+ * Environment check for setting the preferred PHP file extension.
+ */
+ protected function envCheckExtension() {
+ // FIXME: detect this properly
+ if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
+ $ext = 'php5';
+ } else {
+ $ext = 'php';
+ }
+ $this->setVar( 'wgScriptExtension', ".$ext" );
+ }
+
+ /**
+ * TODO: document
+ */
+ protected function envCheckShellLocale() {
+ $os = php_uname( 's' );
+ $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
+
+ if ( !in_array( $os, $supported ) ) {
+ return true;
+ }
+
+ # Get a list of available locales.
+ $ret = false;
+ $lines = wfShellExec( '/usr/bin/locale -a', $ret );
+
+ if ( $ret ) {
+ return true;
+ }
+
+ $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
+ $candidatesByLocale = array();
+ $candidatesByLang = array();
+
+ foreach ( $lines as $line ) {
+ if ( $line === '' ) {
+ continue;
+ }
+
+ if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
+ continue;
+ }
+
+ list( $all, $lang, $territory, $charset, $modifier ) = $m;
+
+ $candidatesByLocale[$m[0]] = $m;
+ $candidatesByLang[$lang][] = $m;
+ }
+
+ # Try the current value of LANG.
+ if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
+ $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
+ return true;
+ }
+
+ # Try the most common ones.
+ $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
+ foreach ( $commonLocales as $commonLocale ) {
+ if ( isset( $candidatesByLocale[$commonLocale] ) ) {
+ $this->setVar( 'wgShellLocale', $commonLocale );
+ return true;
+ }
+ }
+
+ # Is there an available locale in the Wiki's language?
+ $wikiLang = $this->getVar( 'wgLanguageCode' );
+
+ if ( isset( $candidatesByLang[$wikiLang] ) ) {
+ $m = reset( $candidatesByLang[$wikiLang] );
+ $this->setVar( 'wgShellLocale', $m[0] );
+ return true;
+ }
+
+ # Are there any at all?
+ if ( count( $candidatesByLocale ) ) {
+ $m = reset( $candidatesByLocale );
+ $this->setVar( 'wgShellLocale', $m[0] );
+ return true;
+ }
+
+ # Give up.
+ return true;
+ }
+
+ /**
+ * TODO: document
+ */
+ protected function envCheckUploadsDirectory() {
+ global $IP, $wgServer;
+
+ $dir = $IP . '/images/';
+ $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
+ $safe = !$this->dirIsExecutable( $dir, $url );
+
+ if ( $safe ) {
+ return true;
+ } else {
+ $this->showMessage( 'config-uploads-not-safe', $dir );
+ }
+ }
+
+ /**
+ * Checks if suhosin.get.max_value_length is set, and if so, sets
+ * $wgResourceLoaderMaxQueryLength to that value in the generated
+ * LocalSettings file
+ */
+ protected function envCheckSuhosinMaxValueLength() {
+ $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
+ if ( $maxValueLength > 0 ) {
+ $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
+ } else {
+ $maxValueLength = -1;
+ }
+ $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength );
+ }
+
+ /**
+ * Convert a hex string representing a Unicode code point to that code point.
+ * @param $c String
+ * @return string
+ */
+ protected function unicodeChar( $c ) {
+ $c = hexdec($c);
+ if ($c <= 0x7F) {
+ return chr($c);
+ } else if ($c <= 0x7FF) {
+ return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
+ } else if ($c <= 0xFFFF) {
+ return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
+ . chr(0x80 | $c & 0x3F);
+ } else if ($c <= 0x10FFFF) {
+ return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
+ . chr(0x80 | $c >> 6 & 0x3F)
+ . chr(0x80 | $c & 0x3F);
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * 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
+ * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
+ * Note that we use the hex representation to create the code
+ * points in order to avoid any Unicode-destroying during transit.
+ */
+ $not_normal_c = $this->unicodeChar("FA6C");
+ $normal_c = $this->unicodeChar("242EE");
+
+ $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, UNORM_NFC );
+ if ( $utf8 !== $normal_c ) $needsUpdate = true;
+ }
+ if( $intl ) {
+ $useNormalizer = 'intl';
+ $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
+ if ( $intl !== $normal_c ) $needsUpdate = true;
+ }
+
+ // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
+ if( $useNormalizer === 'php' ) {
+ $this->showMessage( 'config-unicode-pure-php-warning' );
+ } else {
+ $this->showMessage( 'config-unicode-using-' . $useNormalizer );
+ if( $needsUpdate ) {
+ $this->showMessage( 'config-unicode-update-warning' );
+ }
+ }
+ }
+
+ /**
+ * Get an array of likely places we can find executables. Check a bunch
+ * of known Unix-like defaults, as well as the PATH environment variable
+ * (which should maybe make it work for Windows?)
+ *
+ * @return Array
+ */
+ protected static function getPossibleBinPaths() {
+ return array_merge(
+ array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
+ '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
+ explode( PATH_SEPARATOR, getenv( 'PATH' ) )
+ );
+ }
+
+ /**
+ * Search a path for any of the given executable names. Returns the
+ * executable name if found. Also checks the version string returned
+ * by each executable.
+ *
+ * Used only by environment checks.
+ *
+ * @param $path String: path to search
+ * @param $names Array of executable names
+ * @param $versionInfo Boolean false or array with two members:
+ * 0 => Command to run for version check, with $1 for the full executable name
+ * 1 => String to compare the output with
+ *
+ * If $versionInfo is not false, only executables with a version
+ * matching $versionInfo[1] will be returned.
+ */
+ public static function locateExecutable( $path, $names, $versionInfo = false ) {
+ if ( !is_array( $names ) ) {
+ $names = array( $names );
+ }
+
+ foreach ( $names as $name ) {
+ $command = $path . DIRECTORY_SEPARATOR . $name;
+
+ wfSuppressWarnings();
+ $file_exists = file_exists( $command );
+ wfRestoreWarnings();
+
+ if ( $file_exists ) {
+ if ( !$versionInfo ) {
+ return $command;
+ }
+
+ $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
+ if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
+ return $command;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
+ * @see locateExecutable()
+ */
+ public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
+ foreach( self::getPossibleBinPaths() as $path ) {
+ $exe = self::locateExecutable( $path, $names, $versionInfo );
+ if( $exe !== false ) {
+ return $exe;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if scripts located in the given directory can be executed via the given URL.
+ *
+ * Used only by environment checks.
+ */
+ public function dirIsExecutable( $dir, $url ) {
+ $scriptTypes = array(
+ 'php' => array(
+ "<?php echo 'ex' . 'ec';",
+ "#!/var/env php5\n<?php echo 'ex' . 'ec';",
+ ),
+ );
+
+ // it would be good to check other popular languages here, but it'll be slow.
+
+ wfSuppressWarnings();
+
+ foreach ( $scriptTypes as $ext => $contents ) {
+ foreach ( $contents as $source ) {
+ $file = 'exectest.' . $ext;
+
+ if ( !file_put_contents( $dir . $file, $source ) ) {
+ break;
+ }
+
+ try {
+ $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
+ }
+ catch( MWException $e ) {
+ // Http::get throws with allow_url_fopen = false and no curl extension.
+ $text = null;
+ }
+ unlink( $dir . $file );
+
+ if ( $text == 'exec' ) {
+ wfRestoreWarnings();
+ return $ext;
+ }
+ }
+ }
+
+ wfRestoreWarnings();
+
+ return false;
+ }
+
+ /**
+ * ParserOptions are constructed before we determined the language, so fix it
+ */
+ public function setParserLanguage( $lang ) {
+ $this->parserOptions->setTargetLanguage( $lang );
+ $this->parserOptions->setUserLang( $lang->getCode() );
+ }
+
+ /**
+ * Overridden by WebInstaller to provide lastPage parameters.
+ */
+ protected function getDocUrl( $page ) {
+ return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
+ }
+
+ /**
+ * Finds extensions that follow the format /extensions/Name/Name.php,
+ * and returns an array containing the value for 'Name' for each found extension.
+ *
+ * @return array
+ */
+ public function findExtensions() {
+ if( $this->getVar( 'IP' ) === null ) {
+ return false;
+ }
+
+ $exts = array();
+ $dir = $this->getVar( 'IP' ) . '/extensions';
+ $dh = opendir( $dir );
+
+ while ( ( $file = readdir( $dh ) ) !== false ) {
+ if( file_exists( "$dir/$file/$file.php" ) ) {
+ $exts[] = $file;
+ }
+ }
+
+ return $exts;
+ }
+
+ /**
+ * Installs the auto-detected extensions.
+ *
+ * @return Status
+ */
+ protected function includeExtensions() {
+ global $IP;
+ $exts = $this->getVar( '_Extensions' );
+ $IP = $this->getVar( 'IP' );
+
+ /**
+ * We need to include DefaultSettings before including extensions to avoid
+ * warnings about unset variables. However, the only thing we really
+ * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
+ * if the extension has hidden hook registration in $wgExtensionFunctions,
+ * but we're not opening that can of worms
+ * @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
+ */
+ global $wgAutoloadClasses;
+ require( "$IP/includes/DefaultSettings.php" );
+
+ foreach( $exts as $e ) {
+ require_once( $IP . '/extensions' . "/$e/$e.php" );
+ }
+
+ $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
+ $wgHooks['LoadExtensionSchemaUpdates'] : array();
+
+ // Unset everyone else's hooks. Lord knows what someone might be doing
+ // in ParserFirstCallInit (see bug 27171)
+ $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
+
+ return Status::newGood();
+ }
+
+ /**
+ * Get an array of install steps. Should always be in the format of
+ * array(
+ * 'name' => 'someuniquename',
+ * 'callback' => array( $obj, 'method' ),
+ * )
+ * There must be a config-install-$name message defined per step, which will
+ * be shown on install.
+ *
+ * @param $installer DatabaseInstaller so we can make callbacks
+ * @return array
+ */
+ protected function getInstallSteps( DatabaseInstaller $installer ) {
+ $coreInstallSteps = array(
+ array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ),
+ array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ),
+ array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
+ array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
+ array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
+ array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
+ array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
+ );
+
+ // Build the array of install steps starting from the core install list,
+ // then adding any callbacks that wanted to attach after a given step
+ foreach( $coreInstallSteps as $step ) {
+ $this->installSteps[] = $step;
+ if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) {
+ $this->installSteps = array_merge(
+ $this->installSteps,
+ $this->extraInstallSteps[ $step['name'] ]
+ );
+ }
+ }
+
+ // Prepend any steps that want to be at the beginning
+ if( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
+ $this->installSteps = array_merge(
+ $this->extraInstallSteps['BEGINNING'],
+ $this->installSteps
+ );
+ }
+
+ // Extensions should always go first, chance to tie into hooks and such
+ if( count( $this->getVar( '_Extensions' ) ) ) {
+ array_unshift( $this->installSteps,
+ array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
+ );
+ $this->installSteps[] = array(
+ 'name' => 'extension-tables',
+ 'callback' => array( $installer, 'createExtensionTables' )
+ );
+ }
+ return $this->installSteps;
+ }
+
+ /**
+ * Actually perform the installation.
+ *
+ * @param $startCB Array A callback array for the beginning of each step
+ * @param $endCB Array A callback array for the end of each step
+ *
+ * @return Array of Status objects
+ */
+ public function performInstallation( $startCB, $endCB ) {
+ $installResults = array();
+ $installer = $this->getDBInstaller();
+ $installer->preInstall();
+ $steps = $this->getInstallSteps( $installer );
+ foreach( $steps as $stepObj ) {
+ $name = $stepObj['name'];
+ call_user_func_array( $startCB, array( $name ) );
+
+ // Perform the callback step
+ $status = call_user_func( $stepObj['callback'], $installer );
+
+ // Output and save the results
+ call_user_func( $endCB, $name, $status );
+ $installResults[$name] = $status;
+
+ // If we've hit some sort of fatal, we need to bail.
+ // Callback already had a chance to do output above.
+ if( !$status->isOk() ) {
+ break;
+ }
+ }
+ if( $status->isOk() ) {
+ $this->setVar( '_InstallDone', true );
+ }
+ return $installResults;
+ }
+
+ /**
+ * Generate $wgSecretKey. Will warn if we had to use mt_rand() instead of
+ * /dev/urandom
+ *
+ * @return Status
+ */
+ public function generateKeys() {
+ $keys = array( 'wgSecretKey' => 64 );
+ if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
+ $keys['wgUpgradeKey'] = 16;
+ }
+ return $this->doGenerateKeys( $keys );
+ }
+
+ /**
+ * Generate a secret value for variables using either
+ * /dev/urandom or mt_rand(). Produce a warning in the later case.
+ *
+ * @param $keys Array
+ * @return Status
+ */
+ protected function doGenerateKeys( $keys ) {
+ $status = Status::newGood();
+
+ wfSuppressWarnings();
+ $file = fopen( "/dev/urandom", "r" );
+ wfRestoreWarnings();
+
+ foreach ( $keys as $name => $length ) {
+ if ( $file ) {
+ $secretKey = bin2hex( fread( $file, $length / 2 ) );
+ } else {
+ $secretKey = '';
+
+ for ( $i = 0; $i < $length / 8; $i++ ) {
+ $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) );
+ }
+ }
+
+ $this->setVar( $name, $secretKey );
+ }
+
+ if ( $file ) {
+ fclose( $file );
+ } else {
+ $names = array_keys ( $keys );
+ $names = preg_replace( '/^(.*)$/', '\$$1', $names );
+ global $wgLang;
+ $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Create the first user account, grant it sysop and bureaucrat rights
+ *
+ * @return Status
+ */
+ protected function createSysop() {
+ $name = $this->getVar( '_AdminName' );
+ $user = User::newFromName( $name );
+
+ if ( !$user ) {
+ // We should've validated this earlier anyway!
+ return Status::newFatal( 'config-admin-error-user', $name );
+ }
+
+ if ( $user->idForName() == 0 ) {
+ $user->addToDatabase();
+
+ try {
+ $user->setPassword( $this->getVar( '_AdminPassword' ) );
+ } catch( PasswordError $pwe ) {
+ return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
+ }
+
+ $user->addGroup( 'sysop' );
+ $user->addGroup( 'bureaucrat' );
+ if( $this->getVar( '_AdminEmail' ) ) {
+ $user->setEmail( $this->getVar( '_AdminEmail' ) );
+ }
+ $user->saveSettings();
+
+ // Update user count
+ $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
+ $ssUpdate->doUpdate();
+ }
+ $status = Status::newGood();
+
+ if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
+ $this->subscribeToMediaWikiAnnounce( $status );
+ }
+
+ return $status;
+ }
+
+ private function subscribeToMediaWikiAnnounce( Status $s ) {
+ $params = array(
+ 'email' => $this->getVar( '_AdminEmail' ),
+ 'language' => 'en',
+ 'digest' => 0
+ );
+
+ // Mailman doesn't support as many languages as we do, so check to make
+ // sure their selected language is available
+ $myLang = $this->getVar( '_UserLang' );
+ if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
+ $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
+ $params['language'] = $myLang;
+ }
+
+ $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
+ array( 'method' => 'POST', 'postData' => $params ) )->execute();
+ if( !$res->isOK() ) {
+ $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
+ }
+ }
+
+ /**
+ * Insert Main Page with default content.
+ *
+ * @return Status
+ */
+ protected function createMainpage( DatabaseInstaller $installer ) {
+ $status = Status::newGood();
+ try {
+ $article = new Article( Title::newMainPage() );
+ $article->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" .
+ wfMsgForContent( 'mainpagedocfooter' ),
+ '',
+ EDIT_NEW,
+ false,
+ User::newFromName( 'MediaWiki default' ) );
+ } catch (MWException $e) {
+ //using raw, because $wgShowExceptionDetails can not be set yet
+ $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Override the necessary bits of the config to run an installation.
+ */
+ public static function overrideConfig() {
+ define( 'MW_NO_SESSION', 1 );
+
+ // Don't access the database
+ $GLOBALS['wgUseDatabaseMessages'] = false;
+ // Debug-friendly
+ $GLOBALS['wgShowExceptionDetails'] = true;
+ // Don't break forms
+ $GLOBALS['wgExternalLinkTarget'] = '_blank';
+
+ // Extended debugging
+ $GLOBALS['wgShowSQLErrors'] = true;
+ $GLOBALS['wgShowDBErrorBacktrace'] = true;
+
+ // Allow multiple ob_flush() calls
+ $GLOBALS['wgDisableOutputCompression'] = true;
+
+ // Use a sensible cookie prefix (not my_wiki)
+ $GLOBALS['wgCookiePrefix'] = 'mw_installer';
+
+ // Some of the environment checks make shell requests, remove limits
+ $GLOBALS['wgMaxShellMemory'] = 0;
+ }
+
+ /**
+ * Add an installation step following the given step.
+ *
+ * @param $callback Array A valid installation callback array, in this form:
+ * array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
+ * @param $findStep String the step to find. Omit to put the step at the beginning
+ */
+ public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
+ $this->extraInstallSteps[$findStep][] = $callback;
+ }
+}
diff --git a/includes/installer/LocalSettingsGenerator.php b/includes/installer/LocalSettingsGenerator.php
new file mode 100644
index 00000000..04926c9b
--- /dev/null
+++ b/includes/installer/LocalSettingsGenerator.php
@@ -0,0 +1,349 @@
+<?php
+/**
+ * Generator for LocalSettings.php file.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for generating LocalSettings.php file.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class LocalSettingsGenerator {
+
+ private $extensions = array();
+ private $values = array();
+ private $groupPermissions = array();
+ private $dbSettings = '';
+ private $safeMode = false;
+
+ /**
+ * @var Installer
+ */
+ private $installer;
+
+ /**
+ * Constructor.
+ *
+ * @param $installer Installer subclass
+ */
+ public function __construct( Installer $installer ) {
+ $this->installer = $installer;
+
+ $this->extensions = $installer->getVar( '_Extensions' );
+
+ $db = $installer->getDBInstaller( $installer->getVar( 'wgDBtype' ) );
+
+ $confItems = array_merge(
+ array(
+ 'wgScriptPath', 'wgScriptExtension',
+ 'wgPasswordSender', 'wgImageMagickConvertCommand', 'wgShellLocale',
+ 'wgLanguageCode', 'wgEnableEmail', 'wgEnableUserEmail', 'wgDiff3',
+ 'wgEnotifUserTalk', 'wgEnotifWatchlist', 'wgEmailAuthentication',
+ 'wgDBtype', 'wgSecretKey', 'wgRightsUrl', 'wgSitename', 'wgRightsIcon',
+ 'wgRightsText', 'wgRightsCode', 'wgMainCacheType', 'wgEnableUploads',
+ 'wgMainCacheType', '_MemCachedServers', 'wgDBserver', 'wgDBuser',
+ 'wgDBpassword', 'wgUseInstantCommons', 'wgUpgradeKey', 'wgDefaultSkin',
+ 'wgMetaNamespace', 'wgResourceLoaderMaxQueryLength'
+ ),
+ $db->getGlobalNames()
+ );
+
+ $unescaped = array( 'wgRightsIcon' );
+ $boolItems = array(
+ 'wgEnableEmail', 'wgEnableUserEmail', 'wgEnotifUserTalk',
+ 'wgEnotifWatchlist', 'wgEmailAuthentication', 'wgEnableUploads', 'wgUseInstantCommons'
+ );
+
+ foreach( $confItems as $c ) {
+ $val = $installer->getVar( $c );
+
+ if( in_array( $c, $boolItems ) ) {
+ $val = wfBoolToStr( $val );
+ }
+
+ if ( !in_array( $c, $unescaped ) ) {
+ $val = self::escapePhpString( $val );
+ }
+
+ $this->values[$c] = $val;
+ }
+
+ $this->dbSettings = $db->getLocalSettings();
+ $this->safeMode = $installer->getVar( '_SafeMode' );
+ $this->values['wgEmergencyContact'] = $this->values['wgPasswordSender'];
+ }
+
+ /**
+ * For $wgGroupPermissions, set a given ['group']['permission'] value.
+ * @param $group String Group name
+ * @param $rightsArr Array An array of permissions, in the form of:
+ * array( 'right' => true, 'right2' => false )
+ */
+ public function setGroupRights( $group, $rightsArr ) {
+ $this->groupPermissions[$group] = $rightsArr;
+ }
+
+ /**
+ * Returns the escaped version of a string of php code.
+ *
+ * @param $string String
+ *
+ * @return String
+ */
+ public static function escapePhpString( $string ) {
+ if ( is_array( $string ) || is_object( $string ) ) {
+ return false;
+ }
+
+ return strtr(
+ $string,
+ array(
+ "\n" => "\\n",
+ "\r" => "\\r",
+ "\t" => "\\t",
+ "\\" => "\\\\",
+ "\$" => "\\\$",
+ "\"" => "\\\""
+ )
+ );
+ }
+
+ /**
+ * Return the full text of the generated LocalSettings.php file,
+ * including the extensions
+ *
+ * @return String
+ */
+ public function getText() {
+ $localSettings = $this->getDefaultText();
+
+ if( count( $this->extensions ) ) {
+ $localSettings .= "
+# Enabled Extensions. Most extensions are enabled by including the base extension file here
+# but check specific extension documentation for more details
+# The following extensions were automatically enabled:\n";
+
+ foreach( $this->extensions as $extName ) {
+ $encExtName = self::escapePhpString( $extName );
+ $localSettings .= "require( \"extensions/$encExtName/$encExtName.php\" );\n";
+ }
+ }
+
+ $localSettings .= "\n\n# End of automatically generated settings.
+# Add more configuration options below.\n\n";
+
+ return $localSettings;
+ }
+
+ /**
+ * Write the generated LocalSettings to a file
+ *
+ * @param $fileName String Full path to filename to write to
+ */
+ public function writeFile( $fileName ) {
+ file_put_contents( $fileName, $this->getText() );
+ }
+
+ /**
+ * @return String
+ */
+ private function buildMemcachedServerList() {
+ $servers = $this->values['_MemCachedServers'];
+
+ if( !$servers ) {
+ return 'array()';
+ } else {
+ $ret = 'array( ';
+ $servers = explode( ',', $servers );
+
+ foreach( $servers as $srv ) {
+ $srv = trim( $srv );
+ $ret .= "'$srv', ";
+ }
+
+ return rtrim( $ret, ', ' ) . ' )';
+ }
+ }
+
+ /**
+ * @return String
+ */
+ private function getDefaultText() {
+ if( !$this->values['wgImageMagickConvertCommand'] ) {
+ $this->values['wgImageMagickConvertCommand'] = '/usr/bin/convert';
+ $magic = '#';
+ } else {
+ $magic = '';
+ }
+
+ if( !$this->values['wgShellLocale'] ) {
+ $this->values['wgShellLocale'] = 'en_US.UTF-8';
+ $locale = '#';
+ } else {
+ $locale = '';
+ }
+
+ $rightsUrl = $this->values['wgRightsUrl'] ? '' : '#';
+ $hashedUploads = $this->safeMode ? '' : '#';
+ $metaNamespace = '';
+ if( $this->values['wgMetaNamespace'] !== $this->values['wgSitename'] ) {
+ $metaNamespace = "\$wgMetaNamespace = \"{$this->values['wgMetaNamespace']}\";\n";
+ }
+
+ $groupRights = '';
+ if( $this->groupPermissions ) {
+ $groupRights .= "# The following permissions were set based on your choice in the installer\n";
+ foreach( $this->groupPermissions as $group => $rightArr ) {
+ $group = self::escapePhpString( $group );
+ foreach( $rightArr as $right => $perm ) {
+ $right = self::escapePhpString( $right );
+ $groupRights .= "\$wgGroupPermissions['$group']['$right'] = " .
+ wfBoolToStr( $perm ) . ";\n";
+ }
+ }
+ }
+
+ switch( $this->values['wgMainCacheType'] ) {
+ case 'anything':
+ case 'db':
+ case 'memcached':
+ case 'accel':
+ $cacheType = 'CACHE_' . strtoupper( $this->values['wgMainCacheType']);
+ break;
+ case 'none':
+ default:
+ $cacheType = 'CACHE_NONE';
+ }
+
+ $mcservers = $this->buildMemcachedServerList();
+ return "<?php
+# This file was automatically generated by the MediaWiki {$GLOBALS['wgVersion']}
+# installer. If you make manual changes, please keep track in case you
+# need to recreate them later.
+#
+# See includes/DefaultSettings.php for all configurable settings
+# and their default values, but don't forget to make changes in _this_
+# file, not there.
+#
+# Further documentation for configuration settings may be found at:
+# http://www.mediawiki.org/wiki/Manual:Configuration_settings
+
+# Protect against web entry
+if ( !defined( 'MEDIAWIKI' ) ) {
+ exit;
+}
+
+## Uncomment this to disable output compression
+# \$wgDisableOutputCompression = true;
+
+\$wgSitename = \"{$this->values['wgSitename']}\";
+{$metaNamespace}
+## The URL base path to the directory containing the wiki;
+## defaults for all runtime URL paths are based off of this.
+## For more information on customizing the URLs please see:
+## http://www.mediawiki.org/wiki/Manual:Short_URL
+\$wgScriptPath = \"{$this->values['wgScriptPath']}\";
+\$wgScriptExtension = \"{$this->values['wgScriptExtension']}\";
+
+## The relative URL path to the skins directory
+\$wgStylePath = \"\$wgScriptPath/skins\";
+
+## The relative URL path to the logo. Make sure you change this from the default,
+## or else you'll overwrite your logo when you upgrade!
+\$wgLogo = \"\$wgStylePath/common/images/wiki.png\";
+
+## UPO means: this is also a user preference option
+
+\$wgEnableEmail = {$this->values['wgEnableEmail']};
+\$wgEnableUserEmail = {$this->values['wgEnableUserEmail']}; # UPO
+
+\$wgEmergencyContact = \"{$this->values['wgEmergencyContact']}\";
+\$wgPasswordSender = \"{$this->values['wgPasswordSender']}\";
+
+\$wgEnotifUserTalk = {$this->values['wgEnotifUserTalk']}; # UPO
+\$wgEnotifWatchlist = {$this->values['wgEnotifWatchlist']}; # UPO
+\$wgEmailAuthentication = {$this->values['wgEmailAuthentication']};
+
+## Database settings
+\$wgDBtype = \"{$this->values['wgDBtype']}\";
+\$wgDBserver = \"{$this->values['wgDBserver']}\";
+\$wgDBname = \"{$this->values['wgDBname']}\";
+\$wgDBuser = \"{$this->values['wgDBuser']}\";
+\$wgDBpassword = \"{$this->values['wgDBpassword']}\";
+
+{$this->dbSettings}
+
+## Shared memory settings
+\$wgMainCacheType = $cacheType;
+\$wgMemCachedServers = $mcservers;
+
+## To enable image uploads, make sure the 'images' directory
+## is writable, then set this to true:
+\$wgEnableUploads = {$this->values['wgEnableUploads']};
+{$magic}\$wgUseImageMagick = true;
+{$magic}\$wgImageMagickConvertCommand = \"{$this->values['wgImageMagickConvertCommand']}\";
+
+# InstantCommons allows wiki to use images from http://commons.wikimedia.org
+\$wgUseInstantCommons = {$this->values['wgUseInstantCommons']};
+
+## If you use ImageMagick (or any other shell command) on a
+## Linux server, this will need to be set to the name of an
+## available UTF-8 locale
+{$locale}\$wgShellLocale = \"{$this->values['wgShellLocale']}\";
+
+## If you want to use image uploads under safe mode,
+## create the directories images/archive, images/thumb and
+## images/temp, and make them all writable. Then uncomment
+## this, if it's not already uncommented:
+{$hashedUploads}\$wgHashedUploadDirectory = false;
+
+## If you have the appropriate support software installed
+## you can enable inline LaTeX equations:
+\$wgUseTeX = false;
+
+## Set \$wgCacheDirectory to a writable directory on the web server
+## to make your wiki go slightly faster. The directory should not
+## be publically accessible from the web.
+#\$wgCacheDirectory = \"\$IP/cache\";
+
+# Site language code, should be one of ./languages/Language(.*).php
+\$wgLanguageCode = \"{$this->values['wgLanguageCode']}\";
+
+\$wgSecretKey = \"{$this->values['wgSecretKey']}\";
+
+# Site upgrade key. Must be set to a string (default provided) to turn on the
+# web installer while LocalSettings.php is in place
+\$wgUpgradeKey = \"{$this->values['wgUpgradeKey']}\";
+
+## Default skin: you can change the default skin. Use the internal symbolic
+## names, ie 'standard', 'nostalgia', 'cologneblue', 'monobook', 'vector':
+\$wgDefaultSkin = \"{$this->values['wgDefaultSkin']}\";
+
+## For attaching licensing metadata to pages, and displaying an
+## appropriate copyright notice / icon. GNU Free Documentation
+## License and Creative Commons licenses are supported so far.
+{$rightsUrl}\$wgEnableCreativeCommonsRdf = true;
+\$wgRightsPage = \"\"; # Set to the title of a wiki page that describes your license/copyright
+\$wgRightsUrl = \"{$this->values['wgRightsUrl']}\";
+\$wgRightsText = \"{$this->values['wgRightsText']}\";
+\$wgRightsIcon = \"{$this->values['wgRightsIcon']}\";
+# \$wgRightsCode = \"{$this->values['wgRightsCode']}\"; # Not yet used
+
+# Path to the GNU diff3 utility. Used for conflict resolution.
+\$wgDiff3 = \"{$this->values['wgDiff3']}\";
+
+{$groupRights}
+
+# Query string length limit for ResourceLoader. You should only set this if
+# your web server has a query string length limit (then set it to that limit),
+# or if you have suhosin.get.max_value_length set in php.ini (then set it to
+# that value)
+\$wgResourceLoaderMaxQueryLength = {$this->values['wgResourceLoaderMaxQueryLength']};
+";
+ }
+
+}
diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php
new file mode 100644
index 00000000..2fe16dcf
--- /dev/null
+++ b/includes/installer/MysqlInstaller.php
@@ -0,0 +1,589 @@
+<?php
+/**
+ * MySQL-specific installer.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for setting up the MediaWiki database using MySQL.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class MysqlInstaller extends DatabaseInstaller {
+
+ protected $globalNames = array(
+ 'wgDBserver',
+ 'wgDBname',
+ 'wgDBuser',
+ 'wgDBpassword',
+ 'wgDBprefix',
+ 'wgDBTableOptions',
+ 'wgDBmysql5',
+ );
+
+ protected $internalDefaults = array(
+ '_MysqlEngine' => 'InnoDB',
+ '_MysqlCharset' => 'binary',
+ '_InstallUser' => 'root',
+ );
+
+ public $supportedEngines = array( 'InnoDB', 'MyISAM' );
+
+ public $minimumVersion = '4.0.14';
+
+ public $webUserPrivs = array(
+ 'DELETE',
+ 'INSERT',
+ 'SELECT',
+ 'UPDATE',
+ 'CREATE TEMPORARY TABLES',
+ );
+
+ public function getName() {
+ return 'mysql';
+ }
+
+ public function __construct( $parent ) {
+ parent::__construct( $parent );
+ }
+
+ public function isCompiled() {
+ return self::checkExtension( 'mysql' );
+ }
+
+ public function getGlobalDefaults() {
+ return array();
+ }
+
+ public function getConnectForm() {
+ return
+ $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
+ Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) .
+ $this->getTextBox( 'wgDBname', 'config-db-name', array(), $this->parent->getHelpBox( 'config-db-name-help' ) ) .
+ $this->getTextBox( 'wgDBprefix', 'config-db-prefix', array(), $this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
+ Html::closeElement( 'fieldset' ) .
+ $this->getInstallUserBox();
+ }
+
+ public function submitConnectForm() {
+ // Get variables from the request.
+ $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBname', 'wgDBprefix' ) );
+
+ // Validate them.
+ $status = Status::newGood();
+ if ( !strlen( $newValues['wgDBserver'] ) ) {
+ $status->fatal( 'config-missing-db-host' );
+ }
+ if ( !strlen( $newValues['wgDBname'] ) ) {
+ $status->fatal( 'config-missing-db-name' );
+ } elseif ( !preg_match( '/^[a-z0-9_-]+$/i', $newValues['wgDBname'] ) ) {
+ $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
+ }
+ if ( !preg_match( '/^[a-z0-9_-]*$/i', $newValues['wgDBprefix'] ) ) {
+ $status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ // Submit user box
+ $status = $this->submitInstallUserBox();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ // Try to connect
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+
+ // Check version
+ $version = $conn->getServerVersion();
+ if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
+ return Status::newFatal( 'config-mysql-old', $this->minimumVersion, $version );
+ }
+
+ return $status;
+ }
+
+ public function openConnection() {
+ $status = Status::newGood();
+ try {
+ $db = new DatabaseMysql(
+ $this->getVar( 'wgDBserver' ),
+ $this->getVar( '_InstallUser' ),
+ $this->getVar( '_InstallPassword' ),
+ false,
+ false,
+ 0,
+ $this->getVar( 'wgDBprefix' )
+ );
+ $status->value = $db;
+ } catch ( DBConnectionError $e ) {
+ $status->fatal( 'config-connection-error', $e->getMessage() );
+ }
+ return $status;
+ }
+
+ public function preUpgrade() {
+ global $wgDBuser, $wgDBpassword;
+
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ $this->parent->showStatusError( $status );
+ return;
+ }
+ $conn = $status->value;
+ $conn->selectDB( $this->getVar( 'wgDBname' ) );
+
+ # Determine existing default character set
+ if ( $conn->tableExists( "revision" ) ) {
+ $revision = $conn->buildLike( $this->getVar( 'wgDBprefix' ) . 'revision' );
+ $res = $conn->query( "SHOW TABLE STATUS $revision", __METHOD__ );
+ $row = $conn->fetchObject( $res );
+ if ( !$row ) {
+ $this->parent->showMessage( 'config-show-table-status' );
+ $existingSchema = false;
+ $existingEngine = false;
+ } else {
+ if ( preg_match( '/^latin1/', $row->Collation ) ) {
+ $existingSchema = 'mysql4';
+ } elseif ( preg_match( '/^utf8/', $row->Collation ) ) {
+ $existingSchema = 'utf8';
+ } elseif ( preg_match( '/^binary/', $row->Collation ) ) {
+ $existingSchema = 'binary';
+ } else {
+ $existingSchema = false;
+ $this->parent->showMessage( 'config-unknown-collation' );
+ }
+ if ( isset( $row->Engine ) ) {
+ $existingEngine = $row->Engine;
+ } else {
+ $existingEngine = $row->Type;
+ }
+ }
+ } else {
+ $existingSchema = false;
+ $existingEngine = false;
+ }
+
+ if ( $existingSchema && $existingSchema != $this->getVar( '_MysqlCharset' ) ) {
+ $this->setVar( '_MysqlCharset', $existingSchema );
+ }
+ if ( $existingEngine && $existingEngine != $this->getVar( '_MysqlEngine' ) ) {
+ $this->setVar( '_MysqlEngine', $existingEngine );
+ }
+
+ # Normal user and password are selected after this step, so for now
+ # just copy these two
+ $wgDBuser = $this->getVar( '_InstallUser' );
+ $wgDBpassword = $this->getVar( '_InstallPassword' );
+ }
+
+ /**
+ * Get a list of storage engines that are available and supported
+ */
+ public function getEngines() {
+ $engines = array( 'InnoDB', 'MyISAM' );
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $engines;
+ }
+ $conn = $status->value;
+
+ $version = $conn->getServerVersion();
+ if ( version_compare( $version, "4.1.2", "<" ) ) {
+ // No SHOW ENGINES in this version
+ return $engines;
+ }
+
+ $engines = array();
+ $res = $conn->query( 'SHOW ENGINES', __METHOD__ );
+ foreach ( $res as $row ) {
+ if ( $row->Support == 'YES' || $row->Support == 'DEFAULT' ) {
+ $engines[] = $row->Engine;
+ }
+ }
+ $engines = array_intersect( $this->supportedEngines, $engines );
+ return $engines;
+ }
+
+ /**
+ * Get a list of character sets that are available and supported
+ */
+ public function getCharsets() {
+ $status = $this->getConnection();
+ $mysql5 = array( 'binary', 'utf8' );
+ $mysql4 = array( 'mysql4' );
+ if ( !$status->isOK() ) {
+ return $mysql5;
+ }
+ if ( version_compare( $status->value->getServerVersion(), '4.1.0', '>=' ) ) {
+ return $mysql5;
+ }
+ return $mysql4;
+ }
+
+ /**
+ * Return true if the install user can create accounts
+ */
+ public function canCreateAccounts() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return false;
+ }
+ $conn = $status->value;
+
+ // Check version, need INFORMATION_SCHEMA and CREATE USER
+ if ( version_compare( $conn->getServerVersion(), '5.0.2', '<' ) ) {
+ return false;
+ }
+
+ // Get current account name
+ $currentName = $conn->selectField( '', 'CURRENT_USER()', '', __METHOD__ );
+ $parts = explode( '@', $currentName );
+ if ( count( $parts ) != 2 ) {
+ return false;
+ }
+ $quotedUser = $conn->addQuotes( $parts[0] ) .
+ '@' . $conn->addQuotes( $parts[1] );
+
+ // The user needs to have INSERT on mysql.* to be able to CREATE USER
+ // The grantee will be double-quoted in this query, as required
+ $res = $conn->select( 'INFORMATION_SCHEMA.USER_PRIVILEGES', '*',
+ array( 'GRANTEE' => $quotedUser ), __METHOD__ );
+ $insertMysql = false;
+ $grantOptions = array_flip( $this->webUserPrivs );
+ foreach ( $res as $row ) {
+ if ( $row->PRIVILEGE_TYPE == 'INSERT' ) {
+ $insertMysql = true;
+ }
+ if ( $row->IS_GRANTABLE ) {
+ unset( $grantOptions[$row->PRIVILEGE_TYPE] );
+ }
+ }
+
+ // Check for DB-specific privs for mysql.*
+ if ( !$insertMysql ) {
+ $row = $conn->selectRow( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*',
+ array(
+ 'GRANTEE' => $quotedUser,
+ 'TABLE_SCHEMA' => 'mysql',
+ 'PRIVILEGE_TYPE' => 'INSERT',
+ ), __METHOD__ );
+ if ( $row ) {
+ $insertMysql = true;
+ }
+ }
+
+ if ( !$insertMysql ) {
+ return false;
+ }
+
+ // Check for DB-level grant options
+ $res = $conn->select( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*',
+ array(
+ 'GRANTEE' => $quotedUser,
+ 'IS_GRANTABLE' => 1,
+ ), __METHOD__ );
+ foreach ( $res as $row ) {
+ $regex = $conn->likeToRegex( $row->TABLE_SCHEMA );
+ if ( preg_match( $regex, $this->getVar( 'wgDBname' ) ) ) {
+ unset( $grantOptions[$row->PRIVILEGE_TYPE] );
+ }
+ }
+ if ( count( $grantOptions ) ) {
+ // Can't grant everything
+ return false;
+ }
+ return true;
+ }
+
+ public function getSettingsForm() {
+ if ( $this->canCreateAccounts() ) {
+ $noCreateMsg = false;
+ } else {
+ $noCreateMsg = 'config-db-web-no-create-privs';
+ }
+ $s = $this->getWebUserBox( $noCreateMsg );
+
+ // Do engine selector
+ $engines = $this->getEngines();
+ // If the current default engine is not supported, use an engine that is
+ if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) {
+ $this->setVar( '_MysqlEngine', reset( $engines ) );
+ }
+ if ( count( $engines ) >= 2 ) {
+ $s .= $this->getRadioSet( array(
+ 'var' => '_MysqlEngine',
+ 'label' => 'config-mysql-engine',
+ 'itemLabelPrefix' => 'config-mysql-',
+ 'values' => $engines
+ ));
+ $s .= $this->parent->getHelpBox( 'config-mysql-engine-help' );
+ }
+
+ // If the current default charset is not supported, use a charset that is
+ $charsets = $this->getCharsets();
+ if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) {
+ $this->setVar( '_MysqlCharset', reset( $charsets ) );
+ }
+
+ // Do charset selector
+ if ( count( $charsets ) >= 2 ) {
+ $s .= $this->getRadioSet( array(
+ 'var' => '_MysqlCharset',
+ 'label' => 'config-mysql-charset',
+ 'itemLabelPrefix' => 'config-mysql-',
+ 'values' => $charsets
+ ));
+ $s .= $this->parent->getHelpBox( 'config-mysql-charset-help' );
+ }
+
+ return $s;
+ }
+
+ public function submitSettingsForm() {
+ $this->setVarsFromRequest( array( '_MysqlEngine', '_MysqlCharset' ) );
+ $status = $this->submitWebUserBox();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ // Validate the create checkbox
+ $canCreate = $this->canCreateAccounts();
+ if ( !$canCreate ) {
+ $this->setVar( '_CreateDBAccount', false );
+ $create = false;
+ } else {
+ $create = $this->getVar( '_CreateDBAccount' );
+ }
+
+ if ( !$create ) {
+ // Test the web account
+ try {
+ new DatabaseMysql(
+ $this->getVar( 'wgDBserver' ),
+ $this->getVar( 'wgDBuser' ),
+ $this->getVar( 'wgDBpassword' ),
+ false,
+ false,
+ 0,
+ $this->getVar( 'wgDBprefix' )
+ );
+ } catch ( DBConnectionError $e ) {
+ return Status::newFatal( 'config-connection-error', $e->getMessage() );
+ }
+ }
+
+ // Validate engines and charsets
+ // This is done pre-submit already so it's just for security
+ $engines = $this->getEngines();
+ if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) {
+ $this->setVar( '_MysqlEngine', reset( $engines ) );
+ }
+ $charsets = $this->getCharsets();
+ if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) {
+ $this->setVar( '_MysqlCharset', reset( $charsets ) );
+ }
+ return Status::newGood();
+ }
+
+ public function preInstall() {
+ # Add our user callback to installSteps, right before the tables are created.
+ $callback = array(
+ 'name' => 'user',
+ 'callback' => array( $this, 'setupUser' ),
+ );
+ $this->parent->addInstallStep( $callback, 'tables' );
+ }
+
+ public function setupDatabase() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+ $dbName = $this->getVar( 'wgDBname' );
+ if( !$conn->selectDB( $dbName ) ) {
+ $conn->query( "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ), __METHOD__ );
+ $conn->selectDB( $dbName );
+ }
+ $this->setupSchemaVars();
+ return $status;
+ }
+
+ public function setupUser() {
+ $dbUser = $this->getVar( 'wgDBuser' );
+ if( $dbUser == $this->getVar( '_InstallUser' ) ) {
+ return Status::newGood();
+ }
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ $this->setupSchemaVars();
+ $dbName = $this->getVar( 'wgDBname' );
+ $this->db->selectDB( $dbName );
+ $server = $this->getVar( 'wgDBserver' );
+ $password = $this->getVar( 'wgDBpassword' );
+ $grantableNames = array();
+
+ if ( $this->getVar( '_CreateDBAccount' ) ) {
+ // Before we blindly try to create a user that already has access,
+ try { // first attempt to connect to the database
+ new DatabaseMysql(
+ $server,
+ $dbUser,
+ $password,
+ false,
+ false,
+ 0,
+ $this->getVar( 'wgDBprefix' )
+ );
+ $grantableNames[] = $this->buildFullUserName( $dbUser, $server );
+ $tryToCreate = false;
+ } catch ( DBConnectionError $e ) {
+ $tryToCreate = true;
+ }
+ } else {
+ $grantableNames[] = $this->buildFullUserName( $dbUser, $server );
+ $tryToCreate = false;
+ }
+
+ if( $tryToCreate ) {
+ $createHostList = array($server,
+ 'localhost',
+ 'localhost.localdomain',
+ '%'
+ );
+
+ $createHostList = array_unique( $createHostList );
+ $escPass = $this->db->addQuotes( $password );
+
+ foreach( $createHostList as $host ) {
+ $fullName = $this->buildFullUserName( $dbUser, $host );
+ if( !$this->userDefinitelyExists( $dbUser, $host ) ) {
+ try{
+ $this->db->begin();
+ $this->db->query( "CREATE USER $fullName IDENTIFIED BY $escPass", __METHOD__ );
+ $this->db->commit();
+ $grantableNames[] = $fullName;
+ } catch( DBQueryError $dqe ) {
+ if( $this->db->lastErrno() == 1396 /* ER_CANNOT_USER */ ) {
+ // User (probably) already exists
+ $this->db->rollback();
+ $status->warning( 'config-install-user-alreadyexists', $dbUser );
+ $grantableNames[] = $fullName;
+ break;
+ } else {
+ // If we couldn't create for some bizzare reason and the
+ // user probably doesn't exist, skip the grant
+ $this->db->rollback();
+ $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getText() );
+ }
+ }
+ } else {
+ $status->warning( 'config-install-user-alreadyexists', $dbUser );
+ $grantableNames[] = $fullName;
+ break;
+ }
+ }
+ }
+
+ // Try to grant to all the users we know exist or we were able to create
+ $escPass = $this->db->addQuotes( $password );
+ $dbAllTables = $this->db->addIdentifierQuotes( $dbName ) . '.*';
+ foreach( $grantableNames as $name ) {
+ try {
+ $this->db->begin();
+ $this->db->query( "GRANT ALL PRIVILEGES ON $dbAllTables TO $name", __METHOD__ );
+ $this->db->commit();
+ } catch( DBQueryError $dqe ) {
+ $this->db->rollback();
+ $status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getText() );
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Return a formal 'User'@'Host' username for use in queries
+ * @param $name String Username, quotes will be added
+ * @param $host String Hostname, quotes will be added
+ * @return String
+ */
+ private function buildFullUserName( $name, $host ) {
+ return $this->db->addQuotes( $name ) . '@' . $this->db->addQuotes( $host );
+ }
+
+ /**
+ * Try to see if the user account exists. Our "superuser" may not have
+ * access to mysql.user, so false means "no" or "maybe"
+ * @param $host String Hostname to check
+ * @param $user String Username to check
+ * @return boolean
+ */
+ private function userDefinitelyExists( $host, $user ) {
+ try {
+ $res = $this->db->selectRow( 'mysql.user', array( 'Host', 'User' ),
+ array( 'Host' => $host, 'User' => $user ), __METHOD__ );
+ return (bool)$res;
+ } catch( DBQueryError $dqe ) {
+ return false;
+ }
+
+ }
+
+ /**
+ * Return any table options to be applied to all tables that don't
+ * override them.
+ *
+ * @return String
+ */
+ protected function getTableOptions() {
+ $options = array();
+ if ( $this->getVar( '_MysqlEngine' ) !== null ) {
+ $options[] = "ENGINE=" . $this->getVar( '_MysqlEngine' );
+ }
+ if ( $this->getVar( '_MysqlCharset' ) !== null ) {
+ $options[] = 'DEFAULT CHARSET=' . $this->getVar( '_MysqlCharset' );
+ }
+ return implode( ', ', $options );
+ }
+
+ /**
+ * Get variables to substitute into tables.sql and the SQL patch files.
+ */
+ public function getSchemaVars() {
+ return array(
+ 'wgDBTableOptions' => $this->getTableOptions(),
+ 'wgDBname' => $this->getVar( 'wgDBname' ),
+ 'wgDBuser' => $this->getVar( 'wgDBuser' ),
+ 'wgDBpassword' => $this->getVar( 'wgDBpassword' ),
+ );
+ }
+
+ public function getLocalSettings() {
+ $dbmysql5 = wfBoolToStr( $this->getVar( 'wgDBmysql5', true ) );
+ $prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) );
+ $tblOpts = LocalSettingsGenerator::escapePhpString( $this->getTableOptions() );
+ return
+"# MySQL specific settings
+\$wgDBprefix = \"{$prefix}\";
+
+# MySQL table options to use during installation or update
+\$wgDBTableOptions = \"{$tblOpts}\";
+
+# Experimental charset support for MySQL 4.1/5.0.
+\$wgDBmysql5 = {$dbmysql5};";
+ }
+}
diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php
new file mode 100644
index 00000000..9bbda5db
--- /dev/null
+++ b/includes/installer/MysqlUpdater.php
@@ -0,0 +1,832 @@
+<?php
+/**
+ * MySQL-specific updater.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Mysql update list and mysql-specific update functions.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class MysqlUpdater extends DatabaseUpdater {
+
+ protected function getCoreUpdateList() {
+ return array(
+ // 1.2
+ array( 'addField', 'ipblocks', 'ipb_id', 'patch-ipblocks.sql' ),
+ 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' ),
+
+ // 1.3
+ array( 'addField', 'user', 'user_real_name', 'patch-user-realname.sql' ),
+ array( 'addTable', 'querycache', 'patch-querycache.sql' ),
+ array( 'addTable', 'objectcache', 'patch-objectcache.sql' ),
+ array( 'addTable', 'categorylinks', 'patch-categorylinks.sql' ),
+ array( 'doOldLinksUpdate' ),
+ array( 'doFixAncientImagelinks' ),
+ array( 'addField', 'recentchanges', 'rc_ip', 'patch-rc_ip.sql' ),
+
+ // 1.4
+ array( 'addIndex', 'image', 'PRIMARY', 'patch-image_name_primary.sql' ),
+ array( 'addField', 'recentchanges', 'rc_id', 'patch-rc_id.sql' ),
+ array( 'addField', 'recentchanges', 'rc_patrolled', 'patch-rc-patrol.sql' ),
+ array( 'addTable', 'logging', 'patch-logging.sql' ),
+ array( 'addField', 'user', 'user_token', 'patch-user_token.sql' ),
+ array( 'addField', 'watchlist', 'wl_notificationtimestamp', 'patch-email-notification.sql' ),
+ array( 'doWatchlistUpdate' ),
+ array( 'dropField', 'user', 'user_emailauthenticationtimestamp', 'patch-email-authentication.sql' ),
+
+ // 1.5
+ array( 'doSchemaRestructuring' ),
+ array( 'addField', 'logging', 'log_params', 'patch-log_params.sql' ),
+ array( 'checkBin', 'logging', 'log_title', 'patch-logging-title.sql', ),
+ array( 'addField', 'archive', 'ar_rev_id', 'patch-archive-rev_id.sql' ),
+ array( 'addField', 'page', 'page_len', 'patch-page_len.sql' ),
+ array( 'dropField', 'revision', 'inverse_timestamp', 'patch-inverse_timestamp.sql' ),
+ array( 'addField', 'revision', 'rev_text_id', 'patch-rev_text_id.sql' ),
+ array( 'addField', 'revision', 'rev_deleted', 'patch-rev_deleted.sql' ),
+ array( 'addField', 'image', 'img_width', 'patch-img_width.sql' ),
+ array( 'addField', 'image', 'img_metadata', 'patch-img_metadata.sql' ),
+ array( 'addField', 'user', 'user_email_token', 'patch-user_email_token.sql' ),
+ array( 'addField', 'archive', 'ar_text_id', 'patch-archive-text_id.sql' ),
+ array( 'doNamespaceSize' ),
+ array( 'addField', 'image', 'img_media_type', 'patch-img_media_type.sql' ),
+ array( 'doPagelinksUpdate' ),
+ array( 'dropField', 'image', 'img_type', 'patch-drop_img_type.sql' ),
+ array( 'doUserUniqueUpdate' ),
+ array( 'doUserGroupsUpdate' ),
+ array( 'addField', 'site_stats', 'ss_total_pages', 'patch-ss_total_articles.sql' ),
+ array( 'addTable', 'user_newtalk', 'patch-usernewtalk2.sql' ),
+ array( 'addTable', 'transcache', 'patch-transcache.sql' ),
+ array( 'addField', 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ),
+ array( 'addTable', 'trackbacks', 'patch-trackbacks.sql' ),
+
+ // 1.6
+ array( 'doWatchlistNull' ),
+ array( 'addIndex', 'logging', 'times', 'patch-logging-times-index.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ),
+ array( 'doPageRandomUpdate' ),
+ array( 'addField', 'user', 'user_registration', 'patch-user_registration.sql' ),
+ array( 'doTemplatelinksUpdate' ),
+ array( 'addTable', 'externallinks', 'patch-externallinks.sql' ),
+ array( 'addTable', 'job', 'patch-job.sql' ),
+ array( 'addField', 'site_stats', 'ss_images', 'patch-ss_images.sql' ),
+ array( 'addTable', 'langlinks', 'patch-langlinks.sql' ),
+ array( 'addTable', 'querycache_info', 'patch-querycacheinfo.sql' ),
+ array( 'addTable', 'filearchive', 'patch-filearchive.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ),
+ array( 'addIndex', 'recentchanges', 'rc_ns_usertext', 'patch-recentchanges-utindex.sql' ),
+ array( 'addIndex', 'recentchanges', 'rc_user_text', 'patch-rc_user_text-index.sql' ),
+
+ // 1.9
+ array( 'addField', 'user', 'user_newpass_time', 'patch-user_newpass_time.sql' ),
+ array( 'addTable', 'redirect', 'patch-redirect.sql' ),
+ array( 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_enable_autoblock', 'patch-ipb_optional_autoblock.sql' ),
+ array( 'doBacklinkingIndicesUpdate' ),
+ array( 'addField', 'recentchanges', 'rc_old_len', 'patch-rc_len.sql' ),
+ array( 'addField', 'user', 'user_editcount', 'patch-user_editcount.sql' ),
+
+ // 1.10
+ array( 'doRestrictionsUpdate' ),
+ array( 'addField', 'logging', 'log_id', 'patch-log_id.sql' ),
+ array( 'addField', 'revision', 'rev_parent_id', 'patch-rev_parent_id.sql' ),
+ array( 'addField', 'page_restrictions', 'pr_id', 'patch-page_restrictions_sortkey.sql' ),
+ array( 'addField', 'revision', 'rev_len', 'patch-rev_len.sql' ),
+ array( 'addField', 'recentchanges', 'rc_deleted', 'patch-rc_deleted.sql' ),
+ array( 'addField', 'logging', 'log_deleted', 'patch-log_deleted.sql' ),
+ array( 'addField', 'archive', 'ar_deleted', 'patch-ar_deleted.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_deleted', 'patch-ipb_deleted.sql' ),
+ array( 'addField', 'filearchive', 'fa_deleted', 'patch-fa_deleted.sql' ),
+ array( 'addField', 'archive', 'ar_len', 'patch-ar_len.sql' ),
+
+ // 1.11
+ array( 'addField', 'ipblocks', 'ipb_block_email', 'patch-ipb_emailban.sql' ),
+ array( 'doCategorylinksIndicesUpdate' ),
+ array( 'addField', 'oldimage', 'oi_metadata', 'patch-oi_metadata.sql' ),
+ array( 'addIndex', 'archive', 'usertext_timestamp', 'patch-archive-user-index.sql' ),
+ array( 'addIndex', 'image', 'img_usertext_timestamp', 'patch-image-user-index.sql' ),
+ array( 'addIndex', 'oldimage', 'oi_usertext_timestamp', 'patch-oldimage-user-index.sql' ),
+ array( 'addField', 'archive', 'ar_page_id', 'patch-archive-page_id.sql' ),
+ array( 'addField', 'image', 'img_sha1', 'patch-img_sha1.sql' ),
+
+ // 1.12
+ array( 'addTable', 'protected_titles', 'patch-protected_titles.sql' ),
+
+ // 1.13
+ array( 'addField', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ),
+ array( 'addTable', 'page_props', 'patch-page_props.sql' ),
+ array( 'addTable', 'updatelog', 'patch-updatelog.sql' ),
+ array( 'addTable', 'category', 'patch-category.sql' ),
+ array( 'doCategoryPopulation' ),
+ array( 'addField', 'archive', 'ar_parent_id', 'patch-ar_parent_id.sql' ),
+ array( 'addField', 'user_newtalk', 'user_last_timestamp', 'patch-user_last_timestamp.sql' ),
+ array( 'doPopulateParentId' ),
+ array( 'checkBin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ),
+ array( 'doMaybeProfilingMemoryUpdate' ),
+ array( 'doFilearchiveIndicesUpdate' ),
+
+ // 1.14
+ array( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
+ array( 'doActiveUsersInit' ),
+ array( 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
+
+ // 1.15
+ array( 'doUniquePlTlIl' ),
+ array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
+ array( 'addTable', 'tag_summary', 'patch-change_tag.sql' ),
+ array( 'addTable', 'valid_tag', 'patch-change_tag.sql' ),
+
+ // 1.16
+ array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
+ array( 'addTable', 'log_search', 'patch-log_search.sql' ),
+ array( 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ),
+ array( 'doLogUsertextPopulation' ), # listed separately from the previous update because 1.16 was released without this update
+ array( 'doLogSearchPopulation' ),
+ array( 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ),
+ array( 'addTable', 'external_user', 'patch-external_user.sql' ),
+ array( 'addIndex', 'log_search', 'ls_field_val', 'patch-log_search-rename-index.sql' ),
+ array( 'addIndex', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ),
+ array( 'addField', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ),
+ array( 'doUpdateTranscacheField' ),
+ array( 'renameEuWikiId' ),
+ array( 'doUpdateMimeMinorField' ),
+ array( 'doPopulateRevLen' ),
+
+ // 1.17
+ array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
+ array( 'addIndex', 'iwlinks', 'iwl_prefix_title_from', 'patch-rename-iwl_prefix.sql' ),
+ array( 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ),
+ array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ),
+ array( 'dropIndex', 'iwlinks', 'iwl_prefix', 'patch-kill-iwl_prefix.sql' ),
+ array( 'dropIndex', 'iwlinks', 'iwl_prefix_from_title', 'patch-kill-iwl_pft.sql' ),
+ array( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
+ array( 'doClFieldsUpdate' ),
+ array( 'doCollationUpdate' ),
+ array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
+ array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
+ array( 'dropIndex', 'archive', 'ar_page_revid', 'patch-archive_kill_ar_page_revid.sql' ),
+ array( 'addIndex', 'archive', 'ar_revid', 'patch-archive_ar_revid.sql' ),
+ array( 'doLangLinksLengthUpdate' ),
+ );
+ }
+
+ /**
+ * 1.4 betas were missing the 'binary' marker from logging.log_title,
+ * which causes a collation mismatch error on joins in MySQL 4.1.
+ *
+ * @param $table String: table name
+ * @param $field String: field name to check
+ * @param $patchFile String: path to the patch to correct the field
+ */
+ protected function checkBin( $table, $field, $patchFile ) {
+ $tableName = $this->db->tableName( $table );
+ $res = $this->db->query( "SELECT $field FROM $tableName LIMIT 0", __METHOD__ );
+ $flags = explode( ' ', mysql_field_flags( $res->result, 0 ) );
+
+ if ( in_array( 'binary', $flags ) ) {
+ $this->output( "...$table table has correct $field encoding.\n" );
+ } else {
+ $this->output( "Fixing $field encoding on $table table... " );
+ $this->applyPatch( $patchFile );
+ $this->output( "done.\n" );
+ }
+ }
+
+ /**
+ * Check whether an index contain a field
+ *
+ * @param $table String: table name
+ * @param $index String: index name to check
+ * @param $field String: field that should be in the index
+ * @return Boolean
+ */
+ protected function indexHasField( $table, $index, $field ) {
+ $info = $this->db->indexInfo( $table, $index, __METHOD__ );
+ if ( $info ) {
+ foreach ( $info as $row ) {
+ if ( $row->Column_name == $field ) {
+ $this->output( "...index $index on table $table includes field $field\n" );
+ return true;
+ }
+ }
+ }
+ $this->output( "...index $index on table $table has no field $field; adding\n" );
+ return false;
+ }
+
+ /**
+ * Check that interwiki table exists; if it doesn't source it
+ */
+ protected function doInterwikiUpdate() {
+ global $IP;
+
+ if ( $this->db->tableExists( "interwiki" ) ) {
+ $this->output( "...already have interwiki table\n" );
+ return;
+ }
+
+ $this->output( 'Creating interwiki table...' );
+ $this->applyPatch( 'patch-interwiki.sql' );
+ $this->output( "ok\n" );
+ $this->output( 'Adding default interwiki definitions...' );
+ $this->applyPatch( "$IP/maintenance/interwiki.sql", true );
+ $this->output( "done.\n" );
+ }
+
+ /**
+ * Check that proper indexes are in place
+ */
+ protected function doIndexUpdate() {
+ $meta = $this->db->fieldInfo( 'recentchanges', 'rc_timestamp' );
+ if ( $meta->isMultipleKey() ) {
+ $this->output( "...indexes seem up to 20031107 standards\n" );
+ return;
+ }
+
+ $this->output( "Updating indexes to 20031107..." );
+ $this->applyPatch( 'patch-indexes.sql', true );
+ $this->output( "done.\n" );
+ }
+
+ protected function doOldLinksUpdate() {
+ $cl = $this->maintenance->runChild( 'ConvertLinks' );
+ $cl->execute();
+ }
+
+ protected function doFixAncientImagelinks() {
+ $info = $this->db->fieldInfo( 'imagelinks', 'il_from' );
+ if ( !$info || $info->type() !== 'string' ) {
+ $this->output( "...il_from OK\n" );
+ return;
+ }
+
+ $this->output( "Fixing ancient broken imagelinks table.\n" );
+ $this->output( "NOTE: you will have to run maintenance/refreshLinks.php after this.\n" );
+ $this->applyPatch( 'patch-fix-il_from.sql' );
+ $this->output( "done.\n" );
+ }
+
+ /**
+ * Check if we need to add talk page rows to the watchlist
+ */
+ function doWatchlistUpdate() {
+ $talk = $this->db->selectField( 'watchlist', 'count(*)', 'wl_namespace & 1', __METHOD__ );
+ $nontalk = $this->db->selectField( 'watchlist', 'count(*)', 'NOT (wl_namespace & 1)', __METHOD__ );
+ if ( $talk == $nontalk ) {
+ $this->output( "...watchlist talk page rows already present\n" );
+ return;
+ }
+
+ $this->output( "Adding missing watchlist talk page rows... " );
+ $this->db->insertSelect( 'watchlist', 'watchlist',
+ array(
+ 'wl_user' => 'wl_user',
+ 'wl_namespace' => 'wl_namespace | 1',
+ 'wl_title' => 'wl_title',
+ 'wl_notificationtimestamp' => 'wl_notificationtimestamp'
+ ), array( 'NOT (wl_namespace & 1)' ), __METHOD__, 'IGNORE' );
+ $this->output( "done.\n" );
+ }
+
+ function doSchemaRestructuring() {
+ if ( $this->db->tableExists( 'page' ) ) {
+ $this->output( "...page table already exists.\n" );
+ return;
+ }
+
+ $this->output( "...converting from cur/old to page/revision/text DB structure.\n" );
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "......checking for duplicate entries.\n" );
+
+ list ( $cur, $old, $page, $revision, $text ) = $this->db->tableNamesN( 'cur', 'old', 'page', 'revision', 'text' );
+
+ $rows = $this->db->query( "SELECT cur_title, cur_namespace, COUNT(cur_namespace) AS c
+ FROM $cur GROUP BY cur_title, cur_namespace HAVING c>1", __METHOD__ );
+
+ if ( $rows->numRows() > 0 ) {
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "......<b>Found duplicate entries</b>\n" );
+ $this->output( sprintf( "<b> %-60s %3s %5s</b>\n", 'Title', 'NS', 'Count' ) );
+ $duplicate = array();
+ foreach ( $rows as $row ) {
+ if ( ! isset( $duplicate[$row->cur_namespace] ) ) {
+ $duplicate[$row->cur_namespace] = array();
+ }
+ $duplicate[$row->cur_namespace][] = $row->cur_title;
+ $this->output( sprintf( " %-60s %3s %5s\n", $row->cur_title, $row->cur_namespace, $row->c ) );
+ }
+ $sql = "SELECT cur_title, cur_namespace, cur_id, cur_timestamp FROM $cur WHERE ";
+ $firstCond = true;
+ foreach ( $duplicate as $ns => $titles ) {
+ if ( $firstCond ) {
+ $firstCond = false;
+ } else {
+ $sql .= ' OR ';
+ }
+ $sql .= "( cur_namespace = {$ns} AND cur_title in (";
+ $first = true;
+ foreach ( $titles as $t ) {
+ if ( $first ) {
+ $sql .= $this->db->addQuotes( $t );
+ $first = false;
+ } else {
+ $sql .= ', ' . $this->db->addQuotes( $t );
+ }
+ }
+ $sql .= ") ) \n";
+ }
+ # By sorting descending, the most recent entry will be the first in the list.
+ # All following entries will be deleted by the next while-loop.
+ $sql .= 'ORDER BY cur_namespace, cur_title, cur_timestamp DESC';
+
+ $rows = $this->db->query( $sql, __METHOD__ );
+
+ $prev_title = $prev_namespace = false;
+ $deleteId = array();
+
+ foreach ( $rows as $row ) {
+ if ( $prev_title == $row->cur_title && $prev_namespace == $row->cur_namespace ) {
+ $deleteId[] = $row->cur_id;
+ }
+ $prev_title = $row->cur_title;
+ $prev_namespace = $row->cur_namespace;
+ }
+ $sql = "DELETE FROM $cur WHERE cur_id IN ( " . join( ',', $deleteId ) . ')';
+ $this->db->query( $sql, __METHOD__ );
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "......<b>Deleted</b> " . $this->db->affectedRows() . " records.\n" );
+ }
+
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "......Creating tables.\n" );
+ $this->db->query( "CREATE TABLE $page (
+ page_id int(8) unsigned NOT NULL auto_increment,
+ 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,
+ page_touched char(14) binary NOT NULL default '',
+ page_latest int(8) unsigned NOT NULL,
+ page_len int(8) unsigned NOT NULL,
+
+ PRIMARY KEY page_id (page_id),
+ UNIQUE INDEX name_title (page_namespace,page_title),
+ INDEX (page_random),
+ INDEX (page_len)
+ ) ENGINE=InnoDB", __METHOD__ );
+ $this->db->query( "CREATE TABLE $revision (
+ rev_id int(8) unsigned NOT NULL auto_increment,
+ rev_page int(8) unsigned NOT NULL,
+ rev_comment tinyblob NOT NULL,
+ rev_user int(5) unsigned NOT NULL default '0',
+ rev_user_text varchar(255) binary NOT NULL default '',
+ rev_timestamp char(14) binary NOT NULL default '',
+ rev_minor_edit tinyint(1) unsigned NOT NULL default '0',
+ rev_deleted tinyint(1) unsigned NOT NULL default '0',
+ rev_len int(8) unsigned,
+ rev_parent_id int(8) unsigned default NULL,
+ PRIMARY KEY rev_page_id (rev_page, rev_id),
+ UNIQUE INDEX rev_id (rev_id),
+ INDEX rev_timestamp (rev_timestamp),
+ INDEX page_timestamp (rev_page,rev_timestamp),
+ INDEX user_timestamp (rev_user,rev_timestamp),
+ INDEX usertext_timestamp (rev_user_text,rev_timestamp)
+ ) ENGINE=InnoDB", __METHOD__ );
+
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "......Locking tables.\n" );
+ $this->db->query( "LOCK TABLES $page WRITE, $revision WRITE, $old WRITE, $cur WRITE", __METHOD__ );
+
+ $maxold = intval( $this->db->selectField( 'old', 'max(old_id)', '', __METHOD__ ) );
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "......maxold is {$maxold}\n" );
+
+ $this->output( wfTimestamp( TS_DB ) );
+ global $wgLegacySchemaConversion;
+ if ( $wgLegacySchemaConversion ) {
+ // Create HistoryBlobCurStub entries.
+ // Text will be pulled from the leftover 'cur' table at runtime.
+ $this->output( "......Moving metadata from cur; using blob references to text in cur table.\n" );
+ $cur_text = "concat('O:18:\"historyblobcurstub\":1:{s:6:\"mCurId\";i:',cur_id,';}')";
+ $cur_flags = "'object'";
+ } else {
+ // Copy all cur text in immediately: this may take longer but avoids
+ // having to keep an extra table around.
+ $this->output( "......Moving text from cur.\n" );
+ $cur_text = 'cur_text';
+ $cur_flags = "''";
+ }
+ $this->db->query( "INSERT INTO $old (old_namespace, old_title, old_text, old_comment, old_user, old_user_text,
+ old_timestamp, old_minor_edit, old_flags)
+ SELECT cur_namespace, cur_title, $cur_text, cur_comment, cur_user, cur_user_text, cur_timestamp, cur_minor_edit, $cur_flags
+ FROM $cur", __METHOD__ );
+
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "......Setting up revision table.\n" );
+ $this->db->query( "INSERT INTO $revision (rev_id, rev_page, rev_comment, rev_user, rev_user_text, rev_timestamp,
+ rev_minor_edit)
+ SELECT old_id, cur_id, old_comment, old_user, old_user_text,
+ old_timestamp, old_minor_edit
+ FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title", __METHOD__ );
+
+ $this->output( wfTimestamp( TS_DB ) );
+ $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_touched, page_latest, page_len)
+ SELECT cur_id, cur_namespace, cur_title, cur_restrictions, cur_counter, 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}", __METHOD__ );
+
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "......Unlocking tables.\n" );
+ $this->db->query( "UNLOCK TABLES", __METHOD__ );
+
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "......Renaming old.\n" );
+ $this->db->query( "ALTER TABLE $old RENAME TO $text", __METHOD__ );
+
+ $this->output( wfTimestamp( TS_DB ) );
+ $this->output( "...done.\n" );
+ }
+
+ protected function doNamespaceSize() {
+ $tables = array(
+ 'page' => 'page',
+ 'archive' => 'ar',
+ 'recentchanges' => 'rc',
+ 'watchlist' => 'wl',
+ 'querycache' => 'qc',
+ 'logging' => 'log',
+ );
+ foreach ( $tables as $table => $prefix ) {
+ $field = $prefix . '_namespace';
+
+ $tablename = $this->db->tableName( $table );
+ $result = $this->db->query( "SHOW COLUMNS FROM $tablename LIKE '$field'", __METHOD__ );
+ $info = $this->db->fetchObject( $result );
+
+ if ( substr( $info->Type, 0, 3 ) == 'int' ) {
+ $this->output( "...$field is already a full int ($info->Type).\n" );
+ } else {
+ $this->output( "Promoting $field from $info->Type to int... " );
+ $this->db->query( "ALTER TABLE $tablename MODIFY $field int NOT NULL", __METHOD__ );
+ $this->output( "done.\n" );
+ }
+ }
+ }
+
+ protected function doPagelinksUpdate() {
+ if ( $this->db->tableExists( 'pagelinks' ) ) {
+ $this->output( "...already have pagelinks table.\n" );
+ return;
+ }
+
+ $this->output( "Converting links and brokenlinks tables to pagelinks... " );
+ $this->applyPatch( 'patch-pagelinks.sql' );
+ $this->output( "done.\n" );
+
+ global $wgContLang;
+ foreach ( MWNamespace::getCanonicalNamespaces() as $ns => $name ) {
+ if ( $ns == 0 ) {
+ continue;
+ }
+
+ $this->output( "Cleaning up broken links for namespace $ns... " );
+
+ $pagelinks = $this->db->tableName( 'pagelinks' );
+ $name = $wgContLang->getNsText( $ns );
+ $prefix = $this->db->strencode( $name );
+ $likeprefix = str_replace( '_', '\\_', $prefix );
+
+ $sql = "UPDATE $pagelinks
+ SET pl_namespace=$ns,
+ pl_title=TRIM(LEADING '$prefix:' FROM pl_title)
+ WHERE pl_namespace=0
+ AND pl_title LIKE '$likeprefix:%'";
+
+ $this->db->query( $sql, __METHOD__ );
+ $this->output( "done.\n" );
+ }
+ }
+
+ protected function doUserUniqueUpdate() {
+ $duper = new UserDupes( $this->db, array( $this, 'output' ) );
+ if ( $duper->hasUniqueIndex() ) {
+ $this->output( "...already have unique user_name index.\n" );
+ return;
+ }
+
+ if ( !$duper->clearDupes() ) {
+ $this->output( "WARNING: This next step will probably fail due to unfixed duplicates...\n" );
+ }
+ $this->output( "Adding unique index on user_name... " );
+ $this->applyPatch( 'patch-user_nameindex.sql' );
+ $this->output( "done.\n" );
+ }
+
+ protected function doUserGroupsUpdate() {
+ if ( $this->db->tableExists( 'user_groups' ) ) {
+ $info = $this->db->fieldInfo( 'user_groups', 'ug_group' );
+ if ( $info->type() == 'int' ) {
+ $oldug = $this->db->tableName( 'user_groups' );
+ $newug = $this->db->tableName( 'user_groups_bogus' );
+ $this->output( "user_groups table exists but is in bogus intermediate format. Renaming to $newug... " );
+ $this->db->query( "ALTER TABLE $oldug RENAME TO $newug", __METHOD__ );
+ $this->output( "ok\n" );
+
+ $this->output( "Re-adding fresh user_groups table... " );
+ $this->applyPatch( 'patch-user_groups.sql' );
+ $this->output( "ok\n" );
+
+ $this->output( "***\n" );
+ $this->output( "*** WARNING: You will need to manually fix up user permissions in the user_groups\n" );
+ $this->output( "*** table. Old 1.5 alpha versions did some pretty funky stuff...\n" );
+ $this->output( "***\n" );
+ } else {
+ $this->output( "...user_groups table exists and is in current format.\n" );
+ }
+ return;
+ }
+
+ $this->output( "Adding user_groups table... " );
+ $this->applyPatch( 'patch-user_groups.sql' );
+ $this->output( "ok\n" );
+
+ if ( !$this->db->tableExists( 'user_rights' ) ) {
+ if ( $this->db->fieldExists( 'user', 'user_rights' ) ) {
+ $this->output( "Upgrading from a 1.3 or older database? Breaking out user_rights for conversion..." );
+ $this->db->applyPatch( 'patch-user_rights.sql' );
+ $this->output( "ok\n" );
+ } else {
+ $this->output( "*** WARNING: couldn't locate user_rights table or field for upgrade.\n" );
+ $this->output( "*** You may need to manually configure some sysops by manipulating\n" );
+ $this->output( "*** the user_groups table.\n" );
+ return;
+ }
+ }
+
+ $this->output( "Converting user_rights table to user_groups... " );
+ $result = $this->db->select( 'user_rights',
+ array( 'ur_user', 'ur_rights' ),
+ array( "ur_rights != ''" ),
+ __METHOD__ );
+
+ foreach ( $result as $row ) {
+ $groups = array_unique(
+ array_map( 'trim',
+ explode( ',', $row->ur_rights ) ) );
+
+ foreach ( $groups as $group ) {
+ $this->db->insert( 'user_groups',
+ array(
+ 'ug_user' => $row->ur_user,
+ 'ug_group' => $group ),
+ __METHOD__ );
+ }
+ }
+ $this->output( "done.\n" );
+ }
+
+ /**
+ * Make sure wl_notificationtimestamp can be NULL,
+ * and update old broken items.
+ */
+ protected function doWatchlistNull() {
+ $info = $this->db->fieldInfo( 'watchlist', 'wl_notificationtimestamp' );
+ if ( $info->isNullable() ) {
+ $this->output( "...wl_notificationtimestamp is already nullable.\n" );
+ return;
+ }
+
+ $this->output( "Making wl_notificationtimestamp nullable... " );
+ $this->applyPatch( 'patch-watchlist-null.sql' );
+ $this->output( "done.\n" );
+ }
+
+ /**
+ * Set page_random field to a random value where it is equals to 0.
+ *
+ * @see bug 3946
+ */
+ protected function doPageRandomUpdate() {
+ $page = $this->db->tableName( 'page' );
+ $this->db->query( "UPDATE $page SET page_random = RAND() WHERE page_random = 0", __METHOD__ );
+ $rows = $this->db->affectedRows();
+
+ if( $rows ) {
+ $this->output( "Set page_random to a random value on $rows rows where it was set to 0\n" );
+ } else {
+ $this->output( "...no page_random rows needed to be set\n" );
+ }
+ }
+
+ protected function doTemplatelinksUpdate() {
+ if ( $this->db->tableExists( 'templatelinks' ) ) {
+ $this->output( "...templatelinks table already exists\n" );
+ return;
+ }
+
+ $this->output( "Creating templatelinks table...\n" );
+ $this->applyPatch( 'patch-templatelinks.sql' );
+ $this->output( "Populating...\n" );
+ if ( wfGetLB()->getServerCount() > 1 ) {
+ // Slow, replication-friendly update
+ $res = $this->db->select( 'pagelinks', array( 'pl_from', 'pl_namespace', 'pl_title' ),
+ array( 'pl_namespace' => NS_TEMPLATE ), __METHOD__ );
+ $count = 0;
+ foreach ( $res as $row ) {
+ $count = ( $count + 1 ) % 100;
+ if ( $count == 0 ) {
+ wfWaitForSlaves( 10 );
+ }
+ $this->db->insert( 'templatelinks',
+ array(
+ 'tl_from' => $row->pl_from,
+ 'tl_namespace' => $row->pl_namespace,
+ 'tl_title' => $row->pl_title,
+ ), __METHOD__
+ );
+
+ }
+ } else {
+ // Fast update
+ $this->db->insertSelect( 'templatelinks', 'pagelinks',
+ array(
+ 'tl_from' => 'pl_from',
+ 'tl_namespace' => 'pl_namespace',
+ 'tl_title' => 'pl_title'
+ ), array(
+ 'pl_namespace' => 10
+ ), __METHOD__
+ );
+ }
+ $this->output( "Done. Please run maintenance/refreshLinks.php for a more thorough templatelinks update.\n" );
+ }
+
+ protected function doBacklinkingIndicesUpdate() {
+ if ( !$this->indexHasField( 'pagelinks', 'pl_namespace', 'pl_from' ) ||
+ !$this->indexHasField( 'templatelinks', 'tl_namespace', 'tl_from' ) ||
+ !$this->indexHasField( 'imagelinks', 'il_to', 'il_from' ) )
+ {
+ $this->applyPatch( 'patch-backlinkindexes.sql' );
+ $this->output( "...backlinking indices updated\n" );
+ }
+ }
+
+ /**
+ * Adding page_restrictions table, obsoleting page.page_restrictions.
+ * Migrating old restrictions to new table
+ * -- Andrew Garrett, January 2007.
+ */
+ protected function doRestrictionsUpdate() {
+ if ( $this->db->tableExists( 'page_restrictions' ) ) {
+ $this->output( "...page_restrictions table already exists.\n" );
+ return;
+ }
+
+ $this->output( "Creating page_restrictions table..." );
+ $this->applyPatch( 'patch-page_restrictions.sql' );
+ $this->applyPatch( 'patch-page_restrictions_sortkey.sql' );
+ $this->output( "done.\n" );
+
+ $this->output( "Migrating old restrictions to new table...\n" );
+ $task = $this->maintenance->runChild( 'UpdateRestrictions' );
+ $task->execute();
+ }
+
+ protected function doCategorylinksIndicesUpdate() {
+ if ( !$this->indexHasField( 'categorylinks', 'cl_sortkey', 'cl_from' ) ) {
+ $this->applyPatch( 'patch-categorylinksindex.sql' );
+ $this->output( "...categorylinks indices updated\n" );
+ }
+ }
+
+ protected function doCategoryPopulation() {
+ if ( $this->updateRowExists( 'populate category' ) ) {
+ $this->output( "...category table already populated.\n" );
+ return;
+ }
+
+ $this->output(
+ "Populating category table, printing progress markers. " .
+ "For large databases, you\n" .
+ "may want to hit Ctrl-C and do this manually with maintenance/\n" .
+ "populateCategory.php.\n"
+ );
+ $task = $this->maintenance->runChild( 'PopulateCategory' );
+ $task->execute();
+ $this->output( "Done populating category table.\n" );
+ }
+
+ protected function doPopulateParentId() {
+ if ( $this->updateRowExists( 'populate rev_parent_id' ) ) {
+ $this->output( "...rev_parent_id column already populated.\n" );
+ return;
+ }
+
+ $task = $this->maintenance->runChild( 'PopulateParentId' );
+ $task->execute();
+ }
+
+ protected function doMaybeProfilingMemoryUpdate() {
+ if ( !$this->db->tableExists( 'profiling' ) ) {
+ // Simply ignore
+ } elseif ( $this->db->fieldExists( 'profiling', 'pf_memory' ) ) {
+ $this->output( "...profiling table has pf_memory field.\n" );
+ } else {
+ $this->output( "Adding pf_memory field to table profiling..." );
+ $this->applyPatch( 'patch-profiling-memory.sql' );
+ $this->output( "done.\n" );
+ }
+ }
+
+ protected function doFilearchiveIndicesUpdate() {
+ $info = $this->db->indexInfo( 'filearchive', 'fa_user_timestamp', __METHOD__ );
+ if ( !$info ) {
+ $this->output( "Updating filearchive indices..." );
+ $this->applyPatch( 'patch-filearchive-user-index.sql' );
+ $this->output( "done.\n" );
+ }
+ }
+
+ protected function doUniquePlTlIl() {
+ $info = $this->db->indexInfo( 'pagelinks', 'pl_namespace' );
+ if ( is_array( $info ) && !$info[0]->Non_unique ) {
+ $this->output( "...pl_namespace, tl_namespace, il_to indices are already UNIQUE.\n" );
+ return;
+ }
+
+ $this->output( "Making pl_namespace, tl_namespace and il_to indices UNIQUE... " );
+ $this->applyPatch( 'patch-pl-tl-il-unique.sql' );
+ $this->output( "done.\n" );
+ }
+
+ protected function renameEuWikiId() {
+ if ( $this->db->fieldExists( 'external_user', 'eu_local_id' ) ) {
+ $this->output( "...eu_wiki_id already renamed to eu_local_id.\n" );
+ return;
+ }
+
+ $this->output( "Renaming eu_wiki_id -> eu_local_id... " );
+ $this->applyPatch( 'patch-eu_local_id.sql' );
+ $this->output( "done.\n" );
+ }
+
+ protected function doUpdateMimeMinorField() {
+ if ( $this->updateRowExists( 'mime_minor_length' ) ) {
+ $this->output( "...*_mime_minor fields are already long enough.\n" );
+ return;
+ }
+
+ $this->output( "Altering all *_mime_minor fields to 100 bytes in size ... " );
+ $this->applyPatch( 'patch-mime_minor_length.sql' );
+ $this->output( "done.\n" );
+ }
+
+ protected function doPopulateRevLen() {
+ if ( $this->updateRowExists( 'populate rev_len' ) ) {
+ $this->output( "...rev_len column already populated.\n" );
+ return;
+ }
+
+ $task = $this->maintenance->runChild( 'PopulateRevisionLength' );
+ $task->execute();
+ }
+
+ protected function doClFieldsUpdate() {
+ if ( $this->updateRowExists( 'cl_fields_update' ) ) {
+ $this->output( "...categorylinks up-to-date.\n" );
+ return;
+ }
+
+ $this->output( 'Updating categorylinks (again)...' );
+ $this->applyPatch( 'patch-categorylinks-better-collation2.sql' );
+ $this->output( "done.\n" );
+ }
+
+ protected function doLangLinksLengthUpdate() {
+ $langlinks = $this->db->tableName( 'langlinks' );
+ $res = $this->db->query( "SHOW COLUMNS FROM $langlinks LIKE 'll_lang'" );
+ $row = $this->db->fetchObject( $res );
+
+ if ( $row && $row->Type == "varbinary(10)" ) {
+ $this->output( 'Updating length of ll_lang in langlinks...' );
+ $this->applyPatch( 'patch-langlinks-ll_lang-20.sql' );
+ $this->output( "done.\n" );
+ } else {
+ $this->output( "...ll_lang is up-to-date.\n" );
+ }
+ }
+}
diff --git a/includes/installer/OracleInstaller.php b/includes/installer/OracleInstaller.php
new file mode 100644
index 00000000..8c3e40e1
--- /dev/null
+++ b/includes/installer/OracleInstaller.php
@@ -0,0 +1,279 @@
+<?php
+/**
+ * Oracle-specific installer.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for setting up the MediaWiki database using Oracle.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class OracleInstaller extends DatabaseInstaller {
+
+ protected $globalNames = array(
+ 'wgDBserver',
+ 'wgDBname',
+ 'wgDBuser',
+ 'wgDBpassword',
+ 'wgDBprefix',
+ );
+
+ protected $internalDefaults = array(
+ '_OracleDefTS' => 'USERS',
+ '_OracleTempTS' => 'TEMP',
+ '_InstallUser' => 'SYSDBA',
+ );
+
+ public $minimumVersion = '9.0.1'; // 9iR1
+
+ protected $connError = null;
+
+ public function getName() {
+ return 'oracle';
+ }
+
+ public function isCompiled() {
+ return self::checkExtension( 'oci8' );
+ }
+
+ public function getConnectForm() {
+ if ( $this->getVar( 'wgDBserver' ) == 'localhost' ) {
+ $this->parent->setVar( 'wgDBserver', '' );
+ }
+ return
+ $this->getTextBox( 'wgDBserver', 'config-db-host-oracle', array(), $this->parent->getHelpBox( 'config-db-host-oracle-help' ) ) .
+ Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) .
+ $this->getTextBox( 'wgDBprefix', 'config-db-prefix' ) .
+ $this->getTextBox( '_OracleDefTS', 'config-oracle-def-ts' ) .
+ $this->getTextBox( '_OracleTempTS', 'config-oracle-temp-ts', array(), $this->parent->getHelpBox( 'config-db-oracle-help' ) ) .
+ Html::closeElement( 'fieldset' ) .
+ $this->parent->getWarningBox( wfMsg( 'config-db-account-oracle-warn' ) ).
+ $this->getInstallUserBox().
+ $this->getWebUserBox();
+ }
+
+ public function submitInstallUserBox() {
+ parent::submitInstallUserBox();
+ $this->parent->setVar( '_InstallDBname', $this->getVar( '_InstallUser' ) );
+ return Status::newGood();
+ }
+
+ public function submitConnectForm() {
+ // Get variables from the request
+ $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBprefix', 'wgDBuser', 'wgDBpassword' ) );
+ $this->parent->setVar( 'wgDBname', $this->getVar( 'wgDBuser' ) );
+
+ // Validate them
+ $status = Status::newGood();
+ if ( !strlen( $newValues['wgDBserver'] ) ) {
+ $status->fatal( 'config-missing-db-server-oracle' );
+ } elseif ( !preg_match( '/^[a-zA-Z0-9_\.]+$/', $newValues['wgDBserver'] ) ) {
+ $status->fatal( 'config-invalid-db-server-oracle', $newValues['wgDBserver'] );
+ }
+ if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBprefix'] ) ) {
+ $status->fatal( 'config-invalid-schema', $newValues['wgDBprefix'] );
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ // Submit user box
+ $status = $this->submitInstallUserBox();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ // Try to connect trough multiple scenarios
+ // Scenario 1: Install with a manually created account
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ if ( $this->connError == 28009 ) {
+ // _InstallUser seems to be a SYSDBA
+ // Scenario 2: Create user with SYSDBA and install with new user
+ $status = $this->submitWebUserBox();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $status = $this->openSYSDBAConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ if ( !$this->getVar( '_CreateDBAccount' ) ) {
+ $status->fatal('config-db-sys-create-oracle');
+ }
+ } else {
+ return $status;
+ }
+ } else {
+ // check for web user credentials
+ // Scenario 3: Install with a priviliged user but use a restricted user
+ $statusIS3 = $this->submitWebUserBox();
+ if ( !$statusIS3->isOK() ) {
+ return $statusIS3;
+ }
+ }
+ $conn = $status->value;
+
+ // Check version
+ $version = $conn->getServerVersion();
+ if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
+ return Status::newFatal( 'config-oracle-old', $this->minimumVersion, $version );
+ }
+
+ return $status;
+ }
+
+ public function openConnection() {
+ $status = Status::newGood();
+ try {
+ $db = new DatabaseOracle(
+ $this->getVar( 'wgDBserver' ),
+ $this->getVar( '_InstallUser' ),
+ $this->getVar( '_InstallPassword' ),
+ $this->getVar( '_InstallDBname' ),
+ 0,
+ $this->getVar( 'wgDBprefix' )
+ );
+ $status->value = $db;
+ } catch ( DBConnectionError $e ) {
+ $this->connError = $e->db->lastErrno();
+ $status->fatal( 'config-connection-error', $e->getMessage() );
+ }
+ return $status;
+ }
+
+ public function openSYSDBAConnection() {
+ $status = Status::newGood();
+ try {
+ $db = new DatabaseOracle(
+ $this->getVar( 'wgDBserver' ),
+ $this->getVar( '_InstallUser' ),
+ $this->getVar( '_InstallPassword' ),
+ $this->getVar( '_InstallDBname' ),
+ DBO_SYSDBA,
+ $this->getVar( 'wgDBprefix' )
+ );
+ $status->value = $db;
+ } catch ( DBConnectionError $e ) {
+ $this->connError = $e->db->lastErrno();
+ $status->fatal( 'config-connection-error', $e->getMessage() );
+ }
+ return $status;
+ }
+
+ public function needsUpgrade() {
+ $tempDBname = $this->getVar( 'wgDBname' );
+ $this->parent->setVar( 'wgDBname', $this->getVar( 'wgDBuser' ) );
+ $retVal = parent::needsUpgrade();
+ $this->parent->setVar( 'wgDBname', $tempDBname );
+ return $retVal;
+ }
+
+ public function preInstall() {
+ # Add our user callback to installSteps, right before the tables are created.
+ $callback = array(
+ 'name' => 'user',
+ 'callback' => array( $this, 'setupUser' )
+ );
+ $this->parent->addInstallStep( $callback, 'database' );
+ }
+
+
+ public function setupDatabase() {
+ $status = Status::newGood();
+ return $status;
+ }
+
+ public function setupUser() {
+ global $IP;
+
+ if ( !$this->getVar( '_CreateDBAccount' ) ) {
+ return Status::newGood();
+ }
+
+ // normaly only SYSDBA users can create accounts
+ $status = $this->openSYSDBAConnection();
+ if ( !$status->isOK() ) {
+ if ( $this->connError == 1031 ) {
+ // insufficient privileges (looks like a normal user)
+ $status = $this->openConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ } else {
+ return $status;
+ }
+ }
+ $this->db = $status->value;
+ $this->setupSchemaVars();
+
+ if ( !$this->db->selectDB( $this->getVar( 'wgDBuser' ) ) ) {
+ $this->db->setFlag( DBO_DDLMODE );
+ $error = $this->db->sourceFile( "$IP/maintenance/oracle/user.sql" );
+ if ( $error !== true || !$this->db->selectDB( $this->getVar( 'wgDBuser' ) ) ) {
+ $status->fatal( 'config-install-user-failed', $this->getVar( 'wgDBuser' ), $error );
+ }
+ } elseif ( $this->db->getFlag( DBO_SYSDBA ) ) {
+ $status->fatal( 'config-db-sys-user-exists-oracle', $this->getVar( 'wgDBuser' ) );
+ }
+
+ if ($status->isOK()) {
+ // user created or already existing, switching back to a normal connection
+ // as the new user has all needed privileges to setup the rest of the schema
+ // i will be using that user as _InstallUser from this point on
+ $this->parent->setVar( '_InstallUser', $this->getVar( 'wgDBuser' ) );
+ $this->parent->setVar( '_InstallPassword', $this->getVar( 'wgDBpassword' ) );
+ $this->parent->setVar( '_InstallDBname', $this->getVar( 'wgDBuser' ) );
+ $status = $this->getConnection();
+ }
+
+ return $status;
+ }
+
+ /**
+ * Overload: after this action field info table has to be rebuilt
+ */
+ public function createTables() {
+ $this->setupSchemaVars();
+ $this->db->selectDB( $this->getVar( 'wgDBuser' ) );
+ $this->db->setFlag( DBO_DDLMODE );
+ $status = parent::createTables();
+ $this->db->clearFlag( DBO_DDLMODE );
+
+ $this->db->query( 'BEGIN fill_wiki_info; END;' );
+
+ return $status;
+ }
+
+ public function getSchemaVars() {
+ $varNames = array(
+ # These variables are used by maintenance/oracle/user.sql
+ '_OracleDefTS',
+ '_OracleTempTS',
+ 'wgDBuser',
+ 'wgDBpassword',
+
+ # These are used by tables.sql
+ 'wgDBprefix',
+ );
+ $vars = array();
+ foreach ( $varNames as $name ) {
+ $vars[$name] = $this->getVar( $name );
+ }
+ return $vars;
+ }
+
+ public function getLocalSettings() {
+ $prefix = $this->getVar( 'wgDBprefix' );
+ return
+"# Oracle specific settings
+\$wgDBprefix = \"{$prefix}\";
+";
+ }
+
+}
diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php
new file mode 100644
index 00000000..4d85924e
--- /dev/null
+++ b/includes/installer/OracleUpdater.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Oracle-specific updater.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for handling updates to Oracle databases.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class OracleUpdater extends DatabaseUpdater {
+ protected function getCoreUpdateList() {
+ return array(
+ // 1.16
+ array( 'doNamespaceDefaults' ),
+ array( 'doFKRenameDeferr' ),
+ array( 'doFunctions17' ),
+ array( 'doSchemaUpgrade17' ),
+ array( 'doInsertPage0' ),
+ );
+ }
+
+ /**
+ * MySQL uses datatype defaults for NULL inserted into NOT NULL fields
+ * In namespace case that results into insert of 0 which is default namespace
+ * Oracle inserts NULL, so namespace fields should have a default value
+ */
+ protected function doNamespaceDefaults() {
+ $this->output( "Altering namespace fields with default value ... " );
+ $meta = $this->db->fieldInfo( 'page', 'page_namespace' );
+ if ( $meta->defaultValue() != null ) {
+ $this->output( "defaults seem to present on namespace fields\n" );
+ return;
+ }
+
+ $this->applyPatch( 'patch_namespace_defaults.sql', false );
+ $this->output( "ok\n" );
+ }
+
+ /**
+ * Uniform FK names + deferrable state
+ */
+ protected function doFKRenameDeferr() {
+ $this->output( "Altering foreign keys ... " );
+ $meta = $this->db->query( 'SELECT COUNT(*) cnt FROM user_constraints WHERE constraint_type = \'R\' AND deferrable = \'DEFERRABLE\'' );
+ $row = $meta->fetchRow();
+ if ( $row && $row['cnt'] > 0 ) {
+ $this->output( "at least one FK is deferrable, considering up to date\n" );
+ return;
+ }
+
+ $this->applyPatch( 'patch_fk_rename_deferred.sql', false );
+ $this->output( "ok\n" );
+ }
+
+ /**
+ * Recreate functions to 17 schema layout
+ */
+ protected function doFunctions17() {
+ $this->output( "Recreating functions ... " );
+ $this->applyPatch( 'patch_create_17_functions.sql', false );
+ $this->output( "ok\n" );
+ }
+
+ /**
+ * Schema upgrade 16->17
+ * there are no incremental patches prior to this
+ */
+ protected function doSchemaUpgrade17() {
+ $this->output( "Updating schema to 17 ... " );
+ // check if iwlinks table exists which was added in 1.17
+ if ( $this->db->tableExists( $this->db->tableName( 'iwlinks' ) ) ) {
+ $this->output( "schema seem to be up to date.\n" );
+ return;
+ }
+ $this->applyPatch( 'patch_16_17_schema_changes.sql', false );
+ $this->output( "ok\n" );
+ }
+
+ /**
+ * Insert page (page_id = 0) to prevent FK constraint violation
+ */
+ protected function doInsertPage0() {
+ $this->output( "Inserting page 0 if missing ... " );
+ $row = array(
+ 'page_id' => 0,
+ 'page_namespace' => 0,
+ 'page_title' => ' ',
+ 'page_counter' => 0,
+ 'page_is_redirect' => 0,
+ 'page_is_new' => 0,
+ 'page_random' => 0,
+ 'page_touched' => $this->db->timestamp(),
+ 'page_latest' => 0,
+ 'page_len' => 0
+ );
+ $this->db->insert( 'page', $row, 'OracleUpdater:doInserPage0', array( 'IGNORE' ) );
+ $this->output( "ok\n" );
+ }
+
+ /**
+ * Overload: after this action field info table has to be rebuilt
+ */
+ public function doUpdates( $what = array( 'core', 'extensions', 'purge' ) ) {
+ parent::doUpdates( $what );
+
+ $this->db->query( 'BEGIN fill_wiki_info; END;' );
+ }
+
+}
diff --git a/includes/installer/PhpBugTests.php b/includes/installer/PhpBugTests.php
new file mode 100644
index 00000000..9cafd150
--- /dev/null
+++ b/includes/installer/PhpBugTests.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Classes for self-contained tests for known bugs in PHP.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @defgroup PHPBugTests
+ * @ingroup PHPBugTests
+ */
+
+/**
+ * Test for PHP+libxml2 bug which breaks XML input subtly with certain versions.
+ * Known fixed with PHP 5.2.9 + libxml2-2.7.3
+ * @see http://bugs.php.net/bug.php?id=45996
+ * @ingroup PHPBugTests
+ */
+class PhpXmlBugTester {
+ private $parsedData = '';
+ public $ok = false;
+ public function __construct() {
+ $charData = '<b>c</b>';
+ $xml = '<a>' . htmlspecialchars( $charData ) . '</a>';
+
+ $parser = xml_parser_create();
+ xml_set_character_data_handler( $parser, array( $this, 'chardata' ) );
+ $parsedOk = xml_parse( $parser, $xml, true );
+ $this->ok = $parsedOk && ( $this->parsedData == $charData );
+ }
+ public function chardata( $parser, $data ) {
+ $this->parsedData .= $data;
+ }
+}
+
+/**
+ * Test for PHP bug #50394 (PHP 5.3.x conversion to null only, not 5.2.x)
+ * @see http://bugs.php.net/bug.php?id=45996
+ * @ingroup PHPBugTests
+ */
+class PhpRefCallBugTester {
+ public $ok = false;
+
+ function __call( $name, $args ) {
+ $old = error_reporting( E_ALL & ~E_WARNING );
+ call_user_func_array( array( $this, 'checkForBrokenRef' ), $args );
+ error_reporting( $old );
+ }
+
+ function checkForBrokenRef( &$var ) {
+ if ( $var ) {
+ $this->ok = true;
+ }
+ }
+
+ function execute() {
+ $var = true;
+ call_user_func_array( array( $this, 'foo' ), array( &$var ) );
+ }
+}
diff --git a/includes/installer/PostgresInstaller.php b/includes/installer/PostgresInstaller.php
new file mode 100644
index 00000000..20575b62
--- /dev/null
+++ b/includes/installer/PostgresInstaller.php
@@ -0,0 +1,601 @@
+<?php
+/**
+ * PostgreSQL-specific installer.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for setting up the MediaWiki database using Postgres.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class PostgresInstaller extends DatabaseInstaller {
+
+ protected $globalNames = array(
+ 'wgDBserver',
+ 'wgDBport',
+ 'wgDBname',
+ 'wgDBuser',
+ 'wgDBpassword',
+ 'wgDBmwschema',
+ );
+
+ protected $internalDefaults = array(
+ '_InstallUser' => 'postgres',
+ );
+
+ var $minimumVersion = '8.3';
+ var $maxRoleSearchDepth = 5;
+
+ protected $pgConns = array();
+
+ function getName() {
+ return 'postgres';
+ }
+
+ public function isCompiled() {
+ return self::checkExtension( 'pgsql' );
+ }
+
+ function getConnectForm() {
+ return
+ $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
+ $this->getTextBox( 'wgDBport', 'config-db-port' ) .
+ Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) .
+ $this->getTextBox( 'wgDBname', 'config-db-name', array(), $this->parent->getHelpBox( 'config-db-name-help' ) ) .
+ $this->getTextBox( 'wgDBmwschema', 'config-db-schema', array(), $this->parent->getHelpBox( 'config-db-schema-help' ) ) .
+ Html::closeElement( 'fieldset' ) .
+ $this->getInstallUserBox();
+ }
+
+ function submitConnectForm() {
+ // Get variables from the request
+ $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBport',
+ 'wgDBname', 'wgDBmwschema' ) );
+
+ // Validate them
+ $status = Status::newGood();
+ if ( !strlen( $newValues['wgDBname'] ) ) {
+ $status->fatal( 'config-missing-db-name' );
+ } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
+ $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
+ }
+ if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
+ $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
+ }
+
+ // Submit user box
+ if ( $status->isOK() ) {
+ $status->merge( $this->submitInstallUserBox() );
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ $status = $this->getPgConnection( 'create-db' );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+
+ // Check version
+ $version = $conn->getServerVersion();
+ if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
+ return Status::newFatal( 'config-postgres-old', $this->minimumVersion, $version );
+ }
+
+ $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
+ $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
+ return Status::newGood();
+ }
+
+ public function getConnection() {
+ $status = $this->getPgConnection( 'create-tables' );
+ if ( $status->isOK() ) {
+ $this->db = $status->value;
+ }
+ return $status;
+ }
+
+ public function openConnection() {
+ return $this->openPgConnection( 'create-tables' );
+ }
+
+ /**
+ * Open a PG connection with given parameters
+ * @param $user User name
+ * @param $password Password
+ * @param $dbName Database name
+ * @return Status
+ */
+ protected function openConnectionWithParams( $user, $password, $dbName ) {
+ $status = Status::newGood();
+ try {
+ $GLOBALS['wgDBport'] = $this->getVar( 'wgDBport' );
+ $db = new DatabasePostgres(
+ $this->getVar( 'wgDBserver' ),
+ $user,
+ $password,
+ $dbName);
+ $status->value = $db;
+ } catch ( DBConnectionError $e ) {
+ $status->fatal( 'config-connection-error', $e->getMessage() );
+ }
+ return $status;
+ }
+
+ /**
+ * Get a special type of connection
+ * @param $type See openPgConnection() for details.
+ * @return Status
+ */
+ protected function getPgConnection( $type ) {
+ if ( isset( $this->pgConns[$type] ) ) {
+ return Status::newGood( $this->pgConns[$type] );
+ }
+ $status = $this->openPgConnection( $type );
+
+ if ( $status->isOK() ) {
+ $conn = $status->value;
+ $conn->clearFlag( DBO_TRX );
+ $conn->commit();
+ $this->pgConns[$type] = $conn;
+ }
+ return $status;
+ }
+
+ /**
+ * Get a connection of a specific PostgreSQL-specific type. Connections
+ * of a given type are cached.
+ *
+ * PostgreSQL lacks cross-database operations, so after the new database is
+ * created, you need to make a separate connection to connect to that
+ * database and add tables to it.
+ *
+ * New tables are owned by the user that creates them, and MediaWiki's
+ * PostgreSQL support has always assumed that the table owner will be
+ * $wgDBuser. So before we create new tables, we either need to either
+ * connect as the other user or to execute a SET ROLE command. Using a
+ * separate connection for this allows us to avoid accidental cross-module
+ * dependencies.
+ *
+ * @param $type The type of connection to get:
+ * - create-db: A connection for creating DBs, suitable for pre-
+ * installation.
+ * - create-schema: A connection to the new DB, for creating schemas and
+ * other similar objects in the new DB.
+ * - create-tables: A connection with a role suitable for creating tables.
+ *
+ * @return A Status object. On success, a connection object will be in the
+ * value member.
+ */
+ protected function openPgConnection( $type ) {
+ switch ( $type ) {
+ case 'create-db':
+ return $this->openConnectionToAnyDB(
+ $this->getVar( '_InstallUser' ),
+ $this->getVar( '_InstallPassword' ) );
+ case 'create-schema':
+ return $this->openConnectionWithParams(
+ $this->getVar( '_InstallUser' ),
+ $this->getVar( '_InstallPassword' ),
+ $this->getVar( 'wgDBname' ) );
+ case 'create-tables':
+ $status = $this->openPgConnection( 'create-schema' );
+ if ( $status->isOK() ) {
+ $conn = $status->value;
+ $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
+ $conn->query( "SET ROLE $safeRole" );
+ }
+ return $status;
+ default:
+ throw new MWException( "Invalid special connection type: \"$type\"" );
+ }
+ }
+
+ public function openConnectionToAnyDB( $user, $password ) {
+ $dbs = array(
+ 'template1',
+ 'postgres',
+ );
+ if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) {
+ array_unshift( $dbs, $this->getVar( 'wgDBname' ) );
+ }
+ $status = Status::newGood();
+ foreach ( $dbs as $db ) {
+ try {
+ $GLOBALS['wgDBport'] = $this->getVar( 'wgDBport' );
+ $conn = new DatabasePostgres(
+ $this->getVar( 'wgDBserver' ),
+ $user,
+ $password,
+ $db );
+ } catch ( DBConnectionError $error ) {
+ $conn = false;
+ $status->fatal( 'config-pg-test-error', $db,
+ $error->getMessage() );
+ }
+ if ( $conn !== false ) {
+ break;
+ }
+ }
+ if ( $conn !== false ) {
+ return Status::newGood( $conn );
+ } else {
+ return $status;
+ }
+ }
+
+ protected function getInstallUserPermissions() {
+ $status = $this->getPgConnection( 'create-db' );
+ if ( !$status->isOK() ) {
+ return false;
+ }
+ $conn = $status->value;
+ $superuser = $this->getVar( '_InstallUser' );
+
+ $row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*',
+ array( 'rolname' => $superuser ), __METHOD__ );
+ return $row;
+ }
+
+ protected function canCreateAccounts() {
+ $perms = $this->getInstallUserPermissions();
+ if ( !$perms ) {
+ return false;
+ }
+ return $perms->rolsuper === 't' || $perms->rolcreaterole === 't';
+ }
+
+ protected function isSuperUser() {
+ $perms = $this->getInstallUserPermissions();
+ if ( !$perms ) {
+ return false;
+ }
+ return $perms->rolsuper === 't';
+ }
+
+ public function getSettingsForm() {
+ if ( $this->canCreateAccounts() ) {
+ $noCreateMsg = false;
+ } else {
+ $noCreateMsg = 'config-db-web-no-create-privs';
+ }
+ $s = $this->getWebUserBox( $noCreateMsg );
+
+ return $s;
+ }
+
+ public function submitSettingsForm() {
+ $status = $this->submitWebUserBox();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ $same = $this->getVar( 'wgDBuser' ) === $this->getVar( '_InstallUser' );
+
+ if ( $same ) {
+ $exists = true;
+ } else {
+ // Check if the web user exists
+ // Connect to the database with the install user
+ $status = $this->getPgConnection( 'create-db' );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) );
+ }
+
+ // Validate the create checkbox
+ if ( $this->canCreateAccounts() && !$same && !$exists ) {
+ $create = $this->getVar( '_CreateDBAccount' );
+ } else {
+ $this->setVar( '_CreateDBAccount', false );
+ $create = false;
+ }
+
+ if ( !$create && !$exists ) {
+ if ( $this->canCreateAccounts() ) {
+ $msg = 'config-install-user-missing-create';
+ } else {
+ $msg = 'config-install-user-missing';
+ }
+ return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) );
+ }
+
+ if ( !$exists ) {
+ // No more checks to do
+ return Status::newGood();
+ }
+
+ // Existing web account. Test the connection.
+ $status = $this->openConnectionToAnyDB(
+ $this->getVar( 'wgDBuser' ),
+ $this->getVar( 'wgDBpassword' ) );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ // The web user is conventionally the table owner in PostgreSQL
+ // installations. Make sure the install user is able to create
+ // objects on behalf of the web user.
+ if ( $same || $this->canCreateObjectsForWebUser() ) {
+ return Status::newGood();
+ } else {
+ return Status::newFatal( 'config-pg-not-in-role' );
+ }
+ }
+
+ /**
+ * Returns true if the install user is able to create objects owned
+ * by the web user, false otherwise.
+ */
+ protected function canCreateObjectsForWebUser() {
+ if ( $this->isSuperUser() ) {
+ return true;
+ }
+
+ $status = $this->getPgConnection( 'create-db' );
+ if ( !$status->isOK() ) {
+ return false;
+ }
+ $conn = $status->value;
+ $installerId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
+ array( 'rolname' => $this->getVar( '_InstallUser' ) ), __METHOD__ );
+ $webId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
+ array( 'rolname' => $this->getVar( 'wgDBuser' ) ), __METHOD__ );
+
+ return $this->isRoleMember( $conn, $installerId, $webId, $this->maxRoleSearchDepth );
+ }
+
+ /**
+ * Recursive helper for canCreateObjectsForWebUser().
+ * @param $conn Database object
+ * @param $targetMember Role ID of the member to look for
+ * @param $group Role ID of the group to look for
+ * @param $maxDepth Maximum recursive search depth
+ */
+ protected function isRoleMember( $conn, $targetMember, $group, $maxDepth ) {
+ if ( $targetMember === $group ) {
+ // A role is always a member of itself
+ return true;
+ }
+ // Get all members of the given group
+ $res = $conn->select( '"pg_catalog"."pg_auth_members"', array( 'member' ),
+ array( 'roleid' => $group ), __METHOD__ );
+ foreach ( $res as $row ) {
+ if ( $row->member == $targetMember ) {
+ // Found target member
+ return true;
+ }
+ // Recursively search each member of the group to see if the target
+ // is a member of it, up to the given maximum depth.
+ if ( $maxDepth > 0 ) {
+ if ( $this->isRoleMember( $conn, $targetMember, $row->member, $maxDepth - 1 ) ) {
+ // Found member of member
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public function preInstall() {
+ $commitCB = array(
+ 'name' => 'pg-commit',
+ 'callback' => array( $this, 'commitChanges' ),
+ );
+ $plpgCB = array(
+ 'name' => 'pg-plpgsql',
+ 'callback' => array( $this, 'setupPLpgSQL' ),
+ );
+ $schemaCB = array(
+ 'name' => 'schema',
+ 'callback' => array( $this, 'setupSchema' )
+ );
+ $this->parent->addInstallStep( $commitCB, 'interwiki' );
+ $this->parent->addInstallStep( $plpgCB, 'database' );
+ $this->parent->addInstallStep( $schemaCB, 'database' );
+ if( $this->getVar( '_CreateDBAccount' ) ) {
+ $this->parent->addInstallStep( array(
+ 'name' => 'user',
+ 'callback' => array( $this, 'setupUser' ),
+ ) );
+ }
+ }
+
+ function setupDatabase() {
+ $status = $this->getPgConnection( 'create-db' );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+
+ $dbName = $this->getVar( 'wgDBname' );
+ $schema = $this->getVar( 'wgDBmwschema' );
+ $user = $this->getVar( 'wgDBuser' );
+ $safeschema = $conn->addIdentifierQuotes( $schema );
+ $safeuser = $conn->addIdentifierQuotes( $user );
+
+ $exists = $conn->selectField( '"pg_catalog"."pg_database"', '1',
+ array( 'datname' => $dbName ), __METHOD__ );
+ if ( !$exists ) {
+ $safedb = $conn->addIdentifierQuotes( $dbName );
+ $conn->query( "CREATE DATABASE $safedb", __METHOD__ );
+ }
+ return Status::newGood();
+ }
+
+ function setupSchema() {
+ // Get a connection to the target database
+ $status = $this->getPgConnection( 'create-schema' );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+
+ // Create the schema if necessary
+ $schema = $this->getVar( 'wgDBmwschema' );
+ $safeschema = $conn->addIdentifierQuotes( $schema );
+ $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
+ if( !$conn->schemaExists( $schema ) ) {
+ try {
+ $conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" );
+ } catch ( DBQueryError $e ) {
+ return Status::newFatal( 'config-install-pg-schema-failed',
+ $this->getVar( '_InstallUser' ), $schema );
+ }
+ }
+
+ // If we created a user, alter it now to search the new schema by default
+ if ( $this->getVar( '_CreateDBAccount' ) ) {
+ $conn->query( "ALTER ROLE $safeuser SET search_path = $safeschema, public",
+ __METHOD__ );
+ }
+
+ // Select the new schema in the current connection
+ $conn->query( "SET search_path = $safeschema" );
+ return Status::newGood();
+ }
+
+ function commitChanges() {
+ $this->db->query( 'COMMIT' );
+ return Status::newGood();
+ }
+
+ function setupUser() {
+ if ( !$this->getVar( '_CreateDBAccount' ) ) {
+ return Status::newGood();
+ }
+
+ $status = $this->getPgConnection( 'create-db' );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+
+ $schema = $this->getVar( 'wgDBmwschema' );
+ $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
+ $safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) );
+ $safeschema = $conn->addIdentifierQuotes( $schema );
+
+ // Check if the user already exists
+ $userExists = $conn->roleExists( $this->getVar( 'wgDBuser' ) );
+ if ( !$userExists ) {
+ // Create the user
+ try {
+ $sql = "CREATE ROLE $safeuser NOCREATEDB LOGIN PASSWORD $safepass";
+
+ // If the install user is not a superuser, we need to make the install
+ // user a member of the new user's group, so that the install user will
+ // be able to create a schema and other objects on behalf of the new user.
+ if ( !$this->isSuperUser() ) {
+ $sql .= ' ROLE' . $conn->addIdentifierQuotes( $this->getVar( '_InstallUser' ) );
+ }
+
+ $conn->query( $sql, __METHOD__ );
+ } catch ( DBQueryError $e ) {
+ return Status::newFatal( 'config-install-user-create-failed',
+ $this->getVar( 'wgDBuser' ), $e->getMessage() );
+ }
+ }
+
+ return Status::newGood();
+ }
+
+ function getLocalSettings() {
+ $port = $this->getVar( 'wgDBport' );
+ $schema = $this->getVar( 'wgDBmwschema' );
+ return
+"# Postgres specific settings
+\$wgDBport = \"{$port}\";
+\$wgDBmwschema = \"{$schema}\";";
+ }
+
+ public function preUpgrade() {
+ global $wgDBuser, $wgDBpassword;
+
+ # Normal user and password are selected after this step, so for now
+ # just copy these two
+ $wgDBuser = $this->getVar( '_InstallUser' );
+ $wgDBpassword = $this->getVar( '_InstallPassword' );
+ }
+
+ public function createTables() {
+ $schema = $this->getVar( 'wgDBmwschema' );
+
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+
+ if( $conn->tableExists( 'user' ) ) {
+ $status->warning( 'config-install-tables-exist' );
+ return $status;
+ }
+
+ $conn->begin( __METHOD__ );
+
+ if( !$conn->schemaExists( $schema ) ) {
+ $status->fatal( 'config-install-pg-schema-not-exist' );
+ return $status;
+ }
+ $error = $conn->sourceFile( $conn->getSchema() );
+ if( $error !== true ) {
+ $conn->reportQueryError( $error, 0, '', __METHOD__ );
+ $conn->rollback( __METHOD__ );
+ $status->fatal( 'config-install-tables-failed', $error );
+ } else {
+ $conn->commit( __METHOD__ );
+ }
+ // Resume normal operations
+ if( $status->isOk() ) {
+ $this->enableLB();
+ }
+ return $status;
+ }
+
+ public function setupPLpgSQL() {
+ // Connect as the install user, since it owns the database and so is
+ // the user that needs to run "CREATE LANGAUGE"
+ $status = $this->getPgConnection( 'create-schema' );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $conn = $status->value;
+
+ $exists = $conn->selectField( '"pg_catalog"."pg_language"', 1,
+ array( 'lanname' => 'plpgsql' ), __METHOD__ );
+ if ( $exists ) {
+ // Already exists, nothing to do
+ return Status::newGood();
+ }
+
+ // plpgsql is not installed, but if we have a pg_pltemplate table, we
+ // should be able to create it
+ $exists = $conn->selectField(
+ array( '"pg_catalog"."pg_class"', '"pg_catalog"."pg_namespace"' ),
+ 1,
+ array(
+ 'pg_namespace.oid=relnamespace',
+ 'nspname' => 'pg_catalog',
+ 'relname' => 'pg_pltemplate',
+ ),
+ __METHOD__ );
+ if ( $exists ) {
+ try {
+ $conn->query( 'CREATE LANGUAGE plpgsql' );
+ } catch ( DBQueryError $e ) {
+ return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
+ }
+ } else {
+ return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
+ }
+ return Status::newGood();
+ }
+}
diff --git a/includes/installer/PostgresUpdater.php b/includes/installer/PostgresUpdater.php
new file mode 100644
index 00000000..272638ce
--- /dev/null
+++ b/includes/installer/PostgresUpdater.php
@@ -0,0 +1,628 @@
+<?php
+/**
+ * PostgreSQL-specific updater.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for handling updates to Postgres databases.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+
+class PostgresUpdater extends DatabaseUpdater {
+
+ /**
+ * @todo FIXME: Postgres should use sequential updates like Mysql, Sqlite
+ * and everybody else. It never got refactored like it should've.
+ */
+ protected function getCoreUpdateList() {
+ return array(
+ # new sequences
+ array( 'addSequence', 'logging_log_id_seq' ),
+ array( 'addSequence', 'page_restrictions_pr_id_seq' ),
+
+ # renamed sequences
+ array( 'renameSequence', 'ipblocks_ipb_id_val', 'ipblocks_ipb_id_seq' ),
+ array( 'renameSequence', 'rev_rev_id_val', 'revision_rev_id_seq' ),
+ array( 'renameSequence', 'text_old_id_val', 'text_old_id_seq' ),
+ array( 'renameSequence', 'category_id_seq', 'category_cat_id_seq' ),
+ array( 'renameSequence', 'rc_rc_id_seq', 'recentchanges_rc_id_seq' ),
+ array( 'renameSequence', 'log_log_id_seq', 'logging_log_id_seq' ),
+ array( 'renameSequence', 'pr_id_val', 'page_restrictions_pr_id_seq' ),
+
+ # new tables
+ array( 'addTable', 'category', 'patch-category.sql' ),
+ array( 'addTable', 'page', 'patch-page.sql' ),
+ array( 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ),
+ array( 'addTable', 'page_props', 'patch-page_props.sql' ),
+ array( 'addTable', 'page_restrictions', 'patch-page_restrictions.sql' ),
+ array( 'addTable', 'profiling', 'patch-profiling.sql' ),
+ array( 'addTable', 'protected_titles', 'patch-protected_titles.sql' ),
+ array( 'addTable', 'redirect', 'patch-redirect.sql' ),
+ array( 'addTable', 'updatelog', 'patch-updatelog.sql' ),
+ array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
+ array( 'addTable', 'tag_summary', 'patch-tag_summary.sql' ),
+ array( 'addTable', 'valid_tag', 'patch-valid_tag.sql' ),
+ array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
+ array( 'addTable', 'log_search', 'patch-log_search.sql' ),
+ array( 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ),
+ array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
+ array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
+ array( 'addTable', 'msg_resource_links','patch-msg_resource_links.sql' ),
+ array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
+
+ # Needed before new field
+ array( 'convertArchive2' ),
+
+ # new fields
+ array( 'addPgField', 'archive', 'ar_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'archive', 'ar_len', 'INTEGER' ),
+ array( 'addPgField', 'archive', 'ar_page_id', 'INTEGER' ),
+ array( 'addPgField', 'archive', 'ar_parent_id', 'INTEGER' ),
+ array( 'addPgField', 'categorylinks', 'cl_sortkey_prefix', "TEXT NOT NULL DEFAULT ''"),
+ array( 'addPgField', 'categorylinks', 'cl_collation', "TEXT NOT NULL DEFAULT 0"),
+ array( 'addPgField', 'categorylinks', 'cl_type', "TEXT NOT NULL DEFAULT 'page'"),
+ array( 'addPgField', 'image', 'img_sha1', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'ipblocks', 'ipb_allow_usertalk', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'ipblocks', 'ipb_anon_only', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'ipblocks', 'ipb_by_text', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'ipblocks', 'ipb_block_email', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'ipblocks', 'ipb_create_account', 'SMALLINT NOT NULL DEFAULT 1' ),
+ array( 'addPgField', 'ipblocks', 'ipb_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'ipblocks', 'ipb_enable_autoblock', 'SMALLINT NOT NULL DEFAULT 1' ),
+ array( 'addPgField', 'filearchive', 'fa_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'logging', 'log_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'logging', 'log_id', "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('logging_log_id_seq')" ),
+ array( 'addPgField', 'logging', 'log_params', 'TEXT' ),
+ array( 'addPgField', 'mwuser', 'user_editcount', 'INTEGER' ),
+ array( 'addPgField', 'mwuser', 'user_newpass_time', 'TIMESTAMPTZ' ),
+ array( 'addPgField', 'oldimage', 'oi_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'oldimage', 'oi_major_mime', "TEXT NOT NULL DEFAULT 'unknown'" ),
+ array( 'addPgField', 'oldimage', 'oi_media_type', 'TEXT' ),
+ array( 'addPgField', 'oldimage', 'oi_metadata', "BYTEA NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'oldimage', 'oi_minor_mime', "TEXT NOT NULL DEFAULT 'unknown'" ),
+ array( 'addPgField', 'oldimage', 'oi_sha1', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'page_restrictions', 'pr_id', "INTEGER NOT NULL UNIQUE DEFAULT nextval('page_restrictions_pr_id_seq')" ),
+ array( 'addPgField', 'profiling', 'pf_memory', 'NUMERIC(18,10) NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'recentchanges', 'rc_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'recentchanges', 'rc_log_action', 'TEXT' ),
+ array( 'addPgField', 'recentchanges', 'rc_log_type', 'TEXT' ),
+ array( 'addPgField', 'recentchanges', 'rc_logid', 'INTEGER NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'recentchanges', 'rc_new_len', 'INTEGER' ),
+ array( 'addPgField', 'recentchanges', 'rc_old_len', 'INTEGER' ),
+ array( 'addPgField', 'recentchanges', 'rc_params', 'TEXT' ),
+ array( 'addPgField', 'redirect', 'rd_interwiki', 'TEXT NULL' ),
+ array( 'addPgField', 'redirect', 'rd_fragment', 'TEXT NULL' ),
+ array( 'addPgField', 'revision', 'rev_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'revision', 'rev_len', 'INTEGER' ),
+ array( 'addPgField', 'revision', 'rev_parent_id', 'INTEGER DEFAULT NULL' ),
+ array( 'addPgField', 'site_stats', 'ss_active_users', "INTEGER DEFAULT '-1'" ),
+ array( 'addPgField', 'user_newtalk', 'user_last_timestamp', 'TIMESTAMPTZ' ),
+ array( 'addPgField', 'logging', 'log_user_text', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'logging', 'log_page', 'INTEGER' ),
+ array( 'addPgField', 'interwiki', 'iw_api', "TEXT NOT NULL DEFAULT ''"),
+ array( 'addPgField', 'interwiki', 'iw_wikiid', "TEXT NOT NULL DEFAULT ''"),
+
+ # type changes
+ array( 'changeField', 'archive', 'ar_deleted', 'smallint', '' ),
+ array( 'changeField', 'archive', 'ar_minor_edit', 'smallint', 'ar_minor_edit::smallint DEFAULT 0' ),
+ array( 'changeField', 'filearchive', 'fa_deleted', 'smallint', '' ),
+ array( 'changeField', 'filearchive', 'fa_height', 'integer', '' ),
+ array( 'changeField', 'filearchive', 'fa_metadata', 'bytea', "decode(fa_metadata,'escape')" ),
+ array( 'changeField', 'filearchive', 'fa_size', 'integer', '' ),
+ array( 'changeField', 'filearchive', 'fa_width', 'integer', '' ),
+ array( 'changeField', 'filearchive', 'fa_storage_group', 'text', '' ),
+ array( 'changeField', 'filearchive', 'fa_storage_key', 'text', '' ),
+ array( 'changeField', 'image', 'img_metadata', 'bytea', "decode(img_metadata,'escape')" ),
+ array( 'changeField', 'image', 'img_size', 'integer', '' ),
+ array( 'changeField', 'image', 'img_width', 'integer', '' ),
+ array( 'changeField', 'image', 'img_height', 'integer', '' ),
+ array( 'changeField', 'interwiki', 'iw_local', 'smallint', 'iw_local::smallint DEFAULT 0' ),
+ array( 'changeField', 'interwiki', 'iw_trans', 'smallint', 'iw_trans::smallint DEFAULT 0' ),
+ array( 'changeField', 'ipblocks', 'ipb_auto', 'smallint', 'ipb_auto::smallint DEFAULT 0' ),
+ array( 'changeField', 'ipblocks', 'ipb_anon_only', 'smallint', "CASE WHEN ipb_anon_only=' ' THEN 0 ELSE ipb_anon_only::smallint END DEFAULT 0" ),
+ array( 'changeField', 'ipblocks', 'ipb_create_account', 'smallint', "CASE WHEN ipb_create_account=' ' THEN 0 ELSE ipb_create_account::smallint END DEFAULT 1" ),
+ array( 'changeField', 'ipblocks', 'ipb_enable_autoblock', 'smallint', "CASE WHEN ipb_enable_autoblock=' ' THEN 0 ELSE ipb_enable_autoblock::smallint END DEFAULT 1" ),
+ array( 'changeField', 'ipblocks', 'ipb_block_email', 'smallint', "CASE WHEN ipb_block_email=' ' THEN 0 ELSE ipb_block_email::smallint END DEFAULT 0" ),
+ array( 'changeField', 'ipblocks', 'ipb_address', 'text', 'ipb_address::text' ),
+ array( 'changeField', 'ipblocks', 'ipb_deleted', 'smallint', 'ipb_deleted::smallint DEFAULT 0' ),
+ array( 'changeField', 'math', 'math_inputhash', 'bytea', "decode(math_inputhash,'escape')" ),
+ array( 'changeField', 'math', 'math_outputhash', 'bytea', "decode(math_outputhash,'escape')" ),
+ array( 'changeField', 'mwuser', 'user_token', 'text', '' ),
+ array( 'changeField', 'mwuser', 'user_email_token', 'text', '' ),
+ array( 'changeField', 'objectcache', 'keyname', 'text', '' ),
+ array( 'changeField', 'oldimage', 'oi_height', 'integer', '' ),
+ array( 'changeField', 'oldimage', 'oi_metadata', 'bytea', "decode(img_metadata,'escape')" ),
+ array( 'changeField', 'oldimage', 'oi_size', 'integer', '' ),
+ array( 'changeField', 'oldimage', 'oi_width', 'integer', '' ),
+ array( 'changeField', 'page', 'page_is_redirect', 'smallint', 'page_is_redirect::smallint DEFAULT 0' ),
+ array( 'changeField', 'page', 'page_is_new', 'smallint', 'page_is_new::smallint DEFAULT 0' ),
+ array( 'changeField', 'querycache', 'qc_value', 'integer', '' ),
+ array( 'changeField', 'querycachetwo', 'qcc_value', 'integer', '' ),
+ array( 'changeField', 'recentchanges', 'rc_bot', 'smallint', 'rc_bot::smallint DEFAULT 0' ),
+ array( 'changeField', 'recentchanges', 'rc_deleted', 'smallint', '' ),
+ array( 'changeField', 'recentchanges', 'rc_minor', 'smallint', 'rc_minor::smallint DEFAULT 0' ),
+ array( 'changeField', 'recentchanges', 'rc_new', 'smallint', 'rc_new::smallint DEFAULT 0' ),
+ array( 'changeField', 'recentchanges', 'rc_type', 'smallint', 'rc_type::smallint DEFAULT 0' ),
+ array( 'changeField', 'recentchanges', 'rc_patrolled', 'smallint', 'rc_patrolled::smallint DEFAULT 0' ),
+ array( 'changeField', 'revision', 'rev_deleted', 'smallint', 'rev_deleted::smallint DEFAULT 0' ),
+ array( 'changeField', 'revision', 'rev_minor_edit', 'smallint', 'rev_minor_edit::smallint DEFAULT 0' ),
+ array( 'changeField', 'templatelinks', 'tl_namespace', 'smallint', 'tl_namespace::smallint' ),
+ array( 'changeField', 'user_newtalk', 'user_ip', 'text', 'host(user_ip)' ),
+
+ # null changes
+ array( 'changeNullableField', 'oldimage', 'oi_bits', 'NULL' ),
+ array( 'changeNullableField', 'oldimage', 'oi_timestamp', 'NULL' ),
+ array( 'changeNullableField', 'oldimage', 'oi_major_mime', 'NULL' ),
+ array( 'changeNullableField', 'oldimage', 'oi_minor_mime', 'NULL' ),
+
+ array( 'checkOiDeleted' ),
+
+ # New indexes
+ array( 'addPgIndex', 'archive', 'archive_user_text', '(ar_user_text)' ),
+ array( 'addPgIndex', 'image', 'img_sha1', '(img_sha1)' ),
+ array( 'addPgIndex', 'oldimage', 'oi_sha1', '(oi_sha1)' ),
+ array( 'addPgIndex', 'page', 'page_mediawiki_title', '(page_title) WHERE page_namespace = 8' ),
+ array( 'addPgIndex', 'pagelinks', 'pagelinks_title', '(pl_title)' ),
+ array( 'addPgIndex', 'revision', 'rev_text_id_idx', '(rev_text_id)' ),
+ array( 'addPgIndex', 'recentchanges', 'rc_timestamp_bot', '(rc_timestamp) WHERE rc_bot = 0' ),
+ array( 'addPgIndex', 'templatelinks', 'templatelinks_from', '(tl_from)' ),
+ array( 'addPgIndex', 'watchlist', 'wl_user', '(wl_user)' ),
+ array( 'addPgIndex', 'logging', 'logging_user_type_time', '(log_user, log_type, log_timestamp)' ),
+ array( 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ),
+ array( 'addPgIndex', 'iwlinks', 'iwl_prefix_title_from', '(iwl_prefix, iwl_title, iwl_from)' ),
+
+ array( 'checkOiNameConstraint' ),
+ array( 'checkPageDeletedTrigger' ),
+ array( 'checkRcCurIdNullable' ),
+ array( 'checkPagelinkUniqueIndex' ),
+ array( 'checkRevUserFkey' ),
+ array( 'checkIpbAdress' ),
+ array( 'checkIwlPrefix' ),
+
+ # All FK columns should be deferred
+ array( 'changeFkeyDeferrable', 'archive', 'ar_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'categorylinks', 'cl_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'externallinks', 'el_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'filearchive', 'fa_deleted_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'filearchive', 'fa_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'image', 'img_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'imagelinks', 'il_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_by', 'mwuser(user_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'langlinks', 'll_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'logging', 'log_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'oldimage', 'oi_name', 'image(img_name) ON DELETE CASCADE ON UPDATE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'oldimage', 'oi_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'pagelinks', 'pl_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'page_props', 'pp_page', 'page (page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'page_restrictions', 'pr_page', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'protected_titles', 'pt_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'recentchanges', 'rc_cur_id', 'page(page_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'recentchanges', 'rc_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'redirect', 'rd_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'revision', 'rev_page', 'page (page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'revision', 'rev_user', 'mwuser(user_id) ON DELETE RESTRICT' ),
+ array( 'changeFkeyDeferrable', 'templatelinks', 'tl_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'trackbacks', 'tb_page', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'user_groups', 'ug_user', 'mwuser(user_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'user_newtalk', 'user_id', 'mwuser(user_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'user_properties', 'up_user', 'mwuser(user_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'watchlist', 'wl_user', 'mwuser(user_id) ON DELETE CASCADE' ),
+
+ # end
+ array( 'tsearchFixes' ),
+ );
+ }
+
+ protected function getOldGlobalUpdates() {
+ global $wgExtNewTables, $wgExtPGNewFields, $wgExtPGAlteredFields, $wgExtNewIndexes;
+
+ $updates = array();
+
+ # Add missing extension tables
+ foreach ( $wgExtNewTables as $tableRecord ) {
+ $updates[] = array(
+ 'addTable', $tableRecord[0], $tableRecord[1], true
+ );
+ }
+
+ # Add missing extension fields
+ foreach ( $wgExtPGNewFields as $fieldRecord ) {
+ $updates[] = array(
+ 'addPgField', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2]
+ );
+ }
+
+ # Change altered columns
+ foreach ( $wgExtPGAlteredFields as $fieldRecord ) {
+ $updates[] = array(
+ 'changeField', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2]
+ );
+ }
+
+ # Add missing extension indexes
+ foreach ( $wgExtNewIndexes as $fieldRecord ) {
+ $updates[] = array(
+ 'addPgExtIndex', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2]
+ );
+ }
+
+ return $updates;
+ }
+
+ protected function describeTable( $table ) {
+ global $wgDBmwschema;
+ $q = <<<END
+SELECT attname, attnum FROM pg_namespace, pg_class, pg_attribute
+ WHERE pg_class.relnamespace = pg_namespace.oid
+ AND attrelid=pg_class.oid AND attnum > 0
+ AND relname=%s AND nspname=%s
+END;
+ $res = $this->db->query( sprintf( $q,
+ $this->db->addQuotes( $table ),
+ $this->db->addQuotes( $wgDBmwschema ) ) );
+ if ( !$res ) {
+ return null;
+ }
+
+ $cols = array();
+ foreach ( $res as $r ) {
+ $cols[] = array(
+ "name" => $r[0],
+ "ord" => $r[1],
+ );
+ }
+ return $cols;
+ }
+
+ function describeIndex( $idx ) {
+ global $wgDBmwschema;
+
+ // first fetch the key (which is a list of columns ords) and
+ // the table the index applies to (an oid)
+ $q = <<<END
+SELECT indkey, indrelid FROM pg_namespace, pg_class, pg_index
+ WHERE nspname=%s
+ AND pg_class.relnamespace = pg_namespace.oid
+ AND relname=%s
+ AND indexrelid=pg_class.oid
+END;
+ $res = $this->db->query(
+ sprintf(
+ $q,
+ $this->db->addQuotes( $wgDBmwschema ),
+ $this->db->addQuotes( $idx )
+ )
+ );
+ if ( !$res ) {
+ return null;
+ }
+ if ( !( $r = $this->db->fetchRow( $res ) ) ) {
+ return null;
+ }
+
+ $indkey = $r[0];
+ $relid = intval( $r[1] );
+ $indkeys = explode( ' ', $indkey );
+
+ $colnames = array();
+ foreach ( $indkeys as $rid ) {
+ $query = <<<END
+SELECT attname FROM pg_class, pg_attribute
+ WHERE attrelid=$relid
+ AND attnum=%d
+ AND attrelid=pg_class.oid
+END;
+ $r2 = $this->db->query( sprintf( $query, $rid ) );
+ if ( !$r2 ) {
+ return null;
+ }
+ if ( !( $row2 = $this->db->fetchRow( $r2 ) ) ) {
+ return null;
+ }
+ $colnames[] = $row2[0];
+ }
+
+ return $colnames;
+ }
+
+ function fkeyDeltype( $fkey ) {
+ global $wgDBmwschema;
+ $q = <<<END
+SELECT confdeltype FROM pg_constraint, pg_namespace
+ WHERE connamespace=pg_namespace.oid
+ AND nspname=%s
+ AND conname=%s;
+END;
+ $r = $this->db->query(
+ sprintf(
+ $q,
+ $this->db->addQuotes( $wgDBmwschema ),
+ $this->db->addQuotes( $fkey )
+ )
+ );
+ if ( !( $row = $this->db->fetchRow( $r ) ) ) {
+ return null;
+ }
+ return $row[0];
+ }
+
+ function ruleDef( $table, $rule ) {
+ global $wgDBmwschema;
+ $q = <<<END
+SELECT definition FROM pg_rules
+ WHERE schemaname = %s
+ AND tablename = %s
+ AND rulename = %s
+END;
+ $r = $this->db->query(
+ sprintf(
+ $q,
+ $this->db->addQuotes( $wgDBmwschema ),
+ $this->db->addQuotes( $table ),
+ $this->db->addQuotes( $rule )
+ )
+ );
+ $row = $this->db->fetchRow( $r );
+ if ( !$row ) {
+ return null;
+ }
+ $d = $row[0];
+ return $d;
+ }
+
+ protected function addSequence( $ns ) {
+ if ( !$this->db->sequenceExists( $ns ) ) {
+ $this->output( "Creating sequence $ns\n" );
+ $this->db->query( "CREATE SEQUENCE $ns" );
+ }
+ }
+
+ protected function renameSequence( $old, $new ) {
+ if ( $this->db->sequenceExists( $old ) ) {
+ $this->output( "Renaming sequence $old to $new\n" );
+ $this->db->query( "ALTER SEQUENCE $old RENAME TO $new" );
+ }
+ }
+
+ protected function renameTable( $old, $new ) {
+ if ( $this->db->tableExists( $old ) ) {
+ $this->output( "Renaming table $old to $new\n" );
+ $old = $this->db->addQuotes( $old );
+ $this->db->query( "ALTER TABLE $old RENAME TO $new" );
+ }
+ }
+
+ protected function addPgField( $table, $field, $type ) {
+ $fi = $this->db->fieldInfo( $table, $field );
+ if ( !is_null( $fi ) ) {
+ $this->output( "... column '$table.$field' already exists\n" );
+ return;
+ } else {
+ $this->output( "Adding column '$table.$field'\n" );
+ $this->db->query( "ALTER TABLE $table ADD $field $type" );
+ }
+ }
+
+ protected function changeField( $table, $field, $newtype, $default ) {
+ $fi = $this->db->fieldInfo( $table, $field );
+ if ( is_null( $fi ) ) {
+ $this->output( "... error: expected column $table.$field to exist\n" );
+ exit( 1 );
+ }
+
+ if ( $fi->type() === $newtype )
+ $this->output( "... column '$table.$field' is already of type '$newtype'\n" );
+ else {
+ $this->output( "Changing column type of '$table.$field' from '{$fi->type()}' to '$newtype'\n" );
+ $sql = "ALTER TABLE $table ALTER $field TYPE $newtype";
+ if ( strlen( $default ) ) {
+ $res = array();
+ if ( preg_match( '/DEFAULT (.+)/', $default, $res ) ) {
+ $sqldef = "ALTER TABLE $table ALTER $field SET DEFAULT $res[1]";
+ $this->db->query( $sqldef );
+ $default = preg_replace( '/\s*DEFAULT .+/', '', $default );
+ }
+ $sql .= " USING $default";
+ }
+ $sql .= ";\nCOMMIT;\n";
+ $this->db->query( $sql );
+ }
+ }
+
+ protected function changeNullableField( $table, $field, $null ) {
+ $fi = $this->db->fieldInfo( $table, $field );
+ if ( is_null( $fi ) ) {
+ $this->output( "... error: expected column $table.$field to exist\n" );
+ exit( 1 );
+ }
+ if ( $fi->isNullable() ) {
+ # # It's NULL - does it need to be NOT NULL?
+ if ( 'NOT NULL' === $null ) {
+ $this->output( "Changing '$table.$field' to not allow NULLs\n" );
+ $this->db->query( "ALTER TABLE $table ALTER $field SET NOT NULL" );
+ } else {
+ $this->output( "... column '$table.$field' is already set as NULL\n" );
+ }
+ } else {
+ # # It's NOT NULL - does it need to be NULL?
+ if ( 'NULL' === $null ) {
+ $this->output( "Changing '$table.$field' to allow NULLs\n" );
+ $this->db->query( "ALTER TABLE $table ALTER $field DROP NOT NULL" );
+ }
+ else {
+ $this->output( "... column '$table.$field' is already set as NOT NULL\n" );
+ }
+ }
+ }
+
+ public function addPgIndex( $table, $index, $type ) {
+ if ( $this->db->indexExists( $table, $index ) ) {
+ $this->output( "... index '$index' on table '$table' already exists\n" );
+ } else {
+ $this->output( "Creating index '$index' on table '$table' $type\n" );
+ $this->db->query( "CREATE INDEX $index ON $table $type" );
+ }
+ }
+
+ public function addPgExtIndex( $table, $index, $type ) {
+ if ( $this->db->indexExists( $table, $index ) ) {
+ $this->output( "... index '$index' on table '$table' already exists\n" );
+ } else {
+ $this->output( "Creating index '$index' on table '$table'\n" );
+ if ( preg_match( '/^\(/', $type ) ) {
+ $this->db->query( "CREATE INDEX $index ON $table $type" );
+ } else {
+ $this->applyPatch( $type, true );
+ }
+ }
+ }
+
+ protected function changeFkeyDeferrable( $table, $field, $clause ) {
+ $fi = $this->db->fieldInfo( $table, $field );
+ if ( is_null( $fi ) ) {
+ $this->output( "WARNING! Column '$table.$field' does not exist but it should! Please report this.\n" );
+ return;
+ }
+ if ( $fi->is_deferred() && $fi->is_deferrable() ) {
+ return;
+ }
+ $this->output( "Altering column '$table.$field' to be DEFERRABLE INITIALLY DEFERRED\n" );
+ $conname = $fi->conname();
+ $command = "ALTER TABLE $table DROP CONSTRAINT $conname";
+ $this->db->query( $command );
+ $command = "ALTER TABLE $table ADD CONSTRAINT $conname FOREIGN KEY ($field) REFERENCES $clause DEFERRABLE INITIALLY DEFERRED";
+ $this->db->query( $command );
+ }
+
+ protected function convertArchive2() {
+ if ( $this->db->tableExists( "archive2" ) ) {
+ $this->output( "Converting 'archive2' back to normal archive table\n" );
+ if ( $this->db->ruleExists( 'archive', 'archive_insert' ) ) {
+ $this->output( "Dropping rule 'archive_insert'\n" );
+ $this->db->query( 'DROP RULE archive_insert ON archive' );
+ }
+ if ( $this->db->ruleExists( 'archive', 'archive_delete' ) ) {
+ $this->output( "Dropping rule 'archive_delete'\n" );
+ $this->db->query( 'DROP RULE archive_delete ON archive' );
+ }
+ $this->applyPatch( 'patch-remove-archive2.sql' );
+ } else {
+ $this->output( "... obsolete table 'archive2' does not exist\n" );
+ }
+ }
+
+ protected function checkOiDeleted() {
+ if ( $this->db->fieldInfo( 'oldimage', 'oi_deleted' )->type() !== 'smallint' ) {
+ $this->output( "Changing 'oldimage.oi_deleted' to type 'smallint'\n" );
+ $this->db->query( "ALTER TABLE oldimage ALTER oi_deleted DROP DEFAULT" );
+ $this->db->query( "ALTER TABLE oldimage ALTER oi_deleted TYPE SMALLINT USING (oi_deleted::smallint)" );
+ $this->db->query( "ALTER TABLE oldimage ALTER oi_deleted SET DEFAULT 0" );
+ } else {
+ $this->output( "... column 'oldimage.oi_deleted' is already of type 'smallint'\n" );
+ }
+ }
+
+ protected function checkOiNameConstraint() {
+ if ( $this->db->hasConstraint( "oldimage_oi_name_fkey_cascaded" ) ) {
+ $this->output( "... table 'oldimage' has correct cascading delete/update foreign key to image\n" );
+ } else {
+ if ( $this->db->hasConstraint( "oldimage_oi_name_fkey" ) ) {
+ $this->db->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey" );
+ }
+ if ( $this->db->hasConstraint( "oldimage_oi_name_fkey_cascade" ) ) {
+ $this->db->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey_cascade" );
+ }
+ $this->output( "Making foreign key on table 'oldimage' (to image) a cascade delete/update\n" );
+ $this->db->query( "ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascaded " .
+ "FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE ON UPDATE CASCADE" );
+ }
+ }
+
+ protected function checkPageDeletedTrigger() {
+ if ( !$this->db->triggerExists( 'page', 'page_deleted' ) ) {
+ $this->output( "Adding function and trigger 'page_deleted' to table 'page'\n" );
+ $this->applyPatch( 'patch-page_deleted.sql' );
+ } else {
+ $this->output( "... table 'page' has 'page_deleted' trigger\n" );
+ }
+ }
+
+ protected function checkRcCurIdNullable(){
+ $fi = $this->db->fieldInfo( 'recentchanges', 'rc_cur_id' );
+ if ( !$fi->isNullable() ) {
+ $this->output( "Removing NOT NULL constraint from 'recentchanges.rc_cur_id'\n" );
+ $this->applyPatch( 'patch-rc_cur_id-not-null.sql' );
+ } else {
+ $this->output( "... column 'recentchanges.rc_cur_id' has a NOT NULL constraint\n" );
+ }
+ }
+
+ protected function checkPagelinkUniqueIndex() {
+ $pu = $this->describeIndex( 'pagelink_unique' );
+ if ( !is_null( $pu ) && ( $pu[0] != 'pl_from' || $pu[1] != 'pl_namespace' || $pu[2] != 'pl_title' ) ) {
+ $this->output( "Dropping obsolete version of index 'pagelink_unique index'\n" );
+ $this->db->query( 'DROP INDEX pagelink_unique' );
+ $pu = null;
+ } else {
+ $this->output( "... obsolete version of index 'pagelink_unique index' does not exist\n" );
+ }
+
+ if ( is_null( $pu ) ) {
+ $this->output( "Creating index 'pagelink_unique index'\n" );
+ $this->db->query( 'CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title)' );
+ } else {
+ $this->output( "... index 'pagelink_unique_index' already exists\n" );
+ }
+ }
+
+ protected function checkRevUserFkey() {
+ if ( $this->fkeyDeltype( 'revision_rev_user_fkey' ) == 'r' ) {
+ $this->output( "... constraint 'revision_rev_user_fkey' is ON DELETE RESTRICT\n" );
+ } else {
+ $this->output( "Changing constraint 'revision_rev_user_fkey' to ON DELETE RESTRICT\n" );
+ $this->applyPatch( 'patch-revision_rev_user_fkey.sql' );
+ }
+ }
+
+ protected function checkIpbAdress() {
+ if ( $this->db->indexExists( 'ipblocks', 'ipb_address' ) ) {
+ $this->output( "Removing deprecated index 'ipb_address'...\n" );
+ $this->db->query( 'DROP INDEX ipb_address' );
+ }
+ if ( $this->db->indexExists( 'ipblocks', 'ipb_address_unique' ) ) {
+ $this->output( "... have ipb_address_unique\n" );
+ } else {
+ $this->output( "Adding ipb_address_unique index\n" );
+ $this->applyPatch( 'patch-ipb_address_unique.sql' );
+ }
+ }
+
+ protected function checkIwlPrefix() {
+ if ( $this->db->indexExists( 'iwlinks', 'iwl_prefix' ) ) {
+ $this->output( "Replacing index 'iwl_prefix' with 'iwl_prefix_from_title'...\n" );
+ $this->applyPatch( 'patch-rename-iwl_prefix.sql' );
+ }
+ }
+
+ protected function tsearchFixes() {
+ # Tweak the page_title tsearch2 trigger to filter out slashes
+ # This is create or replace, so harmless to call if not needed
+ $this->applyPatch( 'patch-ts2pagetitle.sql' );
+
+ # If the server is 8.3 or higher, rewrite the tsearch2 triggers
+ # in case they have the old 'default' versions
+ # Gather version numbers in case we need them
+ if ( $this->db->getServerVersion() >= 8.3 ) {
+ $this->applyPatch( 'patch-tsearch2funcs.sql' );
+ }
+ }
+}
diff --git a/includes/installer/SqliteInstaller.php b/includes/installer/SqliteInstaller.php
new file mode 100644
index 00000000..2edb3d9b
--- /dev/null
+++ b/includes/installer/SqliteInstaller.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Sqlite-specific installer.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for setting up the MediaWiki database using SQLLite.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class SqliteInstaller extends DatabaseInstaller {
+
+ protected $globalNames = array(
+ 'wgDBname',
+ 'wgSQLiteDataDir',
+ );
+
+ public function getName() {
+ return 'sqlite';
+ }
+
+ public function isCompiled() {
+ return self::checkExtension( 'pdo_sqlite' );
+ }
+
+ public function getGlobalDefaults() {
+ if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
+ $path = str_replace(
+ array( '/', '\\' ),
+ DIRECTORY_SEPARATOR,
+ dirname( $_SERVER['DOCUMENT_ROOT'] ) . '/data'
+ );
+ return array( 'wgSQLiteDataDir' => $path );
+ } else {
+ return array();
+ }
+ }
+
+ public function getConnectForm() {
+ return $this->getTextBox( 'wgSQLiteDataDir', 'config-sqlite-dir', array(), $this->parent->getHelpBox( 'config-sqlite-dir-help' ) ) .
+ $this->getTextBox( 'wgDBname', 'config-db-name', array(), $this->parent->getHelpBox( 'config-sqlite-name-help' ) );
+ }
+
+ /*
+ * Safe wrapper for PHP's realpath() that fails gracefully if it's unable to canonicalize the path.
+ */
+ private static function realpath( $path ) {
+ $result = realpath( $path );
+ if ( !$result ) {
+ return $path;
+ }
+ return $result;
+ }
+
+ public function submitConnectForm() {
+ $this->setVarsFromRequest( array( 'wgSQLiteDataDir', 'wgDBname' ) );
+
+ # Try realpath() if the directory already exists
+ $dir = self::realpath( $this->getVar( 'wgSQLiteDataDir' ) );
+ $result = self::dataDirOKmaybeCreate( $dir, true /* create? */ );
+ if ( $result->isOK() )
+ {
+ # Try expanding again in case we've just created it
+ $dir = self::realpath( $dir );
+ $this->setVar( 'wgSQLiteDataDir', $dir );
+ }
+ return $result;
+ }
+
+ private static function dataDirOKmaybeCreate( $dir, $create = false ) {
+ if ( !is_dir( $dir ) ) {
+ if ( !is_writable( dirname( $dir ) ) ) {
+ $webserverGroup = Installer::maybeGetWebserverPrimaryGroup();
+ if ( $webserverGroup !== null ) {
+ return Status::newFatal( 'config-sqlite-parent-unwritable-group', $dir, dirname( $dir ), basename( $dir ), $webserverGroup );
+ } else {
+ return Status::newFatal( 'config-sqlite-parent-unwritable-nogroup', $dir, dirname( $dir ), basename( $dir ) );
+ }
+ }
+
+ # Called early on in the installer, later we just want to sanity check
+ # if it's still writable
+ if ( $create ) {
+ wfSuppressWarnings();
+ $ok = wfMkdirParents( $dir, 0700 );
+ wfRestoreWarnings();
+ if ( !$ok ) {
+ return Status::newFatal( 'config-sqlite-mkdir-error', $dir );
+ }
+ # Put a .htaccess file in in case the user didn't take our advice
+ file_put_contents( "$dir/.htaccess", "Deny from all\n" );
+ }
+ }
+ if ( !is_writable( $dir ) ) {
+ return Status::newFatal( 'config-sqlite-dir-unwritable', $dir );
+ }
+
+ # We haven't blown up yet, fall through
+ return Status::newGood();
+ }
+
+ public function openConnection() {
+ global $wgSQLiteDataDir;
+
+ $status = Status::newGood();
+ $dir = $this->getVar( 'wgSQLiteDataDir' );
+ $dbName = $this->getVar( 'wgDBname' );
+ try {
+ # 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 );
+ $status->value = $db;
+ } catch ( DBConnectionError $e ) {
+ $status->fatal( 'config-sqlite-connection-error', $e->getMessage() );
+ }
+ return $status;
+ }
+
+ public function needsUpgrade() {
+ $dir = $this->getVar( 'wgSQLiteDataDir' );
+ $dbName = $this->getVar( 'wgDBname' );
+ // Don't create the data file yet
+ if ( !file_exists( DatabaseSqlite::generateFileName( $dir, $dbName ) ) ) {
+ return false;
+ }
+
+ // If the data file exists, look inside it
+ return parent::needsUpgrade();
+ }
+
+ public function setupDatabase() {
+ $dir = $this->getVar( 'wgSQLiteDataDir' );
+
+ # Sanity check. We checked this before but maybe someone deleted the
+ # data dir between then and now
+ $dir_status = self::dataDirOKmaybeCreate( $dir, false /* create? */ );
+ if ( !$dir_status->isOK() ) {
+ return $dir_status;
+ }
+
+ $db = $this->getVar( 'wgDBname' );
+ $file = DatabaseSqlite::generateFileName( $dir, $db );
+ if ( file_exists( $file ) ) {
+ if ( !is_writable( $file ) ) {
+ return Status::newFatal( 'config-sqlite-readonly', $file );
+ }
+ } else {
+ if ( file_put_contents( $file, '' ) === false ) {
+ 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();
+ }
+
+ public function createTables() {
+ $status = parent::createTables();
+ return $this->setupSearchIndex( $status );
+ }
+
+ public function setupSearchIndex( &$status ) {
+ global $IP;
+
+ $module = DatabaseSqlite::getFulltextSearchModule();
+ $fts3tTable = $this->db->checkForEnabledSearch();
+ if ( $fts3tTable && !$module ) {
+ $status->warning( 'config-sqlite-fts3-downgrade' );
+ $this->db->sourceFile( "$IP/maintenance/sqlite/archives/searchindex-no-fts.sql" );
+ } elseif ( !$fts3tTable && $module == 'FTS3' ) {
+ $this->db->sourceFile( "$IP/maintenance/sqlite/archives/searchindex-fts3.sql" );
+ }
+ return $status;
+ }
+
+ public function getLocalSettings() {
+ $dir = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgSQLiteDataDir' ) );
+ return
+"# SQLite-specific settings
+\$wgSQLiteDataDir = \"{$dir}\";";
+ }
+}
diff --git a/includes/installer/SqliteUpdater.php b/includes/installer/SqliteUpdater.php
new file mode 100644
index 00000000..d1a6c20b
--- /dev/null
+++ b/includes/installer/SqliteUpdater.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Sqlite-specific updater.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for handling updates to Sqlite databases.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class SqliteUpdater extends DatabaseUpdater {
+
+ protected function getCoreUpdateList() {
+ return array(
+ // 1.14
+ array( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
+ array( 'doActiveUsersInit' ),
+ array( 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
+ array( 'sqliteInitialIndexes' ),
+
+ // 1.15
+ array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
+ array( 'addTable', 'tag_summary', 'patch-change_tag.sql' ),
+ array( 'addTable', 'valid_tag', 'patch-change_tag.sql' ),
+
+ // 1.16
+ array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
+ array( 'addTable', 'log_search', 'patch-log_search.sql' ),
+ array( 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ),
+ array( 'doLogUsertextPopulation' ), # listed separately from the previous update because 1.16 was released without this update
+ array( 'doLogSearchPopulation' ),
+ array( 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ),
+ array( 'addTable', 'external_user', 'patch-external_user.sql' ),
+ array( 'addIndex', 'log_search', 'ls_field_val', 'patch-log_search-rename-index.sql' ),
+ array( 'addIndex', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ),
+ array( 'addField', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ),
+ array( 'doUpdateTranscacheField' ),
+ array( 'sqliteSetupSearchindex' ),
+
+ // 1.17
+ array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
+ array( 'addIndex', 'iwlinks', 'iwl_prefix_title_from', 'patch-rename-iwl_prefix.sql' ),
+ array( 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ),
+ array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ),
+ array( 'dropIndex', 'iwlinks', 'iwl_prefix', 'patch-kill-iwl_prefix.sql' ),
+ array( 'dropIndex', 'iwlinks', 'iwl_prefix_from_title', 'patch-kill-iwl_pft.sql' ),
+ array( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
+ array( 'doCollationUpdate' ),
+ array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
+ array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
+ );
+ }
+
+ protected function sqliteInitialIndexes() {
+ // initial-indexes.sql fails if the indexes are already present, so we perform a quick check if our database is newer.
+ if ( $this->updateRowExists( 'initial_indexes' ) || $this->db->indexExists( 'user', 'user_name' ) ) {
+ $this->output( "...have initial indexes\n" );
+ return;
+ }
+ $this->output( "Adding initial indexes..." );
+ $this->applyPatch( 'initial-indexes.sql' );
+ $this->output( "done\n" );
+ }
+
+ protected function sqliteSetupSearchindex() {
+ $module = DatabaseSqlite::getFulltextSearchModule();
+ $fts3tTable = $this->updateRowExists( 'fts3' );
+ if ( $fts3tTable && !$module ) {
+ $this->output( '...PHP is missing FTS3 support, downgrading tables...' );
+ $this->applyPatch( 'searchindex-no-fts.sql' );
+ $this->output( "done\n" );
+ } elseif ( !$fts3tTable && $module == 'FTS3' ) {
+ $this->output( '...adding FTS3 search capabilities...' );
+ $this->applyPatch( 'searchindex-fts3.sql' );
+ $this->output( "done\n" );
+ } else {
+ $this->output( "...fulltext search table appears to be in order.\n" );
+ }
+ }
+}
diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php
new file mode 100644
index 00000000..b75db74e
--- /dev/null
+++ b/includes/installer/WebInstaller.php
@@ -0,0 +1,1034 @@
+<?php
+/**
+ * Core installer web interface.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for the core installer web interface.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class WebInstaller extends Installer {
+
+ /**
+ * @var WebInstallerOutput
+ */
+ public $output;
+
+ /**
+ * WebRequest object.
+ *
+ * @var WebRequest
+ */
+ public $request;
+
+ /**
+ * Cached session array.
+ *
+ * @var array
+ */
+ protected $session;
+
+ /**
+ * Captured PHP error text. Temporary.
+ * @var array
+ */
+ protected $phpErrors;
+
+ /**
+ * The main sequence of page names. These will be displayed in turn.
+ * To add one:
+ * * Add it here
+ * * Add a config-page-<name> message
+ * * Add a WebInstaller_<name> class
+ * @var array
+ */
+ public $pageSequence = array(
+ 'Language',
+ 'ExistingWiki',
+ 'Welcome',
+ 'DBConnect',
+ 'Upgrade',
+ 'DBSettings',
+ 'Name',
+ 'Options',
+ 'Install',
+ 'Complete',
+ );
+
+ /**
+ * Out of sequence pages, selectable by the user at any time.
+ * @var array
+ */
+ protected $otherPages = array(
+ 'Restart',
+ 'Readme',
+ 'ReleaseNotes',
+ 'Copying',
+ 'UpgradeDoc', // Can't use Upgrade due to Upgrade step
+ );
+
+ /**
+ * Array of pages which have declared that they have been submitted, have validated
+ * their input, and need no further processing.
+ * @var array
+ */
+ protected $happyPages;
+
+ /**
+ * List of "skipped" pages. These are pages that will automatically continue
+ * to the next page on any GET request. To avoid breaking the "back" button,
+ * they need to be skipped during a back operation.
+ * @var array
+ */
+ protected $skippedPages;
+
+ /**
+ * Flag indicating that session data may have been lost.
+ * @var bool
+ */
+ public $showSessionWarning = false;
+
+ /**
+ * Numeric index of the page we're on
+ * @var int
+ */
+ protected $tabIndex = 1;
+
+ /**
+ * Name of the page we're on
+ * @var string
+ */
+ protected $currentPageName;
+
+ /**
+ * Constructor.
+ *
+ * @param $request WebRequest
+ */
+ public function __construct( WebRequest $request ) {
+ parent::__construct();
+ $this->output = new WebInstallerOutput( $this );
+ $this->request = $request;
+
+ // Add parser hooks
+ global $wgParser;
+ $wgParser->setHook( 'downloadlink', array( $this, 'downloadLinkHook' ) );
+ $wgParser->setHook( 'doclink', array( $this, 'docLink' ) );
+ }
+
+ /**
+ * Main entry point.
+ *
+ * @param $session Array: initial session array
+ *
+ * @return Array: new session array
+ */
+ public function execute( array $session ) {
+ $this->session = $session;
+
+ if ( isset( $session['settings'] ) ) {
+ $this->settings = $session['settings'] + $this->settings;
+ }
+
+ $this->exportVars();
+ $this->setupLanguage();
+
+ if( ( $this->getVar( '_InstallDone' ) || $this->getVar( '_UpgradeDone' ) )
+ && $this->request->getVal( 'localsettings' ) )
+ {
+ $this->request->response()->header( 'Content-type: application/x-httpd-php' );
+ $this->request->response()->header(
+ 'Content-Disposition: attachment; filename="LocalSettings.php"'
+ );
+
+ $ls = new LocalSettingsGenerator( $this );
+ $rightsProfile = $this->rightsProfiles[$this->getVar( '_RightsProfile' )];
+ foreach( $rightsProfile as $group => $rightsArr ) {
+ $ls->setGroupRights( $group, $rightsArr );
+ }
+ echo $ls->getText();
+ return $this->session;
+ }
+
+ $cssDir = $this->request->getVal( 'css' );
+ if( $cssDir ) {
+ $cssDir = ( $cssDir == 'rtl' ? 'rtl' : 'ltr' );
+ $this->request->response()->header( 'Content-type: text/css' );
+ echo $this->output->getCSS( $cssDir );
+ return $this->session;
+ }
+
+ if ( isset( $session['happyPages'] ) ) {
+ $this->happyPages = $session['happyPages'];
+ } else {
+ $this->happyPages = array();
+ }
+
+ if ( isset( $session['skippedPages'] ) ) {
+ $this->skippedPages = $session['skippedPages'];
+ } else {
+ $this->skippedPages = array();
+ }
+
+ $lowestUnhappy = $this->getLowestUnhappy();
+
+ # Special case for Creative Commons partner chooser box.
+ if ( $this->request->getVal( 'SubmitCC' ) ) {
+ $page = $this->getPageByName( 'Options' );
+ $this->output->useShortHeader();
+ $this->output->allowFrames();
+ $page->submitCC();
+ return $this->finish();
+ }
+
+ if ( $this->request->getVal( 'ShowCC' ) ) {
+ $page = $this->getPageByName( 'Options' );
+ $this->output->useShortHeader();
+ $this->output->allowFrames();
+ $this->output->addHTML( $page->getCCDoneBox() );
+ return $this->finish();
+ }
+
+ # Get the page name.
+ $pageName = $this->request->getVal( 'page' );
+
+ if ( in_array( $pageName, $this->otherPages ) ) {
+ # Out of sequence
+ $pageId = false;
+ $page = $this->getPageByName( $pageName );
+ } else {
+ # Main sequence
+ if ( !$pageName || !in_array( $pageName, $this->pageSequence ) ) {
+ $pageId = $lowestUnhappy;
+ } else {
+ $pageId = array_search( $pageName, $this->pageSequence );
+ }
+
+ # If necessary, move back to the lowest-numbered unhappy page
+ if ( $pageId > $lowestUnhappy ) {
+ $pageId = $lowestUnhappy;
+ if ( $lowestUnhappy == 0 ) {
+ # Knocked back to start, possible loss of session data.
+ $this->showSessionWarning = true;
+ }
+ }
+
+ $pageName = $this->pageSequence[$pageId];
+ $page = $this->getPageByName( $pageName );
+ }
+
+ # If a back button was submitted, go back without submitting the form data.
+ if ( $this->request->wasPosted() && $this->request->getBool( 'submit-back' ) ) {
+ if ( $this->request->getVal( 'lastPage' ) ) {
+ $nextPage = $this->request->getVal( 'lastPage' );
+ } elseif ( $pageId !== false ) {
+ # Main sequence page
+ # Skip the skipped pages
+ $nextPageId = $pageId;
+
+ do {
+ $nextPageId--;
+ $nextPage = $this->pageSequence[$nextPageId];
+ } while( isset( $this->skippedPages[$nextPage] ) );
+ } else {
+ $nextPage = $this->pageSequence[$lowestUnhappy];
+ }
+
+ $this->output->redirect( $this->getUrl( array( 'page' => $nextPage ) ) );
+ return $this->finish();
+ }
+
+ # Execute the page.
+ $this->currentPageName = $page->getName();
+ $this->startPageWrapper( $pageName );
+
+ $result = $page->execute();
+
+ $this->endPageWrapper();
+
+ if ( $result == 'skip' ) {
+ # Page skipped without explicit submission.
+ # Skip it when we click "back" so that we don't just go forward again.
+ $this->skippedPages[$pageName] = true;
+ $result = 'continue';
+ } else {
+ unset( $this->skippedPages[$pageName] );
+ }
+
+ # If it was posted, the page can request a continue to the next page.
+ if ( $result === 'continue' && !$this->output->headerDone() ) {
+ if ( $pageId !== false ) {
+ $this->happyPages[$pageId] = true;
+ }
+
+ $lowestUnhappy = $this->getLowestUnhappy();
+
+ if ( $this->request->getVal( 'lastPage' ) ) {
+ $nextPage = $this->request->getVal( 'lastPage' );
+ } elseif ( $pageId !== false ) {
+ $nextPage = $this->pageSequence[$pageId + 1];
+ } else {
+ $nextPage = $this->pageSequence[$lowestUnhappy];
+ }
+
+ if ( array_search( $nextPage, $this->pageSequence ) > $lowestUnhappy ) {
+ $nextPage = $this->pageSequence[$lowestUnhappy];
+ }
+
+ $this->output->redirect( $this->getUrl( array( 'page' => $nextPage ) ) );
+ }
+
+ return $this->finish();
+ }
+
+ /**
+ * Find the next page in sequence that hasn't been completed
+ * @return int
+ */
+ public function getLowestUnhappy() {
+ if ( count( $this->happyPages ) == 0 ) {
+ return 0;
+ } else {
+ return max( array_keys( $this->happyPages ) ) + 1;
+ }
+ }
+
+ /**
+ * Start the PHP session. This may be called before execute() to start the PHP session.
+ */
+ public function startSession() {
+ if( wfIniGetBool( 'session.auto_start' ) || session_id() ) {
+ // Done already
+ return true;
+ }
+
+ $this->phpErrors = array();
+ set_error_handler( array( $this, 'errorHandler' ) );
+ session_start();
+ restore_error_handler();
+
+ if ( $this->phpErrors ) {
+ $this->showError( 'config-session-error', $this->phpErrors[0] );
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get a hash of data identifying this MW installation.
+ *
+ * This is used by mw-config/index.php to prevent multiple installations of MW
+ * on the same cookie domain from interfering with each other.
+ */
+ public function getFingerprint() {
+ // Get the base URL of the installation
+ $url = $this->request->getFullRequestURL();
+ if ( preg_match( '!^(.*\?)!', $url, $m) ) {
+ // Trim query string
+ $url = $m[1];
+ }
+ if ( preg_match( '!^(.*)/[^/]*/[^/]*$!', $url, $m ) ) {
+ // This... seems to try to get the base path from
+ // the /mw-config/index.php. Kinda scary though?
+ $url = $m[1];
+ }
+ return md5( serialize( array(
+ 'local path' => dirname( dirname( __FILE__ ) ),
+ 'url' => $url,
+ 'version' => $GLOBALS['wgVersion']
+ ) ) );
+ }
+
+ /**
+ * Show an error message in a box. Parameters are like wfMsg().
+ */
+ public function showError( $msg /*...*/ ) {
+ $args = func_get_args();
+ array_shift( $args );
+ $args = array_map( 'htmlspecialchars', $args );
+ $msg = wfMsgReal( $msg, $args, false, false, false );
+ $this->output->addHTML( $this->getErrorBox( $msg ) );
+ }
+
+ /**
+ * Temporary error handler for session start debugging.
+ */
+ public function errorHandler( $errno, $errstr ) {
+ $this->phpErrors[] = $errstr;
+ }
+
+ /**
+ * Clean up from execute()
+ *
+ * @return array
+ */
+ public function finish() {
+ $this->output->output();
+
+ $this->session['happyPages'] = $this->happyPages;
+ $this->session['skippedPages'] = $this->skippedPages;
+ $this->session['settings'] = $this->settings;
+
+ return $this->session;
+ }
+
+ /**
+ * We're restarting the installation, reset the session, happyPages, etc
+ */
+ public function reset() {
+ $this->session = array();
+ $this->happyPages = array();
+ $this->settings = array();
+ }
+
+ /**
+ * Get a URL for submission back to the same script.
+ *
+ * @param $query: Array
+ * @return string
+ */
+ public function getUrl( $query = array() ) {
+ $url = $this->request->getRequestURL();
+ # Remove existing query
+ $url = preg_replace( '/\?.*$/', '', $url );
+
+ if ( $query ) {
+ $url .= '?' . wfArrayToCGI( $query );
+ }
+
+ return $url;
+ }
+
+ /**
+ * Get a WebInstallerPage by name.
+ *
+ * @param $pageName String
+ * @return WebInstallerPage
+ */
+ public function getPageByName( $pageName ) {
+ // Totally lame way to force autoload of WebInstallerPage.php
+ class_exists( 'WebInstallerPage' );
+
+ $pageClass = 'WebInstaller_' . $pageName;
+
+ return new $pageClass( $this );
+ }
+
+ /**
+ * Get a session variable.
+ *
+ * @param $name String
+ * @param $default
+ */
+ public function getSession( $name, $default = null ) {
+ if ( !isset( $this->session[$name] ) ) {
+ return $default;
+ } else {
+ return $this->session[$name];
+ }
+ }
+
+ /**
+ * Set a session variable.
+ * @param $name String key for the variable
+ * @param $value Mixed
+ */
+ public function setSession( $name, $value ) {
+ $this->session[$name] = $value;
+ }
+
+ /**
+ * Get the next tabindex attribute value.
+ * @return int
+ */
+ public function nextTabIndex() {
+ return $this->tabIndex++;
+ }
+
+ /**
+ * Initializes language-related variables.
+ */
+ public function setupLanguage() {
+ global $wgLang, $wgContLang, $wgLanguageCode;
+
+ if ( $this->getSession( 'test' ) === null && !$this->request->wasPosted() ) {
+ $wgLanguageCode = $this->getAcceptLanguage();
+ $wgLang = $wgContLang = Language::factory( $wgLanguageCode );
+ $this->setVar( 'wgLanguageCode', $wgLanguageCode );
+ $this->setVar( '_UserLang', $wgLanguageCode );
+ } else {
+ $wgLanguageCode = $this->getVar( 'wgLanguageCode' );
+ $wgLang = Language::factory( $this->getVar( '_UserLang' ) );
+ $wgContLang = Language::factory( $wgLanguageCode );
+ }
+ }
+
+ /**
+ * Retrieves MediaWiki language from Accept-Language HTTP header.
+ *
+ * @return string
+ */
+ public function getAcceptLanguage() {
+ global $wgLanguageCode, $wgRequest;
+
+ $mwLanguages = Language::getLanguageNames();
+ $headerLanguages = array_keys( $wgRequest->getAcceptLang() );
+
+ foreach ( $headerLanguages as $lang ) {
+ if ( isset( $mwLanguages[$lang] ) ) {
+ return $lang;
+ }
+ }
+
+ return $wgLanguageCode;
+ }
+
+ /**
+ * Called by execute() before page output starts, to show a page list.
+ *
+ * @param $currentPageName String
+ */
+ private function startPageWrapper( $currentPageName ) {
+ $s = "<div class=\"config-page-wrapper\">\n";
+ $s .= "<div class=\"config-page\">\n";
+ $s .= "<div class=\"config-page-list\"><ul>\n";
+ $lastHappy = -1;
+
+ foreach ( $this->pageSequence as $id => $pageName ) {
+ $happy = !empty( $this->happyPages[$id] );
+ $s .= $this->getPageListItem(
+ $pageName,
+ $happy || $lastHappy == $id - 1,
+ $currentPageName
+ );
+
+ if ( $happy ) {
+ $lastHappy = $id;
+ }
+ }
+
+ $s .= "</ul><br/><ul>\n";
+ $s .= $this->getPageListItem( 'Restart', true, $currentPageName );
+ $s .= "</ul></div>\n"; // end list pane
+ $s .= Html::element( 'h2', array(),
+ wfMsg( 'config-page-' . strtolower( $currentPageName ) ) );
+
+ $this->output->addHTMLNoFlush( $s );
+ }
+
+ /**
+ * Get a list item for the page list.
+ *
+ * @param $pageName String
+ * @param $enabled Boolean
+ * @param $currentPageName String
+ *
+ * @return string
+ */
+ private function getPageListItem( $pageName, $enabled, $currentPageName ) {
+ $s = "<li class=\"config-page-list-item\">";
+ $name = wfMsg( 'config-page-' . strtolower( $pageName ) );
+
+ if ( $enabled ) {
+ $query = array( 'page' => $pageName );
+
+ if ( !in_array( $pageName, $this->pageSequence ) ) {
+ if ( in_array( $currentPageName, $this->pageSequence ) ) {
+ $query['lastPage'] = $currentPageName;
+ }
+
+ $link = Html::element( 'a',
+ array(
+ 'href' => $this->getUrl( $query )
+ ),
+ $name
+ );
+ } else {
+ $link = htmlspecialchars( $name );
+ }
+
+ if ( $pageName == $currentPageName ) {
+ $s .= "<span class=\"config-page-current\">$link</span>";
+ } else {
+ $s .= $link;
+ }
+ } else {
+ $s .= Html::element( 'span',
+ array(
+ 'class' => 'config-page-disabled'
+ ),
+ $name
+ );
+ }
+
+ $s .= "</li>\n";
+
+ return $s;
+ }
+
+ /**
+ * Output some stuff after a page is finished.
+ */
+ private function endPageWrapper() {
+ $this->output->addHTMLNoFlush(
+ "<div class=\"visualClear\"></div>\n" .
+ "</div>\n" .
+ "<div class=\"visualClear\"></div>\n" .
+ "</div>" );
+ }
+
+ /**
+ * Get HTML for an error box with an icon.
+ *
+ * @param $text String: wikitext, get this with wfMsgNoTrans()
+ */
+ public function getErrorBox( $text ) {
+ return $this->getInfoBox( $text, 'critical-32.png', 'config-error-box' );
+ }
+
+ /**
+ * Get HTML for a warning box with an icon.
+ *
+ * @param $text String: wikitext, get this with wfMsgNoTrans()
+ */
+ public function getWarningBox( $text ) {
+ return $this->getInfoBox( $text, 'warning-32.png', 'config-warning-box' );
+ }
+
+ /**
+ * Get HTML for an info box with an icon.
+ *
+ * @param $text String: wikitext, get this with wfMsgNoTrans()
+ * @param $icon String: icon name, file in skins/common/images
+ * @param $class String: additional class name to add to the wrapper div
+ */
+ public function getInfoBox( $text, $icon = 'info-32.png', $class = false ) {
+ $s =
+ "<div class=\"config-info $class\">\n" .
+ "<div class=\"config-info-left\">\n" .
+ Html::element( 'img',
+ array(
+ 'src' => '../skins/common/images/' . $icon,
+ 'alt' => wfMsg( 'config-information' ),
+ )
+ ) . "\n" .
+ "</div>\n" .
+ "<div class=\"config-info-right\">\n" .
+ $this->parse( $text, true ) . "\n" .
+ "</div>\n" .
+ "<div style=\"clear: left;\"></div>\n" .
+ "</div>\n";
+ return $s;
+ }
+
+ /**
+ * Get small text indented help for a preceding form field.
+ * Parameters like wfMsg().
+ */
+ public function getHelpBox( $msg /*, ... */ ) {
+ $args = func_get_args();
+ array_shift( $args );
+ $args = array_map( 'htmlspecialchars', $args );
+ $text = wfMsgReal( $msg, $args, false, false, false );
+ $html = htmlspecialchars( $text );
+ $html = $this->parse( $text, true );
+
+ return "<div class=\"mw-help-field-container\">\n" .
+ "<span class=\"mw-help-field-hint\">" . wfMsgHtml( 'config-help' ) . "</span>\n" .
+ "<span class=\"mw-help-field-data\">" . $html . "</span>\n" .
+ "</div>\n";
+ }
+
+ /**
+ * Output a help box.
+ * @param $msg String key for wfMsg()
+ */
+ public function showHelpBox( $msg /*, ... */ ) {
+ $args = func_get_args();
+ $html = call_user_func_array( array( $this, 'getHelpBox' ), $args );
+ $this->output->addHTML( $html );
+ }
+
+ /**
+ * Show a short informational message.
+ * Output looks like a list.
+ *
+ * @param $msg string
+ */
+ public function showMessage( $msg /*, ... */ ) {
+ $args = func_get_args();
+ array_shift( $args );
+ $html = '<div class="config-message">' .
+ $this->parse( wfMsgReal( $msg, $args, false, false, false ) ) .
+ "</div>\n";
+ $this->output->addHTML( $html );
+ }
+
+ /**
+ * @param $status Status
+ */
+ public function showStatusMessage( Status $status ) {
+ $text = $status->getWikiText();
+ $this->output->addWikiText(
+ "<div class=\"config-message\">\n" .
+ $text .
+ "</div>"
+ );
+ }
+
+ /**
+ * Label a control by wrapping a config-input div around it and putting a
+ * label before it.
+ */
+ public function label( $msg, $forId, $contents, $helpData = "" ) {
+ if ( strval( $msg ) == '' ) {
+ $labelText = '&#160;';
+ } else {
+ $labelText = wfMsgHtml( $msg );
+ }
+
+ $attributes = array( 'class' => 'config-label' );
+
+ if ( $forId ) {
+ $attributes['for'] = $forId;
+ }
+
+ return
+ "<div class=\"config-block\">\n" .
+ " <div class=\"config-block-label\">\n" .
+ Xml::tags( 'label',
+ $attributes,
+ $labelText ) . "\n" .
+ $helpData .
+ " </div>\n" .
+ " <div class=\"config-block-elements\">\n" .
+ $contents .
+ " </div>\n" .
+ "</div>\n";
+ }
+
+ /**
+ * Get a labelled text box to configure a variable.
+ *
+ * @param $params Array
+ * Parameters are:
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * attribs: Additional attributes for the input element (optional)
+ * controlName: The name for the input element (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
+ */
+ public function getTextBox( $params ) {
+ if ( !isset( $params['controlName'] ) ) {
+ $params['controlName'] = 'config_' . $params['var'];
+ }
+
+ if ( !isset( $params['value'] ) ) {
+ $params['value'] = $this->getVar( $params['var'] );
+ }
+
+ if ( !isset( $params['attribs'] ) ) {
+ $params['attribs'] = array();
+ }
+ if ( !isset( $params['help'] ) ) {
+ $params['help'] = "";
+ }
+ return
+ $this->label(
+ $params['label'],
+ $params['controlName'],
+ Xml::input(
+ $params['controlName'],
+ 30, // intended to be overridden by CSS
+ $params['value'],
+ $params['attribs'] + array(
+ 'id' => $params['controlName'],
+ 'class' => 'config-input-text',
+ 'tabindex' => $this->nextTabIndex()
+ )
+ ),
+ $params['help']
+ );
+ }
+
+ /**
+ * Get a labelled textarea to configure a variable
+ *
+ * @param $params Array
+ * Parameters are:
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * attribs: Additional attributes for the input element (optional)
+ * controlName: The name for the input element (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
+ */
+ public function getTextArea( $params ) {
+ if ( !isset( $params['controlName'] ) ) {
+ $params['controlName'] = 'config_' . $params['var'];
+ }
+
+ if ( !isset( $params['value'] ) ) {
+ $params['value'] = $this->getVar( $params['var'] );
+ }
+
+ if ( !isset( $params['attribs'] ) ) {
+ $params['attribs'] = array();
+ }
+ if ( !isset( $params['help'] ) ) {
+ $params['help'] = "";
+ }
+ return
+ $this->label(
+ $params['label'],
+ $params['controlName'],
+ Xml::textarea(
+ $params['controlName'],
+ $params['value'],
+ 30,
+ 5,
+ $params['attribs'] + array(
+ 'id' => $params['controlName'],
+ 'class' => 'config-input-text',
+ 'tabindex' => $this->nextTabIndex()
+ )
+ ),
+ $params['help']
+ );
+ }
+
+ /**
+ * Get a labelled password box to configure a variable.
+ *
+ * Implements password hiding
+ * @param $params Array
+ * Parameters are:
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * attribs: Additional attributes for the input element (optional)
+ * controlName: The name for the input element (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
+ */
+ public function getPasswordBox( $params ) {
+ if ( !isset( $params['value'] ) ) {
+ $params['value'] = $this->getVar( $params['var'] );
+ }
+
+ if ( !isset( $params['attribs'] ) ) {
+ $params['attribs'] = array();
+ }
+
+ $params['value'] = $this->getFakePassword( $params['value'] );
+ $params['attribs']['type'] = 'password';
+
+ return $this->getTextBox( $params );
+ }
+
+ /**
+ * Get a labelled checkbox to configure a boolean variable.
+ *
+ * @param $params Array
+ * Parameters are:
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * attribs: Additional attributes for the input element (optional)
+ * controlName: The name for the input element (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
+ */
+ public function getCheckBox( $params ) {
+ if ( !isset( $params['controlName'] ) ) {
+ $params['controlName'] = 'config_' . $params['var'];
+ }
+
+ if ( !isset( $params['value'] ) ) {
+ $params['value'] = $this->getVar( $params['var'] );
+ }
+
+ if ( !isset( $params['attribs'] ) ) {
+ $params['attribs'] = array();
+ }
+ if ( !isset( $params['help'] ) ) {
+ $params['help'] = "";
+ }
+ if( isset( $params['rawtext'] ) ) {
+ $labelText = $params['rawtext'];
+ } else {
+ $labelText = $this->parse( wfMsg( $params['label'] ) );
+ }
+
+ return
+ "<div class=\"config-input-check\">\n" .
+ $params['help'] .
+ "<label>\n" .
+ Xml::check(
+ $params['controlName'],
+ $params['value'],
+ $params['attribs'] + array(
+ 'id' => $params['controlName'],
+ 'tabindex' => $this->nextTabIndex(),
+ )
+ ) .
+ $labelText . "\n" .
+ "</label>\n" .
+ "</div>\n";
+ }
+
+ /**
+ * Get a set of labelled radio buttons.
+ *
+ * @param $params Array
+ * Parameters are:
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * itemLabelPrefix: The message name prefix for the item labels (required)
+ * values: List of allowed values (required)
+ * itemAttribs Array of attribute arrays, outer key is the value name (optional)
+ * commonAttribs Attribute array applied to all items
+ * controlName: The name for the input element (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
+ */
+ public function getRadioSet( $params ) {
+ if ( !isset( $params['controlName'] ) ) {
+ $params['controlName'] = 'config_' . $params['var'];
+ }
+
+ if ( !isset( $params['value'] ) ) {
+ $params['value'] = $this->getVar( $params['var'] );
+ }
+
+ if ( !isset( $params['label'] ) ) {
+ $label = '';
+ } else {
+ $label = $params['label'];
+ }
+ if ( !isset( $params['help'] ) ) {
+ $params['help'] = "";
+ }
+ $s = "<ul>\n";
+ foreach ( $params['values'] as $value ) {
+ $itemAttribs = array();
+
+ if ( isset( $params['commonAttribs'] ) ) {
+ $itemAttribs = $params['commonAttribs'];
+ }
+
+ if ( isset( $params['itemAttribs'][$value] ) ) {
+ $itemAttribs = $params['itemAttribs'][$value] + $itemAttribs;
+ }
+
+ $checked = $value == $params['value'];
+ $id = $params['controlName'] . '_' . $value;
+ $itemAttribs['id'] = $id;
+ $itemAttribs['tabindex'] = $this->nextTabIndex();
+
+ $s .=
+ '<li>' .
+ Xml::radio( $params['controlName'], $value, $checked, $itemAttribs ) .
+ '&#160;' .
+ Xml::tags( 'label', array( 'for' => $id ), $this->parse(
+ wfMsgNoTrans( $params['itemLabelPrefix'] . strtolower( $value ) )
+ ) ) .
+ "</li>\n";
+ }
+
+ $s .= "</ul>\n";
+
+ return $this->label( $label, $params['controlName'], $s, $params['help'] );
+ }
+
+ /**
+ * Output an error or warning box using a Status object.
+ */
+ public function showStatusBox( $status ) {
+ if( !$status->isGood() ) {
+ $text = $status->getWikiText();
+
+ if( $status->isOk() ) {
+ $box = $this->getWarningBox( $text );
+ } else {
+ $box = $this->getErrorBox( $text );
+ }
+
+ $this->output->addHTML( $box );
+ }
+ }
+
+ /**
+ * Convenience function to set variables based on form data.
+ * Assumes that variables containing "password" in the name are (potentially
+ * fake) passwords.
+ *
+ * @param $varNames Array
+ * @param $prefix String: the prefix added to variables to obtain form names
+ */
+ public function setVarsFromRequest( $varNames, $prefix = 'config_' ) {
+ $newValues = array();
+
+ foreach ( $varNames as $name ) {
+ $value = trim( $this->request->getVal( $prefix . $name ) );
+ $newValues[$name] = $value;
+
+ if ( $value === null ) {
+ // Checkbox?
+ $this->setVar( $name, false );
+ } else {
+ if ( stripos( $name, 'password' ) !== false ) {
+ $this->setPassword( $name, $value );
+ } else {
+ $this->setVar( $name, $value );
+ }
+ }
+ }
+
+ return $newValues;
+ }
+
+ /**
+ * Helper for Installer::docLink()
+ */
+ protected function getDocUrl( $page ) {
+ $url = "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
+
+ if ( in_array( $this->currentPageName, $this->pageSequence ) ) {
+ $url .= '&lastPage=' . urlencode( $this->currentPageName );
+ }
+
+ return $url;
+ }
+
+ /**
+ * Extension tag hook for a documentation link.
+ */
+ public function docLink( $linkText, $attribs, $parser ) {
+ $url = $this->getDocUrl( $attribs['href'] );
+ return '<a href="' . htmlspecialchars( $url ) . '">' .
+ htmlspecialchars( $linkText ) .
+ '</a>';
+ }
+
+ /**
+ * Helper for "Download LocalSettings" link on WebInstall_Complete
+ * @return String Html for download link
+ */
+ public function downloadLinkHook( $text, $attribs, $parser ) {
+ $img = Html::element( 'img', array(
+ 'src' => '../skins/common/images/download-32.png',
+ 'width' => '32',
+ 'height' => '32',
+ ) );
+ $anchor = Html::rawElement( 'a',
+ array( 'href' => $this->getURL( array( 'localsettings' => 1 ) ) ),
+ $img . ' ' . wfMsgHtml( 'config-download-localsettings' ) );
+ return Html::rawElement( 'div', array( 'class' => 'config-download-link' ), $anchor );
+ }
+}
diff --git a/includes/installer/WebInstallerOutput.php b/includes/installer/WebInstallerOutput.php
new file mode 100644
index 00000000..cb708d13
--- /dev/null
+++ b/includes/installer/WebInstallerOutput.php
@@ -0,0 +1,269 @@
+<?php
+/**
+ * Output handler for the web installer.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Output class modelled on OutputPage.
+ *
+ * I've opted to use a distinct class rather than derive from OutputPage here in
+ * the interests of separation of concerns: if we used a subclass, there would be
+ * quite a lot of things you could do in OutputPage that would break the installer,
+ * that wouldn't be immediately obvious.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class WebInstallerOutput {
+ /**
+ * The WebInstaller object this WebInstallerOutput is used by.
+ *
+ * @var WebInstaller
+ */
+ public $parent;
+
+ /**
+ * Buffered contents that haven't been output yet
+ * @var String
+ */
+ private $contents = '';
+
+ /**
+ * Has the header (or short header) been output?
+ * @var bool
+ */
+ private $headerDone = false;
+
+ public $redirectTarget;
+
+ /**
+ * Does the current page need to allow being used as a frame?
+ * If not, X-Frame-Options will be output to forbid it.
+ *
+ * @var bool
+ */
+ public $allowFrames = false;
+
+ /**
+ * Whether to use the limited header (used during CC license callbacks)
+ * @var bool
+ */
+ private $useShortHeader = false;
+
+ /**
+ * Constructor.
+ *
+ * @param $parent WebInstaller
+ */
+ public function __construct( WebInstaller $parent ) {
+ $this->parent = $parent;
+ }
+
+ public function addHTML( $html ) {
+ $this->contents .= $html;
+ $this->flush();
+ }
+
+ public function addWikiText( $text ) {
+ $this->addHTML( $this->parent->parse( $text ) );
+ }
+
+ public function addHTMLNoFlush( $html ) {
+ $this->contents .= $html;
+ }
+
+ public function redirect( $url ) {
+ if ( $this->headerDone ) {
+ throw new MWException( __METHOD__ . ' called after sending headers' );
+ }
+ $this->redirectTarget = $url;
+ }
+
+ public function output() {
+ $this->flush();
+ $this->outputFooter();
+ }
+
+ /**
+ * Get the raw vector CSS, flipping if needed
+ * @param $dir String 'ltr' or 'rtl'
+ * @return String
+ */
+ public function getCSS( $dir ) {
+ $skinDir = dirname( dirname( dirname( __FILE__ ) ) ) . '/skins';
+ $vectorCssFile = "$skinDir/vector/screen.css";
+ $configCssFile = "$skinDir/common/config.css";
+ $css = '';
+ wfSuppressWarnings();
+ $vectorCss = file_get_contents( $vectorCssFile );
+ $configCss = file_get_contents( $configCssFile );
+ wfRestoreWarnings();
+ if( !$vectorCss || !$configCss ) {
+ $css = "/** Your webserver cannot read $vectorCssFile or $configCssFile, please check file permissions */";
+ }
+
+ $css .= str_replace( 'images/', '../skins/vector/images/', $vectorCss ) . "\n" . str_replace( 'images/', '../skins/common/images/', $configCss );
+ if( $dir == 'rtl' ) {
+ $css = CSSJanus::transform( $css, true );
+ }
+ return $css;
+ }
+
+ /**
+ * URL for index.php?css=foobar
+ * @return String
+ */
+ private function getCssUrl( ) {
+ return $_SERVER['PHP_SELF'] . '?css=' . $this->getDir();
+ }
+
+ public function useShortHeader( $use = true ) {
+ $this->useShortHeader = $use;
+ }
+
+ public function allowFrames( $allow = true ) {
+ $this->allowFrames = $allow;
+ }
+
+ public function flush() {
+ if ( !$this->headerDone ) {
+ $this->outputHeader();
+ }
+ if ( !$this->redirectTarget && strlen( $this->contents ) ) {
+ echo $this->contents;
+ flush();
+ $this->contents = '';
+ }
+ }
+
+ public function getDir() {
+ global $wgLang;
+ if( !is_object( $wgLang ) || !$wgLang->isRtl() )
+ return 'ltr';
+ else
+ return 'rtl';
+ }
+
+ public function getLanguageCode() {
+ global $wgLang;
+ if( !is_object( $wgLang ) )
+ return 'en';
+ else
+ return $wgLang->getCode();
+ }
+
+ public function getHeadAttribs() {
+ return array(
+ 'dir' => $this->getDir(),
+ 'lang' => $this->getLanguageCode(),
+ );
+ }
+
+ /**
+ * Get whether the header has been output
+ * @return bool
+ */
+ public function headerDone() {
+ return $this->headerDone;
+ }
+
+ public function outputHeader() {
+ $this->headerDone = true;
+ $dbTypes = $this->parent->getDBTypes();
+
+ $this->parent->request->response()->header( 'Content-Type: text/html; charset=utf-8' );
+ if (!$this->allowFrames) {
+ $this->parent->request->response()->header( 'X-Frame-Options: DENY' );
+ }
+ if ( $this->redirectTarget ) {
+ $this->parent->request->response()->header( 'Location: '.$this->redirectTarget );
+ return;
+ }
+
+ if ( $this->useShortHeader ) {
+ $this->outputShortHeader();
+ return;
+ }
+
+?>
+<?php echo Html::htmlHeader( $this->getHeadAttribs() ); ?>
+<head>
+ <meta name="robots" content="noindex, nofollow" />
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <title><?php $this->outputTitle(); ?></title>
+ <?php echo Html::linkedStyle( '../skins/common/shared.css' ) . "\n"; ?>
+ <?php echo Html::linkedStyle( $this->getCssUrl() ) . "\n"; ?>
+ <?php echo Html::inlineScript( "var dbTypes = " . Xml::encodeJsVar( $dbTypes ) ) . "\n"; ?>
+ <?php echo $this->getJQuery() . "\n"; ?>
+ <?php echo Html::linkedScript( '../skins/common/config.js' ) . "\n"; ?>
+</head>
+
+<?php echo Html::openElement( 'body', array( 'class' => $this->getDir() ) ) . "\n"; ?>
+<div id="mw-page-base"></div>
+<div id="mw-head-base"></div>
+<div id="content">
+<div id="bodyContent">
+
+<h1><?php $this->outputTitle(); ?></h1>
+<?php
+ }
+
+ public function outputFooter() {
+ if ( $this->useShortHeader ) {
+?>
+</body></html>
+<?php
+ return;
+ }
+?>
+
+</div></div>
+
+
+<div id="mw-panel">
+ <div class="portal" id="p-logo">
+ <a style="background-image: url(../skins/common/images/mediawiki.png);"
+ href="http://www.mediawiki.org/"
+ title="Main Page"></a>
+ </div>
+ <script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>
+ <div class="portal"><div class="body">
+<?php
+ echo $this->parent->parse( wfMsgNoTrans( 'config-sidebar' ), true );
+?>
+ </div></div>
+</div>
+
+</body>
+</html>
+<?php
+ }
+
+ public function outputShortHeader() {
+?>
+<?php echo Html::htmlHeader( $this->getHeadAttribs() ); ?>
+<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <title><?php $this->outputTitle(); ?></title>
+ <?php echo Html::linkedStyle( $this->getCssUrl() ) . "\n"; ?>
+ <?php echo $this->getJQuery(); ?>
+ <?php echo Html::linkedScript( '../skins/common/config.js' ); ?>
+</head>
+
+<body style="background-image: none">
+<?php
+ }
+
+ public function outputTitle() {
+ global $wgVersion;
+ echo htmlspecialchars( wfMsg( 'config-title', $wgVersion ) );
+ }
+
+ public function getJQuery() {
+ return Html::linkedScript( "../resources/jquery/jquery.js" );
+ }
+}
diff --git a/includes/installer/WebInstallerPage.php b/includes/installer/WebInstallerPage.php
new file mode 100644
index 00000000..5c30182b
--- /dev/null
+++ b/includes/installer/WebInstallerPage.php
@@ -0,0 +1,1238 @@
+<?php
+/**
+ * Base code for web installer pages.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Abstract class to define pages for the web installer.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+abstract class WebInstallerPage {
+
+ /**
+ * The WebInstaller object this WebInstallerPage belongs to.
+ *
+ * @var WebInstaller
+ */
+ public $parent;
+
+ public abstract function execute();
+
+ /**
+ * Constructor.
+ *
+ * @param $parent WebInstaller
+ */
+ public function __construct( WebInstaller $parent ) {
+ $this->parent = $parent;
+ }
+
+ public function addHTML( $html ) {
+ $this->parent->output->addHTML( $html );
+ }
+
+ public function startForm() {
+ $this->addHTML(
+ "<div class=\"config-section\">\n" .
+ Html::openElement(
+ 'form',
+ array(
+ 'method' => 'post',
+ 'action' => $this->parent->getUrl( array( 'page' => $this->getName() ) )
+ )
+ ) . "\n"
+ );
+ }
+
+ public function endForm( $continue = 'continue', $back = 'back' ) {
+ $s = "<div class=\"config-submit\">\n";
+ $id = $this->getId();
+
+ if ( $id === false ) {
+ $s .= Html::hidden( 'lastPage', $this->parent->request->getVal( 'lastPage' ) );
+ }
+
+ if ( $continue ) {
+ // Fake submit button for enter keypress (bug 26267)
+ $s .= Xml::submitButton( wfMsg( "config-$continue" ),
+ array( 'name' => "enter-$continue", 'style' => 'visibility:hidden;overflow:hidden;width:1px;margin:0' ) ) . "\n";
+ }
+
+ if ( $back ) {
+ $s .= Xml::submitButton( wfMsg( "config-$back" ),
+ array(
+ 'name' => "submit-$back",
+ 'tabindex' => $this->parent->nextTabIndex()
+ ) ) . "\n";
+ }
+
+ if ( $continue ) {
+ $s .= Xml::submitButton( wfMsg( "config-$continue" ),
+ array(
+ 'name' => "submit-$continue",
+ 'tabindex' => $this->parent->nextTabIndex(),
+ ) ) . "\n";
+ }
+
+ $s .= "</div></form></div>\n";
+ $this->addHTML( $s );
+ }
+
+ public function getName() {
+ return str_replace( 'WebInstaller_', '', get_class( $this ) );
+ }
+
+ protected function getId() {
+ return array_search( $this->getName(), $this->parent->pageSequence );
+ }
+
+ public function getVar( $var ) {
+ return $this->parent->getVar( $var );
+ }
+
+ public function setVar( $name, $value ) {
+ $this->parent->setVar( $name, $value );
+ }
+
+ /**
+ * Get the starting tags of a fieldset.
+ *
+ * @param $legend String: message name
+ */
+ protected function getFieldsetStart( $legend ) {
+ return "\n<fieldset><legend>" . wfMsgHtml( $legend ) . "</legend>\n";
+ }
+
+ /**
+ * Get the end tag of a fieldset.
+ */
+ protected function getFieldsetEnd() {
+ return "</fieldset>\n";
+ }
+
+ /**
+ * Opens a textarea used to display the progress of a long operation
+ */
+ protected function startLiveBox() {
+ $this->addHTML(
+ '<div id="config-spinner" style="display:none;"><img src="../skins/common/images/ajax-loader.gif" /></div>' .
+ '<script>jQuery( "#config-spinner" ).show();</script>' .
+ '<textarea id="config-live-log" name="LiveLog" rows="10" cols="30" readonly="readonly">'
+ );
+ $this->parent->output->flush();
+ }
+
+ /**
+ * Opposite to startLiveBox()
+ */
+ protected function endLiveBox() {
+ $this->addHTML( '</textarea>
+<script>jQuery( "#config-spinner" ).hide()</script>' );
+ $this->parent->output->flush();
+ }
+}
+
+class WebInstaller_Language extends WebInstallerPage {
+
+ public function execute() {
+ global $wgLang;
+ $r = $this->parent->request;
+ $userLang = $r->getVal( 'UserLang' );
+ $contLang = $r->getVal( 'ContLang' );
+
+ $lifetime = intval( ini_get( 'session.gc_maxlifetime' ) );
+ if ( !$lifetime ) {
+ $lifetime = 1440; // PHP default
+ }
+
+ if ( $r->wasPosted() ) {
+ # Do session test
+ if ( $this->parent->getSession( 'test' ) === null ) {
+ $requestTime = $r->getVal( 'LanguageRequestTime' );
+ if ( !$requestTime ) {
+ // The most likely explanation is that the user was knocked back
+ // from another page on POST due to session expiry
+ $msg = 'config-session-expired';
+ } elseif ( time() - $requestTime > $lifetime ) {
+ $msg = 'config-session-expired';
+ } else {
+ $msg = 'config-no-session';
+ }
+ $this->parent->showError( $msg, $wgLang->formatTimePeriod( $lifetime ) );
+ } else {
+ $languages = Language::getLanguageNames();
+ if ( isset( $languages[$userLang] ) ) {
+ $this->setVar( '_UserLang', $userLang );
+ }
+ if ( isset( $languages[$contLang] ) ) {
+ $this->setVar( 'wgLanguageCode', $contLang );
+ }
+ return 'continue';
+ }
+ } elseif ( $this->parent->showSessionWarning ) {
+ # The user was knocked back from another page to the start
+ # This probably indicates a session expiry
+ $this->parent->showError( 'config-session-expired', $wgLang->formatTimePeriod( $lifetime ) );
+ }
+
+ $this->parent->setSession( 'test', true );
+
+ if ( !isset( $languages[$userLang] ) ) {
+ $userLang = $this->getVar( '_UserLang', 'en' );
+ }
+ if ( !isset( $languages[$contLang] ) ) {
+ $contLang = $this->getVar( 'wgLanguageCode', 'en' );
+ }
+ $this->startForm();
+ $s = Html::hidden( 'LanguageRequestTime', time() ) .
+ $this->getLanguageSelector( 'UserLang', 'config-your-language', $userLang, $this->parent->getHelpBox( 'config-your-language-help' ) ) .
+ $this->getLanguageSelector( 'ContLang', 'config-wiki-language', $contLang, $this->parent->getHelpBox( 'config-wiki-language-help' ) );
+ $this->addHTML( $s );
+ $this->endForm( 'continue', false );
+ }
+
+ /**
+ * Get a <select> for selecting languages.
+ */
+ public function getLanguageSelector( $name, $label, $selectedCode ) {
+ global $wgDummyLanguageCodes;
+ $s = Html::openElement( 'select', array( 'id' => $name, 'name' => $name ) ) . "\n";
+
+ $languages = Language::getLanguageNames();
+ ksort( $languages );
+ $dummies = array_flip( $wgDummyLanguageCodes );
+ foreach ( $languages as $code => $lang ) {
+ if ( isset( $dummies[$code] ) ) continue;
+ $s .= "\n" . Xml::option( "$code - $lang", $code, $code == $selectedCode );
+ }
+ $s .= "\n</select>\n";
+ return $this->parent->label( $label, $name, $s );
+ }
+
+}
+
+class WebInstaller_ExistingWiki extends WebInstallerPage {
+ public function execute() {
+ // If there is no LocalSettings.php, continue to the installer welcome page
+ $vars = Installer::getExistingLocalSettings();
+ if ( !$vars ) {
+ return 'skip';
+ }
+
+ // Check if the upgrade key supplied to the user has appeared in LocalSettings.php
+ if ( $vars['wgUpgradeKey'] !== false
+ && $this->getVar( '_UpgradeKeySupplied' )
+ && $this->getVar( 'wgUpgradeKey' ) === $vars['wgUpgradeKey'] )
+ {
+ // It's there, so the user is authorized
+ $status = $this->handleExistingUpgrade( $vars );
+ if ( $status->isOK() ) {
+ return 'skip';
+ } else {
+ $this->startForm();
+ $this->parent->showStatusBox( $status );
+ $this->endForm( 'continue' );
+ return 'output';
+ }
+ }
+
+ // If there is no $wgUpgradeKey, tell the user to add one to LocalSettings.php
+ if ( $vars['wgUpgradeKey'] === false ) {
+ if ( $this->getVar( 'wgUpgradeKey', false ) === false ) {
+ $secretKey = $this->getVar( 'wgSecretKey' ); // preserve $wgSecretKey
+ $this->parent->generateKeys();
+ $this->setVar( 'wgSecretKey', $secretKey );
+ $this->setVar( '_UpgradeKeySupplied', true );
+ }
+ $this->startForm();
+ $this->addHTML( $this->parent->getInfoBox(
+ wfMsgNoTrans( 'config-upgrade-key-missing',
+ "<pre>\$wgUpgradeKey = '" . $this->getVar( 'wgUpgradeKey' ) . "';</pre>" )
+ ) );
+ $this->endForm( 'continue' );
+ return 'output';
+ }
+
+ // If there is an upgrade key, but it wasn't supplied, prompt the user to enter it
+
+ $r = $this->parent->request;
+ if ( $r->wasPosted() ) {
+ $key = $r->getText( 'config_wgUpgradeKey' );
+ if( !$key || $key !== $vars['wgUpgradeKey'] ) {
+ $this->parent->showError( 'config-localsettings-badkey' );
+ $this->showKeyForm();
+ return 'output';
+ }
+ // Key was OK
+ $status = $this->handleExistingUpgrade( $vars );
+ if ( $status->isOK() ) {
+ return 'continue';
+ } else {
+ $this->parent->showStatusBox( $status );
+ $this->showKeyForm();
+ return 'output';
+ }
+ } else {
+ $this->showKeyForm();
+ return 'output';
+ }
+ }
+
+ /**
+ * Show the "enter key" form
+ */
+ protected function showKeyForm() {
+ $this->startForm();
+ $this->addHTML(
+ $this->parent->getInfoBox( wfMsgNoTrans( 'config-localsettings-upgrade' ) ).
+ '<br />' .
+ $this->parent->getTextBox( array(
+ 'var' => 'wgUpgradeKey',
+ 'label' => 'config-localsettings-key',
+ 'attribs' => array( 'autocomplete' => 'off' ),
+ ) )
+ );
+ $this->endForm( 'continue' );
+ }
+
+ protected function importVariables( $names, $vars ) {
+ $status = Status::newGood();
+ foreach ( $names as $name ) {
+ if ( !isset( $vars[$name] ) ) {
+ $status->fatal( 'config-localsettings-incomplete', $name );
+ }
+ $this->setVar( $name, $vars[$name] );
+ }
+ return $status;
+ }
+
+ /**
+ * Initiate an upgrade of the existing database
+ * @param $vars Variables from LocalSettings.php and AdminSettings.php
+ * @return Status
+ */
+ protected function handleExistingUpgrade( $vars ) {
+ // Check $wgDBtype
+ if ( !isset( $vars['wgDBtype'] ) || !in_array( $vars['wgDBtype'], Installer::getDBTypes() ) ) {
+ return Status::newFatal( 'config-localsettings-connection-error', '' );
+ }
+
+ // Set the relevant variables from LocalSettings.php
+ $requiredVars = array( 'wgDBtype' );
+ $status = $this->importVariables( $requiredVars , $vars );
+ $installer = $this->parent->getDBInstaller();
+ $status->merge( $this->importVariables( $installer->getGlobalNames(), $vars ) );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ if ( isset( $vars['wgDBadminuser'] ) ) {
+ $this->setVar( '_InstallUser', $vars['wgDBadminuser'] );
+ } else {
+ $this->setVar( '_InstallUser', $vars['wgDBuser'] );
+ }
+ if ( isset( $vars['wgDBadminpassword'] ) ) {
+ $this->setVar( '_InstallPassword', $vars['wgDBadminpassword'] );
+ } else {
+ $this->setVar( '_InstallPassword', $vars['wgDBpassword'] );
+ }
+
+ // Test the database connection
+ $status = $installer->getConnection();
+ if ( !$status->isOK() ) {
+ // Adjust the error message to explain things correctly
+ $status->replaceMessage( 'config-connection-error',
+ 'config-localsettings-connection-error' );
+ return $status;
+ }
+
+ // All good
+ $this->setVar( '_ExistingDBSettings', true );
+ return $status;
+ }
+}
+
+class WebInstaller_Welcome extends WebInstallerPage {
+
+ public function execute() {
+ if ( $this->parent->request->wasPosted() ) {
+ if ( $this->getVar( '_Environment' ) ) {
+ return 'continue';
+ }
+ }
+ $this->parent->output->addWikiText( wfMsgNoTrans( 'config-welcome' ) );
+ $status = $this->parent->doEnvironmentChecks();
+ if ( $status->isGood() ) {
+ $this->parent->output->addHTML( '<span class="success-message">' .
+ wfMsgHtml( 'config-env-good' ) . '</span>' );
+ $this->parent->output->addWikiText( wfMsgNoTrans( 'config-copyright',
+ SpecialVersion::getCopyrightAndAuthorList() ) );
+ $this->startForm();
+ $this->endForm();
+ } else {
+ $this->parent->showStatusMessage( $status );
+ }
+ }
+
+}
+
+class WebInstaller_DBConnect extends WebInstallerPage {
+
+ public function execute() {
+ if ( $this->getVar( '_ExistingDBSettings' ) ) {
+ return 'skip';
+ }
+
+ $r = $this->parent->request;
+ if ( $r->wasPosted() ) {
+ $status = $this->submit();
+
+ if ( $status->isGood() ) {
+ $this->setVar( '_UpgradeDone', false );
+ return 'continue';
+ } else {
+ $this->parent->showStatusBox( $status );
+ }
+ }
+
+ $this->startForm();
+
+ $types = "<ul class=\"config-settings-block\">\n";
+ $settings = '';
+ $defaultType = $this->getVar( 'wgDBtype' );
+
+ $dbSupport = '';
+ foreach( $this->parent->getDBTypes() as $type ) {
+ $link = DatabaseBase::newFromType( $type )->getSoftwareLink();
+ $dbSupport .= wfMsgNoTrans( "config-support-$type", $link ) . "\n";
+ }
+ $this->addHTML( $this->parent->getInfoBox(
+ wfMsg( 'config-support-info', $dbSupport ) ) );
+
+ foreach ( $this->parent->getVar( '_CompiledDBs' ) as $type ) {
+ $installer = $this->parent->getDBInstaller( $type );
+ $types .=
+ '<li>' .
+ Xml::radioLabel(
+ $installer->getReadableName(),
+ 'DBType',
+ $type,
+ "DBType_$type",
+ $type == $defaultType,
+ array( 'class' => 'dbRadio', 'rel' => "DB_wrapper_$type" )
+ ) .
+ "</li>\n";
+
+ $settings .=
+ Html::openElement( 'div', array( 'id' => 'DB_wrapper_' . $type, 'class' => 'dbWrapper' ) ) .
+ Html::element( 'h3', array(), wfMsg( 'config-header-' . $type ) ) .
+ $installer->getConnectForm() .
+ "</div>\n";
+ }
+ $types .= "</ul><br clear=\"left\"/>\n";
+
+ $this->addHTML(
+ $this->parent->label( 'config-db-type', false, $types ) .
+ $settings
+ );
+
+ $this->endForm();
+ }
+
+ public function submit() {
+ $r = $this->parent->request;
+ $type = $r->getVal( 'DBType' );
+ $this->setVar( 'wgDBtype', $type );
+ $installer = $this->parent->getDBInstaller( $type );
+ if ( !$installer ) {
+ return Status::newFatal( 'config-invalid-db-type' );
+ }
+ return $installer->submitConnectForm();
+ }
+
+}
+
+class WebInstaller_Upgrade extends WebInstallerPage {
+
+ public function execute() {
+ if ( $this->getVar( '_UpgradeDone' ) ) {
+ // Allow regeneration of LocalSettings.php, unless we are working
+ // from a pre-existing LocalSettings.php file and we want to avoid
+ // leaking its contents
+ if ( $this->parent->request->wasPosted() && !$this->getVar( '_ExistingDBSettings' ) ) {
+ // Done message acknowledged
+ return 'continue';
+ } else {
+ // Back button click
+ // Show the done message again
+ // Make them click back again if they want to do the upgrade again
+ $this->showDoneMessage();
+ return 'output';
+ }
+ }
+
+ // wgDBtype is generally valid here because otherwise the previous page
+ // (connect) wouldn't have declared its happiness
+ $type = $this->getVar( 'wgDBtype' );
+ $installer = $this->parent->getDBInstaller( $type );
+
+ if ( !$installer->needsUpgrade() ) {
+ return 'skip';
+ }
+
+ if ( $this->parent->request->wasPosted() ) {
+ $installer->preUpgrade();
+
+ $this->startLiveBox();
+ $result = $installer->doUpgrade();
+ $this->endLiveBox();
+
+ if ( $result ) {
+ // If they're going to possibly regenerate LocalSettings, we
+ // need to create the upgrade/secret keys. Bug 26481
+ if( !$this->getVar( '_ExistingDBSettings' ) ) {
+ $this->parent->generateKeys();
+ }
+ $this->setVar( '_UpgradeDone', true );
+ $this->showDoneMessage();
+ return 'output';
+ }
+ }
+
+ $this->startForm();
+ $this->addHTML( $this->parent->getInfoBox(
+ wfMsgNoTrans( 'config-can-upgrade', $GLOBALS['wgVersion'] ) ) );
+ $this->endForm();
+ }
+
+ public function showDoneMessage() {
+ $this->startForm();
+ $regenerate = !$this->getVar( '_ExistingDBSettings' );
+ if ( $regenerate ) {
+ $msg = 'config-upgrade-done';
+ } else {
+ $msg = 'config-upgrade-done-no-regenerate';
+ }
+ $this->parent->disableLinkPopups();
+ $this->addHTML(
+ $this->parent->getInfoBox(
+ wfMsgNoTrans( $msg,
+ $GLOBALS['wgServer'] .
+ $this->getVar( 'wgScriptPath' ) . '/index' .
+ $this->getVar( 'wgScriptExtension' )
+ ), 'tick-32.png'
+ )
+ );
+ $this->parent->restoreLinkPopups();
+ $this->endForm( $regenerate ? 'regenerate' : false, false );
+ }
+
+}
+
+class WebInstaller_DBSettings extends WebInstallerPage {
+
+ public function execute() {
+ $installer = $this->parent->getDBInstaller( $this->getVar( 'wgDBtype' ) );
+
+ $r = $this->parent->request;
+ if ( $r->wasPosted() ) {
+ $status = $installer->submitSettingsForm();
+ if ( $status === false ) {
+ return 'skip';
+ } elseif ( $status->isGood() ) {
+ return 'continue';
+ } else {
+ $this->parent->showStatusBox( $status );
+ }
+ }
+
+ $form = $installer->getSettingsForm();
+ if ( $form === false ) {
+ return 'skip';
+ }
+
+ $this->startForm();
+ $this->addHTML( $form );
+ $this->endForm();
+ }
+
+}
+
+class WebInstaller_Name extends WebInstallerPage {
+
+ public function execute() {
+ $r = $this->parent->request;
+ if ( $r->wasPosted() ) {
+ if ( $this->submit() ) {
+ return 'continue';
+ }
+ }
+
+ $this->startForm();
+
+ // Encourage people to not name their site 'MediaWiki' by blanking the
+ // field. I think that was the intent with the original $GLOBALS['wgSitename']
+ // but these two always were the same so had the effect of making the
+ // installer forget $wgSitename when navigating back to this page.
+ if ( $this->getVar( 'wgSitename' ) == 'MediaWiki' ) {
+ $this->setVar( 'wgSitename', '' );
+ }
+
+ // Set wgMetaNamespace to something valid before we show the form.
+ // $wgMetaNamespace defaults to $wgSiteName which is 'MediaWiki'
+ $metaNS = $this->getVar( 'wgMetaNamespace' );
+ $this->setVar( 'wgMetaNamespace', wfMsgForContent( 'config-ns-other-default' ) );
+
+ $this->addHTML(
+ $this->parent->getTextBox( array(
+ 'var' => 'wgSitename',
+ 'label' => 'config-site-name',
+ 'help' => $this->parent->getHelpBox( 'config-site-name-help' )
+ ) ) .
+ $this->parent->getRadioSet( array(
+ 'var' => '_NamespaceType',
+ 'label' => 'config-project-namespace',
+ 'itemLabelPrefix' => 'config-ns-',
+ 'values' => array( 'site-name', 'generic', 'other' ),
+ 'commonAttribs' => array( 'class' => 'enableForOther', 'rel' => 'config_wgMetaNamespace' ),
+ 'help' => $this->parent->getHelpBox( 'config-project-namespace-help' )
+ ) ) .
+ $this->parent->getTextBox( array(
+ 'var' => 'wgMetaNamespace',
+ 'label' => '', //TODO: Needs a label?
+ 'attribs' => array( 'readonly' => 'readonly', 'class' => 'enabledByOther' ),
+
+ ) ) .
+ $this->getFieldSetStart( 'config-admin-box' ) .
+ $this->parent->getTextBox( array(
+ 'var' => '_AdminName',
+ 'label' => 'config-admin-name',
+ 'help' => $this->parent->getHelpBox( 'config-admin-help' )
+ ) ) .
+ $this->parent->getPasswordBox( array(
+ 'var' => '_AdminPassword',
+ 'label' => 'config-admin-password',
+ ) ) .
+ $this->parent->getPasswordBox( array(
+ 'var' => '_AdminPassword2',
+ 'label' => 'config-admin-password-confirm'
+ ) ) .
+ $this->parent->getTextBox( array(
+ 'var' => '_AdminEmail',
+ 'label' => 'config-admin-email',
+ 'help' => $this->parent->getHelpBox( 'config-admin-email-help' )
+ ) ) .
+ $this->parent->getCheckBox( array(
+ 'var' => '_Subscribe',
+ 'label' => 'config-subscribe',
+ 'help' => $this->parent->getHelpBox( 'config-subscribe-help' )
+ ) ) .
+ $this->getFieldSetEnd() .
+ $this->parent->getInfoBox( wfMsg( 'config-almost-done' ) ) .
+ $this->parent->getRadioSet( array(
+ 'var' => '_SkipOptional',
+ 'itemLabelPrefix' => 'config-optional-',
+ 'values' => array( 'continue', 'skip' )
+ ) )
+ );
+
+ // Restore the default value
+ $this->setVar( 'wgMetaNamespace', $metaNS );
+
+ $this->endForm();
+ return 'output';
+ }
+
+ public function submit() {
+ $retVal = true;
+ $this->parent->setVarsFromRequest( array( 'wgSitename', '_NamespaceType',
+ '_AdminName', '_AdminPassword', '_AdminPassword2', '_AdminEmail',
+ '_Subscribe', '_SkipOptional', 'wgMetaNamespace' ) );
+
+ // Validate site name
+ if ( strval( $this->getVar( 'wgSitename' ) ) === '' ) {
+ $this->parent->showError( 'config-site-name-blank' );
+ $retVal = false;
+ }
+
+ // Fetch namespace
+ $nsType = $this->getVar( '_NamespaceType' );
+ if ( $nsType == 'site-name' ) {
+ $name = $this->getVar( 'wgSitename' );
+ // Sanitize for namespace
+ // This algorithm should match the JS one in WebInstallerOutput.php
+ $name = preg_replace( '/[\[\]\{\}|#<>%+? ]/', '_', $name );
+ $name = str_replace( '&', '&amp;', $name );
+ $name = preg_replace( '/__+/', '_', $name );
+ $name = ucfirst( trim( $name, '_' ) );
+ } elseif ( $nsType == 'generic' ) {
+ $name = wfMsg( 'config-ns-generic' );
+ } else { // other
+ $name = $this->getVar( 'wgMetaNamespace' );
+ }
+
+ // Validate namespace
+ if ( strpos( $name, ':' ) !== false ) {
+ $good = false;
+ } else {
+ // Title-style validation
+ $title = Title::newFromText( $name );
+ if ( !$title ) {
+ $good = $nsType == 'site-name';
+ } else {
+ $name = $title->getDBkey();
+ $good = true;
+ }
+ }
+ if ( !$good ) {
+ $this->parent->showError( 'config-ns-invalid', $name );
+ $retVal = false;
+ }
+
+ // Make sure it won't conflict with any existing namespaces
+ global $wgContLang;
+ $nsIndex = $wgContLang->getNsIndex( $name );
+ if( $nsIndex !== false && $nsIndex !== NS_PROJECT ) {
+ $this->parent->showError( 'config-ns-conflict', $name );
+ $retVal = false;
+ }
+
+ $this->setVar( 'wgMetaNamespace', $name );
+
+ // Validate username for creation
+ $name = $this->getVar( '_AdminName' );
+ if ( strval( $name ) === '' ) {
+ $this->parent->showError( 'config-admin-name-blank' );
+ $cname = $name;
+ $retVal = false;
+ } else {
+ $cname = User::getCanonicalName( $name, 'creatable' );
+ if ( $cname === false ) {
+ $this->parent->showError( 'config-admin-name-invalid', $name );
+ $retVal = false;
+ } else {
+ $this->setVar( '_AdminName', $cname );
+ }
+ }
+
+ // Validate password
+ $msg = false;
+ $valid = false;
+ $pwd = $this->getVar( '_AdminPassword' );
+ $user = User::newFromName( $cname );
+ $valid = $user && $user->getPasswordValidity( $pwd );
+ if ( strval( $pwd ) === '' ) {
+ # $user->getPasswordValidity just checks for $wgMinimalPasswordLength.
+ # This message is more specific and helpful.
+ $msg = 'config-admin-password-blank';
+ } elseif ( $pwd !== $this->getVar( '_AdminPassword2' ) ) {
+ $msg = 'config-admin-password-mismatch';
+ } elseif ( $valid !== true ) {
+ # As of writing this will only catch the username being e.g. 'FOO' and
+ # the password 'foo'
+ $msg = $valid;
+ }
+ if ( $msg !== false ) {
+ call_user_func_array( array( $this->parent, 'showError' ), (array)$msg );
+ $this->setVar( '_AdminPassword', '' );
+ $this->setVar( '_AdminPassword2', '' );
+ $retVal = false;
+ }
+
+ // Validate e-mail if provided
+ $email = $this->getVar( '_AdminEmail' );
+ if( $email && !User::isValidEmailAddr( $email ) ) {
+ $this->parent->showError( 'config-admin-error-bademail' );
+ $retVal = false;
+ }
+
+ return $retVal;
+ }
+
+}
+
+class WebInstaller_Options extends WebInstallerPage {
+
+ public function execute() {
+ if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
+ return 'skip';
+ }
+ if ( $this->parent->request->wasPosted() ) {
+ if ( $this->submit() ) {
+ return 'continue';
+ }
+ }
+
+ $emailwrapperStyle = $this->getVar( 'wgEnableEmail' ) ? '' : 'display: none';
+ $this->startForm();
+ $this->addHTML(
+ # User Rights
+ $this->parent->getRadioSet( array(
+ 'var' => '_RightsProfile',
+ 'label' => 'config-profile',
+ 'itemLabelPrefix' => 'config-profile-',
+ 'values' => array_keys( $this->parent->rightsProfiles ),
+ ) ) .
+ $this->parent->getInfoBox( wfMsgNoTrans( 'config-profile-help' ) ) .
+
+ # Licensing
+ $this->parent->getRadioSet( array(
+ 'var' => '_LicenseCode',
+ 'label' => 'config-license',
+ 'itemLabelPrefix' => 'config-license-',
+ 'values' => array_keys( $this->parent->licenses ),
+ 'commonAttribs' => array( 'class' => 'licenseRadio' ),
+ ) ) .
+ $this->getCCChooser() .
+ $this->parent->getHelpBox( 'config-license-help' ) .
+
+ # E-mail
+ $this->getFieldSetStart( 'config-email-settings' ) .
+ $this->parent->getCheckBox( array(
+ 'var' => 'wgEnableEmail',
+ 'label' => 'config-enable-email',
+ 'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'emailwrapper' ),
+ ) ) .
+ $this->parent->getHelpBox( 'config-enable-email-help' ) .
+ "<div id=\"emailwrapper\" style=\"$emailwrapperStyle\">" .
+ $this->parent->getTextBox( array(
+ 'var' => 'wgPasswordSender',
+ 'label' => 'config-email-sender'
+ ) ) .
+ $this->parent->getHelpBox( 'config-email-sender-help' ) .
+ $this->parent->getCheckBox( array(
+ 'var' => 'wgEnableUserEmail',
+ 'label' => 'config-email-user',
+ ) ) .
+ $this->parent->getHelpBox( 'config-email-user-help' ) .
+ $this->parent->getCheckBox( array(
+ 'var' => 'wgEnotifUserTalk',
+ 'label' => 'config-email-usertalk',
+ ) ) .
+ $this->parent->getHelpBox( 'config-email-usertalk-help' ) .
+ $this->parent->getCheckBox( array(
+ 'var' => 'wgEnotifWatchlist',
+ 'label' => 'config-email-watchlist',
+ ) ) .
+ $this->parent->getHelpBox( 'config-email-watchlist-help' ) .
+ $this->parent->getCheckBox( array(
+ 'var' => 'wgEmailAuthentication',
+ 'label' => 'config-email-auth',
+ ) ) .
+ $this->parent->getHelpBox( 'config-email-auth-help' ) .
+ "</div>" .
+ $this->getFieldSetEnd()
+ );
+
+ $extensions = $this->parent->findExtensions();
+
+ if( $extensions ) {
+ $extHtml = $this->getFieldSetStart( 'config-extensions' );
+
+ foreach( $extensions as $ext ) {
+ $extHtml .= $this->parent->getCheckBox( array(
+ 'var' => "ext-$ext",
+ 'rawtext' => $ext,
+ ) );
+ }
+
+ $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
+ $this->getFieldSetEnd();
+ $this->addHTML( $extHtml );
+ }
+
+ // Having / in paths in Windows looks funny :)
+ $this->setVar( 'wgDeletedDirectory',
+ str_replace(
+ '/', DIRECTORY_SEPARATOR,
+ $this->getVar( 'wgDeletedDirectory' )
+ )
+ );
+
+ $uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none';
+ $this->addHTML(
+ # Uploading
+ $this->getFieldSetStart( 'config-upload-settings' ) .
+ $this->parent->getCheckBox( array(
+ 'var' => 'wgEnableUploads',
+ 'label' => 'config-upload-enable',
+ 'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ),
+ 'help' => $this->parent->getHelpBox( 'config-upload-help' )
+ ) ) .
+ '<div id="uploadwrapper" style="' . $uploadwrapperStyle . '">' .
+ $this->parent->getTextBox( array(
+ 'var' => 'wgDeletedDirectory',
+ 'label' => 'config-upload-deleted',
+ 'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' )
+ ) ) .
+ '</div>' .
+ $this->parent->getTextBox( array(
+ 'var' => 'wgLogo',
+ 'label' => 'config-logo',
+ 'help' => $this->parent->getHelpBox( 'config-logo-help' )
+ ) )
+ );
+ $this->addHTML(
+ $this->parent->getCheckBox( array(
+ 'var' => 'wgUseInstantCommons',
+ 'label' => 'config-instantcommons',
+ 'help' => $this->parent->getHelpBox( 'config-instantcommons-help' )
+ ) ) .
+ $this->getFieldSetEnd()
+ );
+
+ $caches = array( 'none' );
+ if( count( $this->getVar( '_Caches' ) ) ) {
+ $caches[] = 'accel';
+ }
+ $caches[] = 'memcached';
+
+ $this->addHTML(
+ # Advanced settings
+ $this->getFieldSetStart( 'config-advanced-settings' ) .
+ # Object cache settings
+ $this->parent->getRadioSet( array(
+ 'var' => 'wgMainCacheType',
+ 'label' => 'config-cache-options',
+ 'itemLabelPrefix' => 'config-cache-',
+ 'values' => $caches,
+ 'value' => 'none',
+ ) ) .
+ $this->parent->getHelpBox( 'config-cache-help' ) .
+ '<div id="config-memcachewrapper">' .
+ $this->parent->getTextArea( array(
+ 'var' => '_MemCachedServers',
+ 'label' => 'config-memcached-servers',
+ 'help' => $this->parent->getHelpBox( 'config-memcached-help' )
+ ) ) .
+ '</div>' .
+ $this->getFieldSetEnd()
+ );
+ $this->endForm();
+ }
+
+ public function getCCPartnerUrl() {
+ global $wgServer;
+ $exitUrl = $wgServer . $this->parent->getUrl( array(
+ 'page' => 'Options',
+ 'SubmitCC' => 'indeed',
+ 'config__LicenseCode' => 'cc',
+ 'config_wgRightsUrl' => '[license_url]',
+ 'config_wgRightsText' => '[license_name]',
+ 'config_wgRightsIcon' => '[license_button]',
+ ) );
+ $styleUrl = $wgServer . dirname( dirname( $this->parent->getUrl() ) ) .
+ '/skins/common/config-cc.css';
+ $iframeUrl = 'http://creativecommons.org/license/?' .
+ wfArrayToCGI( array(
+ 'partner' => 'MediaWiki',
+ 'exit_url' => $exitUrl,
+ 'lang' => $this->getVar( '_UserLang' ),
+ 'stylesheet' => $styleUrl,
+ ) );
+ return $iframeUrl;
+ }
+
+ public function getCCChooser() {
+ $iframeAttribs = array(
+ 'class' => 'config-cc-iframe',
+ 'name' => 'config-cc-iframe',
+ 'id' => 'config-cc-iframe',
+ 'frameborder' => 0,
+ 'width' => '100%',
+ 'height' => '100%',
+ );
+ if ( $this->getVar( '_CCDone' ) ) {
+ $iframeAttribs['src'] = $this->parent->getUrl( array( 'ShowCC' => 'yes' ) );
+ } else {
+ $iframeAttribs['src'] = $this->getCCPartnerUrl();
+ }
+ $wrapperStyle = ($this->getVar('_LicenseCode') == 'cc-choose') ? '' : 'display: none';
+
+ return
+ "<div class=\"config-cc-wrapper\" id=\"config-cc-wrapper\" style=\"$wrapperStyle\">\n" .
+ Html::element( 'iframe', $iframeAttribs, '', false /* not short */ ) .
+ "</div>\n";
+ }
+
+ public function getCCDoneBox() {
+ $js = "parent.document.getElementById('config-cc-wrapper').style.height = '$1';";
+ // If you change this height, also change it in config.css
+ $expandJs = str_replace( '$1', '54em', $js );
+ $reduceJs = str_replace( '$1', '70px', $js );
+ return
+ '<p>'.
+ Html::element( 'img', array( 'src' => $this->getVar( 'wgRightsIcon' ) ) ) .
+ '&#160;&#160;' .
+ htmlspecialchars( $this->getVar( 'wgRightsText' ) ) .
+ "</p>\n" .
+ "<p style=\"text-align: center\">" .
+ Html::element( 'a',
+ array(
+ 'href' => $this->getCCPartnerUrl(),
+ 'onclick' => $expandJs,
+ ),
+ wfMsg( 'config-cc-again' )
+ ) .
+ "</p>\n" .
+ "<script type=\"text/javascript\">\n" .
+ # Reduce the wrapper div height
+ htmlspecialchars( $reduceJs ) .
+ "\n" .
+ "</script>\n";
+ }
+
+ public function submitCC() {
+ $newValues = $this->parent->setVarsFromRequest(
+ array( 'wgRightsUrl', 'wgRightsText', 'wgRightsIcon' ) );
+ if ( count( $newValues ) != 3 ) {
+ $this->parent->showError( 'config-cc-error' );
+ return;
+ }
+ $this->setVar( '_CCDone', true );
+ $this->addHTML( $this->getCCDoneBox() );
+ }
+
+ public function submit() {
+ $this->parent->setVarsFromRequest( array( '_RightsProfile', '_LicenseCode',
+ 'wgEnableEmail', 'wgPasswordSender', 'wgEnableUploads', 'wgLogo',
+ 'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
+ 'wgEmailAuthentication', 'wgMainCacheType', '_MemCachedServers',
+ 'wgUseInstantCommons' ) );
+
+ if ( !in_array( $this->getVar( '_RightsProfile' ),
+ array_keys( $this->parent->rightsProfiles ) ) )
+ {
+ reset( $this->parent->rightsProfiles );
+ $this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
+ }
+
+ $code = $this->getVar( '_LicenseCode' );
+ if ( $code == 'cc-choose' ) {
+ if ( !$this->getVar( '_CCDone' ) ) {
+ $this->parent->showError( 'config-cc-not-chosen' );
+ return false;
+ }
+ } elseif ( in_array( $code, array_keys( $this->parent->licenses ) ) ) {
+ $entry = $this->parent->licenses[$code];
+ if ( isset( $entry['text'] ) ) {
+ $this->setVar( 'wgRightsText', $entry['text'] );
+ } else {
+ $this->setVar( 'wgRightsText', wfMsg( 'config-license-' . $code ) );
+ }
+ $this->setVar( 'wgRightsUrl', $entry['url'] );
+ $this->setVar( 'wgRightsIcon', $entry['icon'] );
+ } else {
+ $this->setVar( 'wgRightsText', '' );
+ $this->setVar( 'wgRightsUrl', '' );
+ $this->setVar( 'wgRightsIcon', '' );
+ }
+
+ $extsAvailable = $this->parent->findExtensions();
+ $extsToInstall = array();
+ foreach( $extsAvailable as $ext ) {
+ if( $this->parent->request->getCheck( 'config_ext-' . $ext ) ) {
+ $extsToInstall[] = $ext;
+ }
+ }
+ $this->parent->setVar( '_Extensions', $extsToInstall );
+
+ if( $this->getVar( 'wgMainCacheType' ) == 'memcached' ) {
+ $memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) );
+ if( !$memcServers ) {
+ $this->parent->showError( 'config-memcache-needservers' );
+ return false;
+ }
+
+ foreach( $memcServers as $server ) {
+ $memcParts = explode( ":", $server );
+ if( !IP::isValid( $memcParts[0] ) ) {
+ $this->parent->showError( 'config-memcache-badip', $memcParts[0] );
+ return false;
+ } elseif( !isset( $memcParts[1] ) ) {
+ $this->parent->showError( 'config-memcache-noport', $memcParts[0] );
+ return false;
+ } elseif( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
+ $this->parent->showError( 'config-memcache-badport', 1, 65535 );
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+}
+
+class WebInstaller_Install extends WebInstallerPage {
+
+ public function execute() {
+ if( $this->getVar( '_UpgradeDone' ) ) {
+ return 'skip';
+ } elseif( $this->getVar( '_InstallDone' ) ) {
+ return 'continue';
+ } elseif( $this->parent->request->wasPosted() ) {
+ $this->startForm();
+ $this->addHTML("<ul>");
+ $results = $this->parent->performInstallation(
+ array( $this, 'startStage'),
+ array( $this, 'endStage' )
+ );
+ $this->addHTML("</ul>");
+ // PerformInstallation bails on a fatal, so make sure the last item
+ // completed before giving 'next.' Likewise, only provide back on failure
+ $lastStep = end( $results );
+ $continue = $lastStep->isOK() ? 'continue' : false;
+ $back = $lastStep->isOK() ? false : 'back';
+ $this->endForm( $continue, $back );
+ } else {
+ $this->startForm();
+ $this->addHTML( $this->parent->getInfoBox( wfMsgNoTrans( 'config-install-begin' ) ) );
+ $this->endForm();
+ }
+ return true;
+ }
+
+ public function startStage( $step ) {
+ $this->addHTML( "<li>" . wfMsgHtml( "config-install-$step" ) . wfMsg( 'ellipsis') );
+ if ( $step == 'extension-tables' ) {
+ $this->startLiveBox();
+ }
+ }
+
+ public function endStage( $step, $status ) {
+ if ( $step == 'extension-tables' ) {
+ $this->endLiveBox();
+ }
+ $msg = $status->isOk() ? 'config-install-step-done' : 'config-install-step-failed';
+ $html = wfMsgHtml( 'word-separator' ) . wfMsgHtml( $msg );
+ if ( !$status->isOk() ) {
+ $html = "<span class=\"error\">$html</span>";
+ }
+ $this->addHTML( $html . "</li>\n" );
+ if( !$status->isGood() ) {
+ $this->parent->showStatusBox( $status );
+ }
+ }
+
+}
+
+class WebInstaller_Complete extends WebInstallerPage {
+
+ public function execute() {
+ // Pop up a dialog box, to make it difficult for the user to forget
+ // to download the file
+ $lsUrl = $GLOBALS['wgServer'] . $this->parent->getURL( array( 'localsettings' => 1 ) );
+ if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false ) {
+ // JS appears the only method that works consistently with IE7+
+ $this->addHtml( "\n<script type=\"" . $GLOBALS['wgJsMimeType'] . '">jQuery( document ).ready( function() { document.location='
+ . Xml::encodeJsVar( $lsUrl) . "; } );</script>\n" );
+ } else {
+ $this->parent->request->response()->header( "Refresh: 0;url=$lsUrl" );
+ }
+
+ $this->startForm();
+ $this->parent->disableLinkPopups();
+ $this->addHTML(
+ $this->parent->getInfoBox(
+ wfMsgNoTrans( 'config-install-done',
+ $lsUrl,
+ $GLOBALS['wgServer'] .
+ $this->getVar( 'wgScriptPath' ) . '/index' .
+ $this->getVar( 'wgScriptExtension' ),
+ '<downloadlink/>'
+ ), 'tick-32.png'
+ )
+ );
+ $this->parent->restoreLinkPopups();
+ $this->endForm( false, false );
+ }
+}
+
+class WebInstaller_Restart extends WebInstallerPage {
+
+ public function execute() {
+ $r = $this->parent->request;
+ if ( $r->wasPosted() ) {
+ $really = $r->getVal( 'submit-restart' );
+ if ( $really ) {
+ $this->parent->reset();
+ }
+ return 'continue';
+ }
+
+ $this->startForm();
+ $s = $this->parent->getWarningBox( wfMsgNoTrans( 'config-help-restart' ) );
+ $this->addHTML( $s );
+ $this->endForm( 'restart' );
+ }
+
+}
+
+abstract class WebInstaller_Document extends WebInstallerPage {
+
+ protected abstract function getFileName();
+
+ public function execute() {
+ $text = $this->getFileContents();
+ $text = $this->formatTextFile( $text );
+ $this->parent->output->addWikiText( $text );
+ $this->startForm();
+ $this->endForm( false );
+ }
+
+ public function getFileContents() {
+ return file_get_contents( dirname( __FILE__ ) . '/../../' . $this->getFileName() );
+ }
+
+ protected function formatTextFile( $text ) {
+ // Use Unix line endings, escape some wikitext stuff
+ $text = str_replace( array( '<', '{{', '[[', "\r" ),
+ array( '&lt;', '&#123;&#123;', '&#91;&#91;', '' ), $text );
+ // join word-wrapped lines into one
+ do {
+ $prev = $text;
+ $text = preg_replace( "/\n([\\*#\t])([^\n]*?)\n([^\n#\\*:]+)/", "\n\\1\\2 \\3", $text );
+ } while ( $text != $prev );
+ // Replace tab indents with colons
+ $text = preg_replace( '/^\t\t/m', '::', $text );
+ $text = preg_replace( '/^\t/m', ':', $text );
+ // turn (bug nnnn) into links
+ $text = preg_replace_callback('/bug (\d+)/', array( $this, 'replaceBugLinks' ), $text );
+ // add links to manual to every global variable mentioned
+ $text = preg_replace_callback('/(\$wg[a-z0-9_]+)/i', array( $this, 'replaceConfigLinks' ), $text );
+ return $text;
+ }
+
+ private function replaceBugLinks( $matches ) {
+ return '<span class="config-plainlink">[https://bugzilla.wikimedia.org/' .
+ $matches[1] . ' bug ' . $matches[1] . ']</span>';
+ }
+
+ private function replaceConfigLinks( $matches ) {
+ return '<span class="config-plainlink">[http://www.mediawiki.org/wiki/Manual:' .
+ $matches[1] . ' ' . $matches[1] . ']</span>';
+ }
+
+}
+
+class WebInstaller_Readme extends WebInstaller_Document {
+ protected function getFileName() { return 'README'; }
+}
+
+class WebInstaller_ReleaseNotes extends WebInstaller_Document {
+ protected function getFileName() { return 'RELEASE-NOTES'; }
+}
+
+class WebInstaller_UpgradeDoc extends WebInstaller_Document {
+ protected function getFileName() { return 'UPGRADE'; }
+}
+
+class WebInstaller_Copying extends WebInstaller_Document {
+ protected function getFileName() { return 'COPYING'; }
+}
+
diff --git a/includes/DoubleRedirectJob.php b/includes/job/DoubleRedirectJob.php
index 0857408a..3b4b0188 100644
--- a/includes/DoubleRedirectJob.php
+++ b/includes/job/DoubleRedirectJob.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Job to fix double redirects after moving a page
+ *
+ * @file
+ * @ingroup JobQueue
+ */
/**
* Job to fix double redirects after moving a page
diff --git a/includes/EmaillingJob.php b/includes/job/EmaillingJob.php
index 380c8982..89b74a41 100644
--- a/includes/EmaillingJob.php
+++ b/includes/job/EmaillingJob.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Old job for notification emails.
+ *
+ * @file
+ * @ingroup JobQueue
+ */
/**
* Old job used for sending single notification emails;
@@ -7,13 +13,12 @@
* @ingroup JobQueue
*/
class EmaillingJob extends Job {
-
function __construct( $title, $params, $id = 0 ) {
parent::__construct( 'sendMail', Title::newMainPage(), $params, $id );
}
function run() {
- userMailer(
+ UserMailer::send(
$this->params['to'],
$this->params['from'],
$this->params['subj'],
diff --git a/includes/EnotifNotifyJob.php b/includes/job/EnotifNotifyJob.php
index f7178d0f..5d2a08ea 100644
--- a/includes/EnotifNotifyJob.php
+++ b/includes/job/EnotifNotifyJob.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Job for notification emails.
+ *
+ * @file
+ * @ingroup JobQueue
+ */
/**
* Job for email notification mails
diff --git a/includes/JobQueue.php b/includes/job/JobQueue.php
index 4ab5eac6..8eec8215 100644
--- a/includes/JobQueue.php
+++ b/includes/job/JobQueue.php
@@ -1,5 +1,8 @@
<?php
/**
+ * Job queue base code
+ *
+ * @file
* @defgroup JobQueue JobQueue
*/
@@ -35,13 +38,6 @@ abstract class Job {
*------------------------------------------------------------------------*/
/**
- * @deprecated use LinksUpdate::queueRecursiveJobs()
- */
- /**
- * static function queueLinksJobs( $titles ) {}
- */
-
- /**
* 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.
@@ -76,7 +72,8 @@ abstract class Job {
$namespace = $row->job_namespace;
$dbkey = $row->job_title;
$title = Title::makeTitleSafe( $namespace, $dbkey );
- $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id );
+ $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ),
+ $row->job_id );
$dbw->delete( 'job', $job->insertFields(), __METHOD__ );
$dbw->commit();
@@ -120,7 +117,6 @@ abstract class Job {
return false;
}
}
- $offset = $row->job_id;
// Try to delete it from the master
$dbw = wfGetDB( DB_MASTER );
@@ -221,12 +217,12 @@ abstract class Job {
* @param $jobs array of Job objects
*/
static function batchInsert( $jobs ) {
- if( !count( $jobs ) ) {
+ if ( !count( $jobs ) ) {
return;
}
$dbw = wfGetDB( DB_MASTER );
$rows = array();
- foreach( $jobs as $job ) {
+ foreach ( $jobs as $job ) {
$rows[] = $job->insertFields();
if ( count( $rows ) >= 50 ) {
# Do a small transaction to avoid slave lag
@@ -236,11 +232,40 @@ abstract class Job {
$rows = array();
}
}
- if ( $rows ) {
+ if ( $rows ) { // last chunk
$dbw->begin();
$dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
$dbw->commit();
}
+ wfIncrStats( 'job-insert', count( $jobs ) );
+ }
+
+ /**
+ * 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 $jobs array of Job objects
+ */
+ static function safeBatchInsert( $jobs ) {
+ if ( !count( $jobs ) ) {
+ return;
+ }
+ $dbw = wfGetDB( DB_MASTER );
+ $rows = array();
+ foreach ( $jobs as $job ) {
+ $rows[] = $job->insertFields();
+ if ( count( $rows ) >= 500 ) {
+ $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
+ $rows = array();
+ }
+ }
+ if ( $rows ) { // last chunk
+ $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
+ }
+ wfIncrStats( 'job-insert', count( $jobs ) );
}
/*-------------------------------------------------------------------------
@@ -260,6 +285,7 @@ abstract class Job {
/**
* Insert a single job into the queue.
+ * @return bool true on success
*/
function insert() {
$fields = $this->insertFields();
@@ -272,7 +298,7 @@ abstract class Job {
return;
}
}
- $dbw->insert( 'job', $fields, __METHOD__ );
+ return $dbw->insert( 'job', $fields, __METHOD__ );
}
protected function insertFields() {
diff --git a/includes/RefreshLinksJob.php b/includes/job/RefreshLinksJob.php
index aba18362..cc91fa81 100644
--- a/includes/RefreshLinksJob.php
+++ b/includes/job/RefreshLinksJob.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Job to update links for a given title.
+ *
+ * @file
+ * @ingroup JobQueue
+ */
/**
* Background job to update links for a given title.
@@ -93,6 +99,8 @@ class RefreshLinksJob2 extends Job {
$jobs[] = new RefreshLinksJob( $title, '' );
}
Job::batchInsert( $jobs );
+
+ wfProfileOut( __METHOD__ );
return true;
}
# Re-parse each page that transcludes this page and update their tracking links...
diff --git a/includes/job/UploadFromUrlJob.php b/includes/job/UploadFromUrlJob.php
new file mode 100644
index 00000000..63166ef9
--- /dev/null
+++ b/includes/job/UploadFromUrlJob.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Job for asynchronous upload-by-url.
+ *
+ * @file
+ * @ingroup JobQueue
+ */
+
+/**
+ * Job for asynchronous upload-by-url.
+ *
+ * This job is in fact an interface to UploadFromUrl, which is designed such
+ * that it does not require any globals. If it does, fix it elsewhere, do not
+ * add globals in here.
+ *
+ * @ingroup JobQueue
+ */
+class UploadFromUrlJob extends Job {
+ const SESSION_KEYNAME = 'wsUploadFromUrlJobData';
+
+ public $upload;
+ protected $user;
+
+ public function __construct( $title, $params, $id = 0 ) {
+ parent::__construct( 'uploadFromUrl', $title, $params, $id );
+ }
+
+ public function run() {
+ # Initialize this object and the upload object
+ $this->upload = new UploadFromUrl();
+ $this->upload->initialize(
+ $this->title->getText(),
+ $this->params['url'],
+ false
+ );
+ $this->user = User::newFromName( $this->params['userName'] );
+
+ # Fetch the file
+ $status = $this->upload->fetchFile();
+ if ( !$status->isOk() ) {
+ $this->leaveMessage( $status );
+ return true;
+ }
+
+ # Verify upload
+ $result = $this->upload->verifyUpload();
+ if ( $result['status'] != UploadBase::OK ) {
+ $status = $this->upload->convertVerifyErrorToStatus( $result );
+ $this->leaveMessage( $status );
+ return true;
+ }
+
+ # Check warnings
+ if ( !$this->params['ignoreWarnings'] ) {
+ $warnings = $this->upload->checkWarnings();
+ if ( $warnings ) {
+ wfSetupSession( $this->params['sessionId'] );
+
+ if ( $this->params['leaveMessage'] ) {
+ $this->user->leaveUserMessage(
+ wfMsg( 'upload-warning-subj' ),
+ wfMsg( 'upload-warning-msg',
+ $this->params['sessionKey'],
+ $this->params['url'] )
+ );
+ } else {
+ $this->storeResultInSession( 'Warning',
+ 'warnings', $warnings );
+ }
+
+ # Stash the upload in the session
+ $this->upload->stashSession( $this->params['sessionKey'] );
+ session_write_close();
+
+ return true;
+ }
+ }
+
+ # Perform the upload
+ $status = $this->upload->performUpload(
+ $this->params['comment'],
+ $this->params['pageText'],
+ $this->params['watch'],
+ $this->user
+ );
+ $this->leaveMessage( $status );
+ return true;
+
+ }
+
+ /**
+ * Leave a message on the user talk page or in the session according to
+ * $params['leaveMessage'].
+ *
+ * @param $status Status
+ */
+ protected function leaveMessage( $status ) {
+ if ( $this->params['leaveMessage'] ) {
+ if ( $status->isGood() ) {
+ $this->user->leaveUserMessage( wfMsg( 'upload-success-subj' ),
+ wfMsg( 'upload-success-msg',
+ $this->upload->getTitle()->getText(),
+ $this->params['url']
+ ) );
+ } else {
+ $this->user->leaveUserMessage( wfMsg( 'upload-failure-subj' ),
+ wfMsg( 'upload-failure-msg',
+ $status->getWikiText(),
+ $this->params['url']
+ ) );
+ }
+ } else {
+ wfSetupSession( $this->params['sessionId'] );
+ if ( $status->isOk() ) {
+ $this->storeResultInSession( 'Success',
+ 'filename', $this->upload->getLocalFile()->getName() );
+ } else {
+ $this->storeResultInSession( 'Failure',
+ 'errors', $status->getErrorsArray() );
+ }
+ session_write_close();
+ }
+ }
+
+ /**
+ * Store a result in the session data. Note that the caller is responsible
+ * for appropriate session_start and session_write_close calls.
+ *
+ * @param $result String: the result (Success|Warning|Failure)
+ * @param $dataKey String: the key of the extra data
+ * @param $dataValue Mixed: the extra data itself
+ */
+ protected function storeResultInSession( $result, $dataKey, $dataValue ) {
+ $session =& self::getSessionData( $this->params['sessionKey'] );
+ $session['result'] = $result;
+ $session[$dataKey] = $dataValue;
+ }
+
+ /**
+ * Initialize the session data. Sets the intial result to queued.
+ */
+ public function initializeSessionData() {
+ $session =& self::getSessionData( $this->params['sessionKey'] );
+ $$session['result'] = 'Queued';
+ }
+
+ public static function &getSessionData( $key ) {
+ if ( !isset( $_SESSION[self::SESSION_KEYNAME][$key] ) ) {
+ $_SESSION[self::SESSION_KEYNAME][$key] = array();
+ }
+ return $_SESSION[self::SESSION_KEYNAME][$key];
+ }
+}
diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php
index 6db4a23f..b7049aeb 100644
--- a/includes/json/FormatJson.php
+++ b/includes/json/FormatJson.php
@@ -1,32 +1,60 @@
<?php
-/*
- * simple wrapper for json_econde and json_decode that falls back on Services_JSON class
+/**
+ * Simple wrapper for json_econde and json_decode that falls back on Services_JSON class
+ *
+ * @file
*/
-if( !(defined( 'MEDIAWIKI' ) ) ) {
+
+if ( !defined( 'MEDIAWIKI' ) ) {
die( 1 );
}
-class FormatJson{
- public static function encode($value, $isHtml=false){
+require_once dirname( __FILE__ ) . '/Services_JSON.php';
+
+class FormatJson {
+
+ /**
+ * Returns the JSON representation of a value.
+ *
+ * @param $value Mixed: the value being encoded. Can be any type except a resource.
+ * @param $isHtml Boolean
+ *
+ * @return string
+ */
+ public static function encode( $value, $isHtml = false ) {
// Some versions of PHP have a broken json_encode, see PHP bug
// 46944. Test encoding an affected character (U+20000) to
// avoid this.
- if (!function_exists('json_encode') || $isHtml || strtolower(json_encode("\xf0\xa0\x80\x80")) != '\ud840\udc00') {
+ if ( !function_exists( 'json_encode' ) || $isHtml || strtolower( json_encode( "\xf0\xa0\x80\x80" ) ) != '\ud840\udc00' ) {
$json = new Services_JSON();
- return $json->encode($value, $isHtml) ;
+ return $json->encode( $value, $isHtml );
} else {
- return json_encode($value);
+ return json_encode( $value );
}
}
- public static function decode( $value, $assoc=false ){
- if (!function_exists('json_decode') ) {
- $json = new Services_JSON();
- $jsonDec = $json->decode( $value );
+
+ /**
+ * Decodes a JSON string.
+ *
+ * @param $value String: the json string being decoded.
+ * @param $assoc Boolean: when true, returned objects will be converted into associative arrays.
+ *
+ * @return Mixed: the value encoded in json in appropriate PHP type.
+ * Values true, false and null (case-insensitive) are returned as true, false
+ * and &null; respectively. &null; is returned if the json cannot be
+ * decoded or if the encoded data is deeper than the recursion limit.
+ */
+ public static function decode( $value, $assoc = false ) {
+ if ( !function_exists( 'json_decode' ) ) {
if( $assoc )
- $jsonDec = wfObjectToArray( $jsonDec );
+ $json = new Services_JSON( SERVICES_JSON_LOOSE_TYPE );
+ else
+ $json = new Services_JSON();
+ $jsonDec = $json->decode( $value );
return $jsonDec;
} else {
return json_decode( $value, $assoc );
}
}
+
}
diff --git a/includes/json/Services_JSON.php b/includes/json/Services_JSON.php
index 588ece9c..5b4e0503 100644
--- a/includes/json/Services_JSON.php
+++ b/includes/json/Services_JSON.php
@@ -45,12 +45,13 @@
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
+* @file
* @ingroup API
* @author Michal Migurski <mike-json@teczno.com>
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @copyright 2005 Michal Migurski
-* @version CVS: $Id: Services_JSON.php 79562 2011-01-04 06:15:54Z tstarling $
+* @version CVS: $Id: Services_JSON.php 90492 2011-06-20 22:39:10Z reedy $
* @license http://www.opensource.org/licenses/bsd-license.php
* @see http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
@@ -118,7 +119,7 @@ class Services_JSON
/**
* constructs a new JSON instance
*
- * @param int $use object behavior flags; combine with boolean-OR
+ * @param $use Integer: object behavior flags; combine with boolean-OR
*
* possible values:
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
@@ -131,7 +132,7 @@ class Services_JSON
* bubble up with an error, so all return values
* from encode() should be checked with isError()
*/
- function Services_JSON($use = 0)
+ function __construct($use = 0)
{
$this->use = $use;
}
@@ -156,8 +157,8 @@ class Services_JSON
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
- * @param string $utf16 UTF-16 character
- * @return string UTF-8 character
+ * @param $utf16 String: UTF-16 character
+ * @return String: UTF-8 character
* @access private
*/
function utf162utf8($utf16)
@@ -211,8 +212,8 @@ class Services_JSON
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
- * @param string $utf8 UTF-8 character
- * @return string UTF-16 character
+ * @param $utf8 String: UTF-8 character
+ * @return String: UTF-16 character
* @access private
*/
function utf82utf16($utf8)
@@ -265,11 +266,11 @@ class Services_JSON
/**
* encodes an arbitrary variable into JSON format
*
- * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * @param $var Mixed: any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
- * @param bool $pretty pretty-print output with indents and newlines
+ * @param $pretty Boolean: pretty-print output with indents and newlines
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
@@ -285,7 +286,7 @@ class Services_JSON
/**
* encodes an arbitrary variable into JSON format
*
- * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * @param $var Mixed: any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
@@ -431,7 +432,7 @@ class Services_JSON
$this->indent--;
foreach($properties as $property) {
- if(Services_JSON::isError($property)) {
+ if($this->isError($property)) {
return $property;
}
}
@@ -445,7 +446,7 @@ class Services_JSON
$this->indent--;
foreach($elements as $element) {
- if(Services_JSON::isError($element)) {
+ if($this->isError($element)) {
return $element;
}
}
@@ -462,7 +463,7 @@ class Services_JSON
$this->indent--;
foreach($properties as $property) {
- if(Services_JSON::isError($property)) {
+ if($this->isError($property)) {
return $property;
}
}
@@ -479,17 +480,17 @@ class Services_JSON
/**
* array-walking function for use in generating JSON-formatted name-value pairs
*
- * @param string $name name of key to use
- * @param mixed $value reference to an array element to be encoded
+ * @param $name String: name of key to use
+ * @param $value Mixed: reference to an array element to be encoded
*
- * @return string JSON-formatted name-value pair, like '"name":value'
+ * @return String: JSON-formatted name-value pair, like '"name":value'
* @access private
*/
function name_value($name, $value)
{
$encoded_value = $this->encode2($value);
- if(Services_JSON::isError($encoded_value)) {
+ if($this->isError($encoded_value)) {
return $encoded_value;
}
@@ -499,9 +500,9 @@ class Services_JSON
/**
* reduce a string by removing leading and trailing comments and whitespace
*
- * @param $str string string value to strip of comments and whitespace
+ * @param $str String: string value to strip of comments and whitespace
*
- * @return string string value stripped of comments and whitespace
+ * @return String: string value stripped of comments and whitespace
* @access private
*/
function reduce_string($str)
@@ -526,7 +527,7 @@ class Services_JSON
/**
* decodes a JSON string into appropriate variable
*
- * @param string $str JSON-formatted string
+ * @param $str String: JSON-formatted string
*
* @return mixed number, boolean, string, array, or object
* corresponding to given JSON input string.
@@ -869,7 +870,12 @@ if (class_exists('PEAR_Error')) {
function Services_JSON_Error($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
-
+ $this->message = $message;
+ }
+
+ function __toString()
+ {
+ return $this->message;
}
}
}
diff --git a/includes/libs/CSSJanus.php b/includes/libs/CSSJanus.php
new file mode 100644
index 00000000..aa04bc49
--- /dev/null
+++ b/includes/libs/CSSJanus.php
@@ -0,0 +1,323 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+/**
+ * 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.
+ *
+ * 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,
+ '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,
+ '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;
+ }
+
+ $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']}+)";
+ $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_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
+ $patterns['right'] = "/{$patterns['lookbehind_not_letter']}(right){$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'] = "/{$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}/i";
+ $patterns['four_notation_color'] = "/(-color\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}/i";
+ // The two regexes below are parenthesized differently then in the original implementation to make the
+ // callback's job more straightforward
+ $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*[^%]*?)({$patterns['num']})(%\s*(?:{$patterns['quantity']}|{$patterns['ident']}))/";
+ $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)({$patterns['num']})(%)/";
+ }
+
+ /**
+ * Transform an LTR stylesheet to RTL
+ * @param $css String: 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 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 CSSJanus_Tokenizer( self::$patterns['noflip_single'], '`NOFLIP_SINGLE`' );
+ $css = $noFlipSingle->tokenize( $css );
+
+ // Tokenize class rules with /* @noflip */
+ $noFlipClass = new CSSJanus_Tokenizer( self::$patterns['noflip_class'], '`NOFLIP_CLASS`' );
+ $css = $noFlipClass->tokenize( $css );
+
+ // Tokenize comments
+ $comments = new CSSJanus_Tokenizer( 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::fixBackgroundPosition( $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 http://code.google.com/p/cssjanus/issues/detail?id=15 and
+ * TODO: URL
+ */
+ 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
+ */
+ 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
+ */
+ 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.
+ */
+ 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;
+ */
+ 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 http://code.google.com/p/cssjanus/issues/detail?id=16
+ */
+ private static function fixFourPartNotation( $css ) {
+ $css = preg_replace( self::$patterns['four_notation_quantity'], '$1$2$7$4$5$6$3', $css );
+ $css = preg_replace( self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4', $css );
+
+ return $css;
+ }
+
+ /**
+ * Flip horizontal background percentages.
+ */
+ private static function fixBackgroundPosition( $css ) {
+ $css = preg_replace_callback( self::$patterns['bg_horizontal_percentage'],
+ array( 'self', 'calculateNewBackgroundPosition' ), $css );
+ $css = preg_replace_callback( self::$patterns['bg_horizontal_percentage_x'],
+ array( 'self', 'calculateNewBackgroundPosition' ), $css );
+
+ return $css;
+ }
+
+ /**
+ * Callback for calculateNewBackgroundPosition()
+ */
+ private static function calculateNewBackgroundPosition( $matches ) {
+ return $matches[1] . ( 100 - $matches[2] ) . $matches[3];
+ }
+}
+
+/**
+ * Utility class used by CSSJanus that tokenizes and untokenizes things we want
+ * to protect from being janused.
+ * @author Roan Kattouw
+ */
+class CSSJanus_Tokenizer {
+ private $regex, $token;
+ private $originals;
+
+ /**
+ * Constructor
+ * @param $regex string Regular expression whose matches to replace by a token.
+ * @param $token string 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 $str String to tokenize
+ * @return string Tokenized string
+ */
+ public function tokenize( $str ) {
+ return preg_replace_callback( $this->regex, array( $this, 'tokenizeCallback' ), $str );
+ }
+
+ 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 $str String: 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 );
+ }
+
+ 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
new file mode 100644
index 00000000..c0e78112
--- /dev/null
+++ b/includes/libs/CSSMin.php
@@ -0,0 +1,214 @@
+<?php
+/*
+ * Copyright 2010 Wikimedia Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Transforms CSS data
+ *
+ * This class provides minification, URL remapping, URL extracting, and data-URL embedding.
+ *
+ * @file
+ * @version 0.1.1 -- 2010-09-11
+ * @author Trevor Parscal <tparscal@wikimedia.org>
+ * @copyright Copyright 2010 Wikimedia Foundation
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+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.
+ */
+ const EMBED_SIZE_LIMIT = 24576;
+ const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*)(?P<query>\??[^\)\'"]*)[\'"]?\s*\)';
+
+ /* Protected Static Members */
+
+ /** @var array List of common image files extensions and mime-types */
+ protected static $mimeTypes = array(
+ 'gif' => 'image/gif',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'xbm' => 'image/x-xbitmap',
+ );
+
+ /* Static Methods */
+
+ /**
+ * Gets a list of local file paths which are referenced in a CSS style sheet
+ *
+ * @param $source string CSS data to remap
+ * @param $path string File path where the source was read from (optional)
+ * @return array List of local file references
+ */
+ public static function getLocalFileReferences( $source, $path = null ) {
+ $files = array();
+ $rFlags = PREG_OFFSET_CAPTURE | PREG_SET_ORDER;
+ if ( preg_match_all( '/' . self::URL_REGEX . '/', $source, $matches, $rFlags ) ) {
+ foreach ( $matches as $match ) {
+ $file = ( isset( $path )
+ ? rtrim( $path, '/' ) . '/'
+ : '' ) . "{$match['file'][0]}";
+
+ // Only proceed if we can access the file
+ if ( !is_null( $path ) && file_exists( $file ) ) {
+ $files[] = $file;
+ }
+ }
+ }
+ return $files;
+ }
+
+ protected static function getMimeType( $file ) {
+ $realpath = realpath( $file );
+ // Try a couple of different ways to get the mime-type of a file, in order of
+ // preference
+ if (
+ $realpath
+ && function_exists( 'finfo_file' )
+ && function_exists( 'finfo_open' )
+ && defined( 'FILEINFO_MIME_TYPE' )
+ ) {
+ // As of PHP 5.3, this is how you get the mime-type of a file; it uses the Fileinfo
+ // PECL extension
+ return finfo_file( finfo_open( FILEINFO_MIME_TYPE ), $realpath );
+ } else if ( function_exists( 'mime_content_type' ) ) {
+ // Before this was deprecated in PHP 5.3, this was how you got the mime-type of a file
+ return mime_content_type( $file );
+ } else {
+ // Worst-case scenario has happened, use the file extension to infer the mime-type
+ $ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
+ if ( isset( self::$mimeTypes[$ext] ) ) {
+ return self::$mimeTypes[$ext];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remaps CSS URL paths and automatically embeds data URIs for URL rules
+ * preceded by an /* @embed * / comment
+ *
+ * @param $source string CSS data to remap
+ * @param $local string File path where the source was read from
+ * @param $remote string URL path to the file
+ * @param $embed ???
+ * @return string Remapped CSS data
+ */
+ public static function remap( $source, $local, $remote, $embed = true ) {
+ $pattern = '/((?P<embed>\s*\/\*\s*\@embed\s*\*\/)(?P<pre>[^\;\}]*))?' .
+ self::URL_REGEX . '(?P<post>[^;]*)[\;]?/';
+ $offset = 0;
+ while ( preg_match( $pattern, $source, $match, PREG_OFFSET_CAPTURE, $offset ) ) {
+ // Skip fully-qualified URLs and data URIs
+ $urlScheme = parse_url( $match['file'][0], PHP_URL_SCHEME );
+ if ( $urlScheme ) {
+ // Move the offset to the end of the match, leaving it alone
+ $offset = $match[0][1] + strlen( $match[0][0] );
+ continue;
+ }
+ // URLs with absolute paths like /w/index.php need to be expanded
+ // to absolute URLs but otherwise left alone
+ if ( $match['file'][0] !== '' && $match['file'][0][0] === '/' ) {
+ // Replace the file path with an expanded URL
+ $source = substr_replace( $source, wfExpandUrl( $match['file'][0] ),
+ $match['file'][1], strlen( $match['file'][0] )
+ );
+ // Move the offset to the end of the match, leaving it alone
+ $offset = $match[0][1] + strlen( $match[0][0] );
+ continue;
+ }
+ // Shortcuts
+ $embed = $match['embed'][0];
+ $pre = $match['pre'][0];
+ $post = $match['post'][0];
+ $query = $match['query'][0];
+ $url = "{$remote}/{$match['file'][0]}";
+ $file = "{$local}/{$match['file'][0]}";
+ // bug 27052 - Guard against double slashes, because foo//../bar
+ // apparently resolves to foo/bar on (some?) clients
+ $url = preg_replace( '#([^:])//+#', '\1/', $url );
+ $replacement = false;
+ if ( $local !== false && file_exists( $file ) ) {
+ // Add version parameter as a time-stamp in ISO 8601 format,
+ // using Z for the timezone, meaning GMT
+ $url .= '?' . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) );
+ // Embedding requires a bit of extra processing, so let's skip that if we can
+ if ( $embed ) {
+ $type = self::getMimeType( $file );
+ // Detect when URLs were preceeded with embed tags, and also verify file size is
+ // below the limit
+ if (
+ $type
+ && $match['embed'][1] > 0
+ && filesize( $file ) < self::EMBED_SIZE_LIMIT
+ ) {
+ // Strip off any trailing = symbols (makes browsers freak out)
+ $data = base64_encode( file_get_contents( $file ) );
+ // Build 2 CSS properties; one which uses a base64 encoded data URI in place
+ // of the @embed comment to try and retain line-number integrity, and the
+ // other with a remapped an versioned URL and an Internet Explorer hack
+ // making it ignored in all browsers that support data URIs
+ $replacement = "{$pre}url(data:{$type};base64,{$data}){$post};";
+ $replacement .= "{$pre}url({$url}){$post}!ie;";
+ }
+ }
+ if ( $replacement === false ) {
+ // Assume that all paths are relative to $remote, and make them absolute
+ $replacement = "{$embed}{$pre}url({$url}){$post};";
+ }
+ } else if ( $local === false ) {
+ // Assume that all paths are relative to $remote, and make them absolute
+ $replacement = "{$embed}{$pre}url({$url}{$query}){$post};";
+ }
+ if ( $replacement !== false ) {
+ // Perform replacement on the source
+ $source = substr_replace(
+ $source, $replacement, $match[0][1], strlen( $match[0][0] )
+ );
+ // Move the offset to the end of the replacement in the source
+ $offset = $match[0][1] + strlen( $replacement );
+ continue;
+ }
+ // Move the offset to the end of the match, leaving it alone
+ $offset = $match[0][1] + strlen( $match[0][0] );
+ }
+ return $source;
+ }
+
+ /**
+ * Removes whitespace from CSS data
+ *
+ * @param $css string CSS data to minify
+ * @return string Minified CSS data
+ */
+ public static function minify( $css ) {
+ return trim(
+ str_replace(
+ array( '; ', ': ', ' {', '{ ', ', ', '} ', ';}' ),
+ array( ';', ':', '{', '{', ',', '}', '}' ),
+ preg_replace( array( '/\s+/', '/\/\*.*?\*\//s' ), array( ' ', '' ), $css )
+ )
+ );
+ }
+}
diff --git a/includes/IEContentAnalyzer.php b/includes/libs/IEContentAnalyzer.php
index df4d36f0..a2ef1a09 100644
--- a/includes/IEContentAnalyzer.php
+++ b/includes/libs/IEContentAnalyzer.php
@@ -323,11 +323,11 @@ class IEContentAnalyzer {
* Get the MIME types from getMimesFromData(), but convert the result from IE's
* idiosyncratic private types into something other apps will understand.
*
- * @param string $fileName The file name (unused at present)
- * @param string $chunk The first 256 bytes of the file
- * @param string $proposed The MIME type proposed by the server
+ * @param $fileName String: the file name (unused at present)
+ * @param $chunk String: the first 256 bytes of the file
+ * @param $proposed String: the MIME type proposed by the server
*
- * @return array Map of IE version to detected mime type
+ * @return Array: map of IE version to detected mime type
*/
public function getRealMimesFromData( $fileName, $chunk, $proposed ) {
$types = $this->getMimesFromData( $fileName, $chunk, $proposed );
@@ -359,11 +359,11 @@ class IEContentAnalyzer {
/**
* Get the untranslated MIME types for all known versions
*
- * @param string $fileName The file name (unused at present)
- * @param string $chunk The first 256 bytes of the file
- * @param string $proposed The MIME type proposed by the server
+ * @param $fileName String: the file name (unused at present)
+ * @param $chunk String: the first 256 bytes of the file
+ * @param $proposed String: the MIME type proposed by the server
*
- * @return array Map of IE version to detected mime type
+ * @return Array: map of IE version to detected mime type
*/
public function getMimesFromData( $fileName, $chunk, $proposed ) {
$types = array();
diff --git a/includes/libs/IEUrlExtension.php b/includes/libs/IEUrlExtension.php
new file mode 100644
index 00000000..100454d4
--- /dev/null
+++ b/includes/libs/IEUrlExtension.php
@@ -0,0 +1,247 @@
+<?php
+
+/**
+ * Internet Explorer derives a cache filename from a URL, and then in certain
+ * circumstances, uses the extension of the resulting file to determine the
+ * content type of the data, ignoring the Content-Type header.
+ *
+ * This can be a problem, especially when non-HTML content is sent by MediaWiki,
+ * and Internet Explorer interprets it as HTML, exposing an XSS vulnerability.
+ *
+ * Usually the script filename (e.g. api.php) is present in the URL, and this
+ * makes Internet Explorer think the extension is a harmless script extension.
+ * But Internet Explorer 6 and earlier allows the script extension to be
+ * obscured by encoding the dot as "%2E".
+ *
+ * This class contains functions which help in detecting and dealing with this
+ * situation.
+ *
+ * Checking the URL for a bad extension is somewhat complicated due to the fact
+ * that CGI doesn't provide a standard method to determine the URL. Instead it
+ * is necessary to pass a subset of $_SERVER variables, which we then attempt
+ * to use to guess parts of the URL.
+ */
+class IEUrlExtension {
+ /**
+ * Check a subset of $_SERVER (or the whole of $_SERVER if you like)
+ * to see if it indicates that the request was sent with a bad file
+ * extension. Returns true if the request should be denied or modified,
+ * false otherwise. The relevant $_SERVER elements are:
+ *
+ * - SERVER_SOFTWARE
+ * - REQUEST_URI
+ * - QUERY_STRING
+ * - PATH_INFO
+ *
+ * If the a variable is unset in $_SERVER, it should be unset in $vars.
+ *
+ * @param $vars A subset of $_SERVER.
+ * @param $extWhitelist Extensions which are allowed, assumed harmless.
+ */
+ public static function areServerVarsBad( $vars, $extWhitelist = array() ) {
+ // Check QUERY_STRING or REQUEST_URI
+ if ( isset( $vars['SERVER_SOFTWARE'] )
+ && isset( $vars['REQUEST_URI'] )
+ && self::haveUndecodedRequestUri( $vars['SERVER_SOFTWARE'] ) )
+ {
+ $urlPart = $vars['REQUEST_URI'];
+ } elseif ( isset( $vars['QUERY_STRING'] ) ) {
+ $urlPart = $vars['QUERY_STRING'];
+ } else {
+ $urlPart = '';
+ }
+
+ if ( self::isUrlExtensionBad( $urlPart, $extWhitelist ) ) {
+ return true;
+ }
+
+ // Some servers have PATH_INFO but not REQUEST_URI, so we check both
+ // to be on the safe side.
+ if ( isset( $vars['PATH_INFO'] )
+ && self::isUrlExtensionBad( $vars['PATH_INFO'], $extWhitelist ) )
+ {
+ return true;
+ }
+
+ // All checks passed
+ return false;
+ }
+
+ /**
+ * Given a right-hand portion of a URL, determine whether IE would detect
+ * a potentially harmful file extension.
+ *
+ * @param $urlPart The right-hand portion of a URL
+ * @param $extWhitelist An array of file extensions which may occur in this
+ * URL, and which should be allowed.
+ * @return bool
+ */
+ public static function isUrlExtensionBad( $urlPart, $extWhitelist = array() ) {
+ if ( strval( $urlPart ) === '' ) {
+ return false;
+ }
+
+ $extension = self::findIE6Extension( $urlPart );
+ if ( strval( $extension ) === '' ) {
+ // No extension or empty extension
+ return false;
+ }
+
+ if ( in_array( $extension, array( 'php', 'php5' ) ) ) {
+ // Script extension, OK
+ return false;
+ }
+ if ( in_array( $extension, $extWhitelist ) ) {
+ // Whitelisted extension
+ return false;
+ }
+
+ if ( !preg_match( '/^[a-zA-Z0-9_-]+$/', $extension ) ) {
+ // Non-alphanumeric extension, unlikely to be registered.
+ //
+ // The regex above is known to match all registered file extensions
+ // in a default Windows XP installation. It's important to allow
+ // extensions with ampersands and percent signs, since that reduces
+ // the number of false positives substantially.
+ return false;
+ }
+
+ // Possibly bad extension
+ return true;
+ }
+
+ /**
+ * Returns a variant of $url which will pass isUrlExtensionBad() but has the
+ * same GET parameters, or false if it can't figure one out.
+ */
+ public static function fixUrlForIE6( $url, $extWhitelist = array() ) {
+ $questionPos = strpos( $url, '?' );
+ if ( $questionPos === false ) {
+ $beforeQuery = $url . '?';
+ $query = '';
+ } elseif ( $questionPos === strlen( $url ) - 1 ) {
+ $beforeQuery = $url;
+ $query = '';
+ } else {
+ $beforeQuery = substr( $url, 0, $questionPos + 1 );
+ $query = substr( $url, $questionPos + 1 );
+ }
+
+ // Multiple question marks cause problems. Encode the second and
+ // subsequent question mark.
+ $query = str_replace( '?', '%3E', $query );
+ // Append an invalid path character so that IE6 won't see the end of the
+ // query string as an extension
+ $query .= '&*';
+ // Put the URL back together
+ $url = $beforeQuery . $query;
+ if ( self::isUrlExtensionBad( $url, $extWhitelist ) ) {
+ // Avoid a redirect loop
+ return false;
+ }
+ return $url;
+ }
+
+ /**
+ * Determine what extension IE6 will infer from a certain query string.
+ * If the URL has an extension before the question mark, IE6 will use
+ * that and ignore the query string, but per the comment at
+ * isPathInfoBad() we don't have a reliable way to determine the URL,
+ * so isPathInfoBad() just passes in the query string for $url.
+ * All entry points have safe extensions (php, php5) anyway, so
+ * checking the query string is possibly overly paranoid but never
+ * insecure.
+ *
+ * The criteria for finding an extension are as follows:
+ * - a possible extension is a dot followed by one or more characters not
+ * in <>\"/:|?.#
+ * - if we find a possible extension followed by the end of the string or
+ * a #, that's our extension
+ * - if we find a possible extension followed by a ?, that's our extension
+ * - UNLESS it's exe, dll or cgi, in which case we ignore it and continue
+ * searching for another possible extension
+ * - if we find a possible extension followed by a dot or another illegal
+ * character, we ignore it and continue searching
+ *
+ * @param $url string URL
+ * @return mixed Detected extension (string), or false if none found
+ */
+ public static function findIE6Extension( $url ) {
+ $pos = 0;
+ $hashPos = strpos( $url, '#' );
+ if ( $hashPos !== false ) {
+ $urlLength = $hashPos;
+ } else {
+ $urlLength = strlen( $url );
+ }
+ $remainingLength = $urlLength;
+ while ( $remainingLength > 0 ) {
+ // Skip ahead to the next dot
+ $pos += strcspn( $url, '.', $pos, $remainingLength );
+ if ( $pos >= $urlLength ) {
+ // End of string, we're done
+ return false;
+ }
+
+ // We found a dot. Skip past it
+ $pos++;
+ $remainingLength = $urlLength - $pos;
+
+ // Check for illegal characters in our prospective extension,
+ // or for another dot
+ $nextPos = $pos + strcspn( $url, "<>\\\"/:|?*.", $pos, $remainingLength );
+ if ( $nextPos >= $urlLength ) {
+ // No illegal character or next dot
+ // We have our extension
+ return substr( $url, $pos, $urlLength - $pos );
+ }
+ if ( $url[$nextPos] === '?' ) {
+ // We've found a legal extension followed by a question mark
+ // If the extension is NOT exe, dll or cgi, return it
+ $extension = substr( $url, $pos, $nextPos - $pos );
+ if ( strcasecmp( $extension, 'exe' ) && strcasecmp( $extension, 'dll' ) &&
+ strcasecmp( $extension, 'cgi' ) )
+ {
+ return $extension;
+ }
+ // Else continue looking
+ }
+ // We found an illegal character or another dot
+ // Skip to that character and continue the loop
+ $pos = $nextPos + 1;
+ $remainingLength = $urlLength - $pos;
+ }
+ return false;
+ }
+
+ /**
+ * When passed the value of $_SERVER['SERVER_SOFTWARE'], this function
+ * returns true if that server is known to have a REQUEST_URI variable
+ * with %2E not decoded to ".". On such a server, it is possible to detect
+ * whether the script filename has been obscured.
+ *
+ * The function returns false if the server is not known to have this
+ * behaviour. Microsoft IIS in particular is known to decode escaped script
+ * filenames.
+ *
+ * SERVER_SOFTWARE typically contains either a plain string such as "Zeus",
+ * or a specification in the style of a User-Agent header, such as
+ * "Apache/1.3.34 (Unix) mod_ssl/2.8.25 OpenSSL/0.9.8a PHP/4.4.2"
+ *
+ * @param $serverSoftware
+ * @return bool
+ *
+ */
+ public static function haveUndecodedRequestUri( $serverSoftware ) {
+ static $whitelist = array(
+ 'Apache',
+ 'Zeus',
+ 'LiteSpeed' );
+ if ( preg_match( '/^(.*?)($|\/| )/', $serverSoftware, $m ) ) {
+ return in_array( $m[1], $whitelist );
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/includes/libs/JavaScriptMinifier.php b/includes/libs/JavaScriptMinifier.php
new file mode 100644
index 00000000..a991d915
--- /dev/null
+++ b/includes/libs/JavaScriptMinifier.php
@@ -0,0 +1,579 @@
+<?php
+/**
+ * JavaScript Minifier
+ *
+ * This class is meant to safely minify javascript code, while leaving syntactically correct
+ * programs intact. Other libraries, such as JSMin require a certain coding style to work
+ * correctly. OTOH, libraries like jsminplus, that do parse the code correctly are rather
+ * slow, because they construct a complete parse tree before outputting the code minified.
+ * So this class is meant to allow arbitrary (but syntactically correct) input, while being
+ * fast enough to be used for on-the-fly minifying.
+ *
+ * Author: Paul Copperman <paul.copperman@gmail.com>
+ * License: choose any of Apache, MIT, GPL, LGPL
+ */
+
+class JavaScriptMinifier {
+
+ /* Class constants */
+ /* Parsing states.
+ * The state machine is only necessary to decide whether to parse a slash as division
+ * operator or as regexp literal.
+ * States are named after the next expected item. We only distinguish states when the
+ * distinction is relevant for our purpose.
+ */
+ const STATEMENT = 0;
+ const CONDITION = 1;
+ const PROPERTY_ASSIGNMENT = 2;
+ const EXPRESSION = 3;
+ const EXPRESSION_NO_NL = 4; // only relevant for semicolon insertion
+ const EXPRESSION_OP = 5;
+ const EXPRESSION_FUNC = 6;
+ const EXPRESSION_TERNARY = 7; // used to determine the role of a colon
+ const EXPRESSION_TERNARY_OP = 8;
+ const EXPRESSION_TERNARY_FUNC = 9;
+ const PAREN_EXPRESSION = 10; // expression which is not on the top level
+ const PAREN_EXPRESSION_OP = 11;
+ const PAREN_EXPRESSION_FUNC = 12;
+ const PROPERTY_EXPRESSION = 13; // expression which is within an object literal
+ const PROPERTY_EXPRESSION_OP = 14;
+ const PROPERTY_EXPRESSION_FUNC = 15;
+
+ /* Token types */
+ const TYPE_UN_OP = 1; // unary operators
+ const TYPE_INCR_OP = 2; // ++ and --
+ const TYPE_BIN_OP = 3; // binary operators
+ const TYPE_ADD_OP = 4; // + and - which can be either unary or binary ops
+ const TYPE_HOOK = 5; // ?
+ const TYPE_COLON = 6; // :
+ const TYPE_COMMA = 7; // ,
+ const TYPE_SEMICOLON = 8; // ;
+ const TYPE_BRACE_OPEN = 9; // {
+ const TYPE_BRACE_CLOSE = 10; // }
+ const TYPE_PAREN_OPEN = 11; // ( and [
+ const TYPE_PAREN_CLOSE = 12; // ) and ]
+ const TYPE_RETURN = 13; // keywords: break, continue, return, throw
+ const TYPE_IF = 14; // keywords: catch, for, with, switch, while, if
+ const TYPE_DO = 15; // keywords: case, var, finally, else, do, try
+ const TYPE_FUNC = 16; // keywords: function
+ const TYPE_LITERAL = 17; // all literals, identifiers and unrecognised tokens
+
+ // Sanity limit to avoid excessive memory usage
+ const STACK_LIMIT = 1000;
+
+ /* Static functions */
+
+ /**
+ * Returns minified JavaScript code.
+ *
+ * NOTE: $maxLineLength isn't a strict maximum. Longer lines will be produced when
+ * literals (e.g. quoted strings) longer than $maxLineLength are encountered
+ * or when required to guard against semicolon insertion.
+ *
+ * @param $s String JavaScript code to minify
+ * @param $statementsOnOwnLine Bool Whether to put each statement on its own line
+ * @param $maxLineLength Int Maximum length of a single line, or -1 for no maximum.
+ * @return String Minified code
+ */
+ public static function minify( $s, $statementsOnOwnLine = false, $maxLineLength = 1000 ) {
+ // First we declare a few tables that contain our parsing rules
+
+ // $opChars : characters, which can be combined without whitespace in between them
+ $opChars = array(
+ '!' => true,
+ '"' => true,
+ '%' => true,
+ '&' => true,
+ "'" => true,
+ '(' => true,
+ ')' => true,
+ '*' => true,
+ '+' => true,
+ ',' => true,
+ '-' => true,
+ '.' => true,
+ '/' => true,
+ ':' => true,
+ ';' => true,
+ '<' => true,
+ '=' => true,
+ '>' => true,
+ '?' => true,
+ '[' => true,
+ ']' => true,
+ '^' => true,
+ '{' => true,
+ '|' => true,
+ '}' => true,
+ '~' => true
+ );
+
+ // $tokenTypes : maps keywords and operators to their corresponding token type
+ $tokenTypes = array(
+ '!' => self::TYPE_UN_OP,
+ '~' => self::TYPE_UN_OP,
+ 'delete' => self::TYPE_UN_OP,
+ 'new' => self::TYPE_UN_OP,
+ 'typeof' => self::TYPE_UN_OP,
+ 'void' => self::TYPE_UN_OP,
+ '++' => self::TYPE_INCR_OP,
+ '--' => self::TYPE_INCR_OP,
+ '!=' => self::TYPE_BIN_OP,
+ '!==' => self::TYPE_BIN_OP,
+ '%' => self::TYPE_BIN_OP,
+ '%=' => self::TYPE_BIN_OP,
+ '&' => self::TYPE_BIN_OP,
+ '&&' => self::TYPE_BIN_OP,
+ '&=' => self::TYPE_BIN_OP,
+ '*' => self::TYPE_BIN_OP,
+ '*=' => self::TYPE_BIN_OP,
+ '+=' => self::TYPE_BIN_OP,
+ '-=' => self::TYPE_BIN_OP,
+ '.' => self::TYPE_BIN_OP,
+ '/' => self::TYPE_BIN_OP,
+ '/=' => self::TYPE_BIN_OP,
+ '<' => self::TYPE_BIN_OP,
+ '<<' => self::TYPE_BIN_OP,
+ '<<=' => self::TYPE_BIN_OP,
+ '<=' => self::TYPE_BIN_OP,
+ '=' => self::TYPE_BIN_OP,
+ '==' => self::TYPE_BIN_OP,
+ '===' => self::TYPE_BIN_OP,
+ '>' => self::TYPE_BIN_OP,
+ '>=' => self::TYPE_BIN_OP,
+ '>>' => self::TYPE_BIN_OP,
+ '>>=' => self::TYPE_BIN_OP,
+ '>>>' => self::TYPE_BIN_OP,
+ '>>>=' => self::TYPE_BIN_OP,
+ '^' => self::TYPE_BIN_OP,
+ '^=' => self::TYPE_BIN_OP,
+ '|' => self::TYPE_BIN_OP,
+ '|=' => self::TYPE_BIN_OP,
+ '||' => self::TYPE_BIN_OP,
+ 'in' => self::TYPE_BIN_OP,
+ 'instanceof' => self::TYPE_BIN_OP,
+ '+' => self::TYPE_ADD_OP,
+ '-' => self::TYPE_ADD_OP,
+ '?' => self::TYPE_HOOK,
+ ':' => self::TYPE_COLON,
+ ',' => self::TYPE_COMMA,
+ ';' => self::TYPE_SEMICOLON,
+ '{' => self::TYPE_BRACE_OPEN,
+ '}' => self::TYPE_BRACE_CLOSE,
+ '(' => self::TYPE_PAREN_OPEN,
+ '[' => self::TYPE_PAREN_OPEN,
+ ')' => self::TYPE_PAREN_CLOSE,
+ ']' => self::TYPE_PAREN_CLOSE,
+ 'break' => self::TYPE_RETURN,
+ 'continue' => self::TYPE_RETURN,
+ 'return' => self::TYPE_RETURN,
+ 'throw' => self::TYPE_RETURN,
+ 'catch' => self::TYPE_IF,
+ 'for' => self::TYPE_IF,
+ 'if' => self::TYPE_IF,
+ 'switch' => self::TYPE_IF,
+ 'while' => self::TYPE_IF,
+ 'with' => self::TYPE_IF,
+ 'case' => self::TYPE_DO,
+ 'do' => self::TYPE_DO,
+ 'else' => self::TYPE_DO,
+ 'finally' => self::TYPE_DO,
+ 'try' => self::TYPE_DO,
+ 'var' => self::TYPE_DO,
+ 'function' => self::TYPE_FUNC
+ );
+
+ // $goto : This is the main table for our state machine. For every state/token pair
+ // the following state is defined. When no rule exists for a given pair,
+ // the state is left unchanged.
+ $goto = array(
+ self::STATEMENT => array(
+ self::TYPE_UN_OP => self::EXPRESSION,
+ self::TYPE_INCR_OP => self::EXPRESSION,
+ self::TYPE_ADD_OP => self::EXPRESSION,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_RETURN => self::EXPRESSION_NO_NL,
+ self::TYPE_IF => self::CONDITION,
+ self::TYPE_FUNC => self::CONDITION,
+ self::TYPE_LITERAL => self::EXPRESSION_OP
+ ),
+ self::CONDITION => array(
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::PROPERTY_ASSIGNMENT => array(
+ self::TYPE_COLON => self::PROPERTY_EXPRESSION,
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ ),
+ self::EXPRESSION => array(
+ self::TYPE_SEMICOLON => self::STATEMENT,
+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC => self::EXPRESSION_FUNC,
+ self::TYPE_LITERAL => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_NO_NL => array(
+ self::TYPE_SEMICOLON => self::STATEMENT,
+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC => self::EXPRESSION_FUNC,
+ self::TYPE_LITERAL => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_OP => array(
+ self::TYPE_BIN_OP => self::EXPRESSION,
+ self::TYPE_ADD_OP => self::EXPRESSION,
+ self::TYPE_HOOK => self::EXPRESSION_TERNARY,
+ self::TYPE_COLON => self::STATEMENT,
+ self::TYPE_COMMA => self::EXPRESSION,
+ self::TYPE_SEMICOLON => self::STATEMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ ),
+ self::EXPRESSION_TERNARY => array(
+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC => self::EXPRESSION_TERNARY_FUNC,
+ self::TYPE_LITERAL => self::EXPRESSION_TERNARY_OP
+ ),
+ self::EXPRESSION_TERNARY_OP => array(
+ self::TYPE_BIN_OP => self::EXPRESSION_TERNARY,
+ self::TYPE_ADD_OP => self::EXPRESSION_TERNARY,
+ self::TYPE_HOOK => self::EXPRESSION_TERNARY,
+ self::TYPE_COMMA => self::EXPRESSION_TERNARY,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::EXPRESSION_TERNARY_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ ),
+ self::PAREN_EXPRESSION => array(
+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC => self::PAREN_EXPRESSION_FUNC,
+ self::TYPE_LITERAL => self::PAREN_EXPRESSION_OP
+ ),
+ self::PAREN_EXPRESSION_OP => array(
+ self::TYPE_BIN_OP => self::PAREN_EXPRESSION,
+ self::TYPE_ADD_OP => self::PAREN_EXPRESSION,
+ self::TYPE_HOOK => self::PAREN_EXPRESSION,
+ self::TYPE_COLON => self::PAREN_EXPRESSION,
+ self::TYPE_COMMA => self::PAREN_EXPRESSION,
+ self::TYPE_SEMICOLON => self::PAREN_EXPRESSION,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::PAREN_EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ ),
+ self::PROPERTY_EXPRESSION => array(
+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC => self::PROPERTY_EXPRESSION_FUNC,
+ self::TYPE_LITERAL => self::PROPERTY_EXPRESSION_OP
+ ),
+ self::PROPERTY_EXPRESSION_OP => array(
+ self::TYPE_BIN_OP => self::PROPERTY_EXPRESSION,
+ self::TYPE_ADD_OP => self::PROPERTY_EXPRESSION,
+ self::TYPE_HOOK => self::PROPERTY_EXPRESSION,
+ self::TYPE_COMMA => self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::PROPERTY_EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ )
+ );
+
+ // $push : This table contains the rules for when to push a state onto the stack.
+ // The pushed state is the state to return to when the corresponding
+ // closing token is found
+ $push = array(
+ self::STATEMENT => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
+ ),
+ self::CONDITION => array(
+ self::TYPE_PAREN_OPEN => self::STATEMENT
+ ),
+ self::PROPERTY_ASSIGNMENT => array(
+ self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT
+ ),
+ self::EXPRESSION => array(
+ self::TYPE_BRACE_OPEN => self::EXPRESSION_OP,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_NO_NL => array(
+ self::TYPE_BRACE_OPEN => self::EXPRESSION_OP,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_OP => array(
+ self::TYPE_HOOK => self::EXPRESSION,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_TERNARY => array(
+ self::TYPE_BRACE_OPEN => self::EXPRESSION_TERNARY_OP,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_TERNARY_OP
+ ),
+ self::EXPRESSION_TERNARY_OP => array(
+ self::TYPE_HOOK => self::EXPRESSION_TERNARY,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_TERNARY_OP
+ ),
+ self::EXPRESSION_TERNARY_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::EXPRESSION_TERNARY_OP
+ ),
+ self::PAREN_EXPRESSION => array(
+ self::TYPE_BRACE_OPEN => self::PAREN_EXPRESSION_OP,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION_OP
+ ),
+ self::PAREN_EXPRESSION_OP => array(
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION_OP
+ ),
+ self::PAREN_EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::PAREN_EXPRESSION_OP
+ ),
+ self::PROPERTY_EXPRESSION => array(
+ self::TYPE_BRACE_OPEN => self::PROPERTY_EXPRESSION_OP,
+ self::TYPE_PAREN_OPEN => self::PROPERTY_EXPRESSION_OP
+ ),
+ self::PROPERTY_EXPRESSION_OP => array(
+ self::TYPE_PAREN_OPEN => self::PROPERTY_EXPRESSION_OP
+ ),
+ self::PROPERTY_EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::PROPERTY_EXPRESSION_OP
+ )
+ );
+
+ // $pop : Rules for when to pop a state from the stack
+ $pop = array(
+ self::STATEMENT => array( self::TYPE_BRACE_CLOSE => true ),
+ self::PROPERTY_ASSIGNMENT => array( self::TYPE_BRACE_CLOSE => true ),
+ self::EXPRESSION => array( self::TYPE_BRACE_CLOSE => true ),
+ self::EXPRESSION_NO_NL => array( self::TYPE_BRACE_CLOSE => true ),
+ self::EXPRESSION_OP => array( self::TYPE_BRACE_CLOSE => true ),
+ self::EXPRESSION_TERNARY_OP => array( self::TYPE_COLON => true ),
+ self::PAREN_EXPRESSION => array( self::TYPE_PAREN_CLOSE => true ),
+ self::PAREN_EXPRESSION_OP => array( self::TYPE_PAREN_CLOSE => true ),
+ self::PROPERTY_EXPRESSION => array( self::TYPE_BRACE_CLOSE => true ),
+ self::PROPERTY_EXPRESSION_OP => array( self::TYPE_BRACE_CLOSE => true )
+ );
+
+ // $semicolon : Rules for when a semicolon insertion is appropriate
+ $semicolon = array(
+ self::EXPRESSION_NO_NL => array(
+ self::TYPE_UN_OP => true,
+ self::TYPE_INCR_OP => true,
+ self::TYPE_ADD_OP => true,
+ self::TYPE_BRACE_OPEN => true,
+ self::TYPE_PAREN_OPEN => true,
+ self::TYPE_RETURN => true,
+ self::TYPE_IF => true,
+ self::TYPE_DO => true,
+ self::TYPE_FUNC => true,
+ self::TYPE_LITERAL => true
+ ),
+ self::EXPRESSION_OP => array(
+ self::TYPE_UN_OP => true,
+ self::TYPE_INCR_OP => true,
+ self::TYPE_BRACE_OPEN => true,
+ self::TYPE_RETURN => true,
+ self::TYPE_IF => true,
+ self::TYPE_DO => true,
+ self::TYPE_FUNC => true,
+ self::TYPE_LITERAL => true
+ )
+ );
+
+ // Rules for when newlines should be inserted if
+ // $statementsOnOwnLine is enabled.
+ // $newlineBefore is checked before switching state,
+ // $newlineAfter is checked after
+ $newlineBefore = array(
+ self::STATEMENT => array(
+ self::TYPE_BRACE_CLOSE => true,
+ ),
+ );
+ $newlineAfter = array(
+ self::STATEMENT => array(
+ self::TYPE_BRACE_OPEN => true,
+ self::TYPE_PAREN_CLOSE => true,
+ self::TYPE_SEMICOLON => true,
+ ),
+ );
+
+ // $divStates : Contains all states that can be followed by a division operator
+ $divStates = array(
+ self::EXPRESSION_OP => true,
+ self::EXPRESSION_TERNARY_OP => true,
+ self::PAREN_EXPRESSION_OP => true,
+ self::PROPERTY_EXPRESSION_OP => true
+ );
+
+ // Here's where the minifying takes place: Loop through the input, looking for tokens
+ // and output them to $out, taking actions to the above defined rules when appropriate.
+ $out = '';
+ $pos = 0;
+ $length = strlen( $s );
+ $lineLength = 0;
+ $newlineFound = true;
+ $state = self::STATEMENT;
+ $stack = array();
+ $last = ';'; // Pretend that we have seen a semicolon yet
+ while( $pos < $length ) {
+ // First, skip over any whitespace and multiline comments, recording whether we
+ // found any newline character
+ $skip = strspn( $s, " \t\n\r\xb\xc", $pos );
+ if( !$skip ) {
+ $ch = $s[$pos];
+ if( $ch === '/' && substr( $s, $pos, 2 ) === '/*' ) {
+ // Multiline comment. Search for the end token or EOT.
+ $end = strpos( $s, '*/', $pos + 2 );
+ $skip = $end === false ? $length - $pos : $end - $pos + 2;
+ }
+ }
+ if( $skip ) {
+ // The semicolon insertion mechanism needs to know whether there was a newline
+ // between two tokens, so record it now.
+ if( !$newlineFound && strcspn( $s, "\r\n", $pos, $skip ) !== $skip ) {
+ $newlineFound = true;
+ }
+ $pos += $skip;
+ continue;
+ }
+ // Handle C++-style comments and html comments, which are treated as single line
+ // comments by the browser, regardless of whether the end tag is on the same line.
+ // Handle --> the same way, but only if it's at the beginning of the line
+ if( ( $ch === '/' && substr( $s, $pos, 2 ) === '//' )
+ || ( $ch === '<' && substr( $s, $pos, 4 ) === '<!--' )
+ || ( $ch === '-' && $newlineFound && substr( $s, $pos, 3 ) === '-->' )
+ ) {
+ $pos += strcspn( $s, "\r\n", $pos );
+ continue;
+ }
+
+ // Find out which kind of token we're handling. $end will point past the end of it.
+ $end = $pos + 1;
+ // Handle string literals
+ if( $ch === "'" || $ch === '"' ) {
+ // Search to the end of the string literal, skipping over backslash escapes
+ $search = $ch . '\\';
+ do{
+ $end += strcspn( $s, $search, $end ) + 2;
+ } while( $end - 2 < $length && $s[$end - 2] === '\\' );
+ $end--;
+ // We have to distinguish between regexp literals and division operators
+ // A division operator is only possible in certain states
+ } elseif( $ch === '/' && !isset( $divStates[$state] ) ) {
+ // Regexp literal, search to the end, skipping over backslash escapes and
+ // character classes
+ for( ; ; ) {
+ do{
+ $end += strcspn( $s, '/[\\', $end ) + 2;
+ } while( $end - 2 < $length && $s[$end - 2] === '\\' );
+ $end--;
+ if( $end - 1 >= $length || $s[$end - 1] === '/' ) {
+ break;
+ }
+ do{
+ $end += strcspn( $s, ']\\', $end ) + 2;
+ } while( $end - 2 < $length && $s[$end - 2] === '\\' );
+ $end--;
+ };
+ // Search past the regexp modifiers (gi)
+ while( $end < $length && ctype_alpha( $s[$end] ) ) {
+ $end++;
+ }
+ } elseif(
+ ctype_digit( $ch )
+ || ( $ch === '.' && $pos + 1 < $length && ctype_digit( $s[$pos + 1] ) )
+ ) {
+ // Numeric literal. Search for the end of it, but don't care about [+-]exponent
+ // at the end, as the results of "numeric [+-] numeric" and "numeric" are
+ // identical to our state machine.
+ $end += strspn( $s, '0123456789ABCDEFabcdefXx.', $end );
+ while( $s[$end - 1] === '.' ) {
+ // Special case: When a numeric ends with a dot, we have to check the
+ // literal for proper syntax
+ $decimal = strspn( $s, '0123456789', $pos, $end - $pos - 1 );
+ if( $decimal === $end - $pos - 1 ) {
+ break;
+ } else {
+ $end--;
+ }
+ }
+ } elseif( isset( $opChars[$ch] ) ) {
+ // Punctuation character. Search for the longest matching operator.
+ while(
+ $end < $length
+ && isset( $tokenTypes[substr( $s, $pos, $end - $pos + 1 )] )
+ ) {
+ $end++;
+ }
+ } else {
+ // Identifier or reserved word. Search for the end by excluding whitespace and
+ // punctuation.
+ $end += strcspn( $s, " \t\n.;,=<>+-{}()[]?:*/%'\"!&|^~\xb\xc\r", $end );
+ }
+
+ // Now get the token type from our type array
+ $token = substr( $s, $pos, $end - $pos ); // so $end - $pos == strlen( $token )
+ $type = isset( $tokenTypes[$token] ) ? $tokenTypes[$token] : self::TYPE_LITERAL;
+
+ if( $newlineFound && isset( $semicolon[$state][$type] ) ) {
+ // This token triggers the semicolon insertion mechanism of javascript. While we
+ // could add the ; token here ourselves, keeping the newline has a few advantages.
+ $out .= "\n";
+ $state = self::STATEMENT;
+ $lineLength = 0;
+ } elseif( $maxLineLength > 0 && $lineLength + $end - $pos > $maxLineLength &&
+ !isset( $semicolon[$state][$type] ) && $type !== self::TYPE_INCR_OP )
+ {
+ // This line would get too long if we added $token, so add a newline first.
+ // Only do this if it won't trigger semicolon insertion and if it won't
+ // put a postfix increment operator on its own line, which is illegal in js.
+ $out .= "\n";
+ $lineLength = 0;
+ // Check, whether we have to separate the token from the last one with whitespace
+ } elseif( !isset( $opChars[$last] ) && !isset( $opChars[$ch] ) ) {
+ $out .= ' ';
+ $lineLength++;
+ // Don't accidentally create ++, -- or // tokens
+ } elseif( $last === $ch && ( $ch === '+' || $ch === '-' || $ch === '/' ) ) {
+ $out .= ' ';
+ $lineLength++;
+ }
+
+ $out .= $token;
+ $lineLength += $end - $pos; // += strlen( $token )
+ $last = $s[$end - 1];
+ $pos = $end;
+ $newlineFound = false;
+
+ // Output a newline after the token if required
+ // This is checked before AND after switching state
+ $newlineAdded = false;
+ if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineBefore[$state][$type] ) ) {
+ $out .= "\n";
+ $lineLength = 0;
+ $newlineAdded = true;
+ }
+
+ // Now that we have output our token, transition into the new state.
+ if( isset( $push[$state][$type] ) && count( $stack ) < self::STACK_LIMIT ) {
+ $stack[] = $push[$state][$type];
+ }
+ if( $stack && isset( $pop[$state][$type] ) ) {
+ $state = array_pop( $stack );
+ } elseif( isset( $goto[$state][$type] ) ) {
+ $state = $goto[$state][$type];
+ }
+
+ // Check for newline insertion again
+ if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineAfter[$state][$type] ) ) {
+ $out .= "\n";
+ $lineLength = 0;
+ }
+ }
+ return $out;
+ }
+}
diff --git a/includes/libs/README b/includes/libs/README
new file mode 100644
index 00000000..85e3db3c
--- /dev/null
+++ b/includes/libs/README
@@ -0,0 +1,4 @@
+The classes in this directory ./includes/libs are considered standalone
+from the remainder of the MediaWiki codebase. They do not call on any other
+portions of MediaWiki code, and can be used in other projects without
+dependency issues.
diff --git a/includes/api/ApiFormatYaml_spyc.php b/includes/libs/spyc.php
index 30f860dd..bc92e869 100644
--- a/includes/api/ApiFormatYaml_spyc.php
+++ b/includes/libs/spyc.php
@@ -1,6 +1,8 @@
<?php
/**
* Spyc -- A Simple PHP YAML Class
+ *
+ * @file
* @version 0.2.3 -- 2006-02-04
* @author Chris Wanstrath <chris@ozmm.org>
* @see http://spyc.sourceforge.net/
@@ -33,10 +35,10 @@ class Spyc {
* Indent's default is 2 spaces, wordwrap's default is 40 characters. And
* you can turn off wordwrap by passing in 0.
*
- * @return string
* @param $array Array: PHP array
* @param $indent Integer: Pass in false to use the default, which is 2
* @param $wordwrap Integer: Pass in 0 for no wordwrap, false for default (40)
+ * @return String
*/
public static function YAMLDump( $array, $indent = false, $wordwrap = false ) {
$spyc = new Spyc;
@@ -57,13 +59,12 @@ class Spyc {
* Indent's default is 2 spaces, wordwrap's default is 40 characters. And
* you can turn off wordwrap by passing in 0.
*
- * @public
- * @return string
* @param $array Array: PHP array
* @param $indent Integer: Pass in false to use the default, which is 2
* @param $wordwrap Integer: Pass in 0 for no wordwrap, false for default (40)
+ * @return String
*/
- function dump( $array, $indent = false, $wordwrap = false ) {
+ public function dump( $array, $indent = false, $wordwrap = false ) {
// Dumps to some very clean YAML. We'll have to add some more features
// and options soon. And better support for folding.
@@ -92,12 +93,16 @@ class Spyc {
/**** Private Properties ****/
+ /**
+ * Unused variables, but just commented rather than deleting
+ * to save altering the library
private $_haveRefs;
private $_allNodes;
private $_lastIndent;
private $_lastNode;
private $_inBlock;
private $_isInline;
+ **/
private $_dumpIndent;
private $_dumpWordWrap;
@@ -105,10 +110,11 @@ class Spyc {
/**
* Attempts to convert a key / value array item to YAML
- * @return string
- * @param $key The name of the key
- * @param $value The value of the item
- * @param $indent The indent of the current node
+ *
+ * @param $key Mixed: the name of the key
+ * @param $value Mixed: the value of the item
+ * @param $indent Integer: the indent of the current node
+ * @return String
*/
private function _yamlize( $key, $value, $indent ) {
if ( is_array( $value ) ) {
@@ -128,9 +134,10 @@ class Spyc {
/**
* Attempts to convert an array to YAML
- * @return string
- * @param $array The array you want to convert
- * @param $indent The indent of the current level
+ *
+ * @param $array Array: the array you want to convert
+ * @param $indent Integer: the indent of the current level
+ * @return String
*/
private function _yamlizeArray( $array, $indent ) {
if ( is_array( $array ) ) {
@@ -140,15 +147,16 @@ class Spyc {
}
return $string;
} else {
- return false;
+ return false;
}
}
/**
* Find out whether a string needs to be output as a literal rather than in plain style.
* Added by Roan Kattouw 13-03-2008
- * @param $value The string to check
- * @return bool
+ *
+ * @param $value String: the string to check
+ * @return Boolean
*/
function _needLiteral( $value ) {
// Check whether the string contains # or : or begins with any of:
@@ -163,17 +171,18 @@ class Spyc {
/**
* Returns YAML from a key and a value
- * @return string
- * @param $key The name of the key
- * @param $value The value of the item
- * @param $indent The indent of the current node
+ *
+ * @param $key Mixed: the name of the key
+ * @param $value Mixed: the value of the item
+ * @param $indent Integer: the indent of the current node
+ * @return String
*/
private function _dumpNode( $key, $value, $indent ) {
// do some folding here, for blocks
if ( $this->_needLiteral( $value ) ) {
$value = $this->_doLiteralBlock( $value, $indent );
} else {
- $value = $this->_doFolding( $value, $indent );
+ $value = $this->_doFolding( $value, $indent );
}
$spaces = str_repeat( ' ', $indent );
@@ -185,9 +194,9 @@ class Spyc {
else
$string = $spaces . "-\n";
} else {
- if ($key == '*') //bug 21922 - Quote asterix used as keys
+ if ( $key == '*' ) // bug 21922 - Quote asterix used as keys
$key = "'*'";
-
+
// It's mapped
if ( $value !== '' && !is_null( $value ) )
$string = $spaces . $key . ': ' . $value . "\n";
@@ -199,9 +208,10 @@ class Spyc {
/**
* Creates a literal block for dumping
- * @return string
- * @param $value
- * @param $indent int The value of the indent
+ *
+ * @param $value String
+ * @param $indent Integer: the value of the indent
+ * @return String
*/
private function _doLiteralBlock( $value, $indent ) {
$exploded = explode( "\n", $value );
@@ -216,8 +226,10 @@ class Spyc {
/**
* Folds a string of text, if necessary
- * @return string
- * @param $value The string you wish to fold
+ *
+ * @param $value String: the string you wish to fold
+ * @param $indent Integer: the indent of the current node
+ * @return String
*/
private function _doFolding( $value, $indent ) {
// Don't do anything if wordwrap is set to 0
diff --git a/includes/media/BMP.php b/includes/media/BMP.php
index 39b29744..de836b59 100644
--- a/includes/media/BMP.php
+++ b/includes/media/BMP.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Handler for Microsoft's bitmap format
+ *
* @file
* @ingroup Media
*/
@@ -17,7 +19,7 @@ class BmpHandler extends BitmapHandler {
}
// Render files as PNG
- function getThumbType( $text, $mime ) {
+ function getThumbType( $text, $mime, $params = null ) {
return array( 'png', 'image/png' );
}
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
index 870e2126..f5f7ba6d 100644
--- a/includes/media/Bitmap.php
+++ b/includes/media/Bitmap.php
@@ -1,10 +1,14 @@
<?php
/**
+ * Generic handler for bitmap images
+ *
* @file
* @ingroup Media
*/
/**
+ * Generic handler for bitmap images
+ *
* @ingroup Media
*/
class BitmapHandler extends ImageHandler {
@@ -18,15 +22,6 @@ class BitmapHandler extends ImageHandler {
$srcWidth = $image->getWidth( $params['page'] );
$srcHeight = $image->getHeight( $params['page'] );
- # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
- # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
- # an exception for it.
- if ( $mimeType !== 'image/jpeg' &&
- $this->getImageArea( $image, $srcWidth, $srcHeight ) > $wgMaxImageArea )
- {
- return false;
- }
-
# Don't make an image bigger than the source
$params['physicalWidth'] = $params['width'];
$params['physicalHeight'] = $params['height'];
@@ -34,13 +29,25 @@ class BitmapHandler extends ImageHandler {
if ( $params['physicalWidth'] >= $srcWidth ) {
$params['physicalWidth'] = $srcWidth;
$params['physicalHeight'] = $srcHeight;
- return true;
+ # Skip scaling limit checks if no scaling is required
+ if ( !$image->mustRender() )
+ return true;
+ }
+
+ # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
+ # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
+ # an exception for it.
+ # FIXME: This actually only applies to ImageMagick
+ if ( $mimeType !== 'image/jpeg' &&
+ $srcWidth * $srcHeight > $wgMaxImageArea )
+ {
+ return false;
}
return true;
}
-
-
+
+
// Function that returns the number of pixels to be thumbnailed.
// Intended for animated GIFs to multiply by the number of frames.
function getImageArea( $image, $width, $height ) {
@@ -48,36 +55,48 @@ class BitmapHandler extends ImageHandler {
}
function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
- global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgImageMagickTempDir;
+ global $wgUseImageMagick;
global $wgCustomConvertCommand, $wgUseImageResize;
- global $wgSharpenParameter, $wgSharpenReductionThreshold;
- global $wgMaxAnimatedGifArea;
if ( !$this->normaliseParams( $image, $params ) ) {
return new TransformParameterError( $params );
}
- $physicalWidth = $params['physicalWidth'];
- $physicalHeight = $params['physicalHeight'];
- $clientWidth = $params['width'];
- $clientHeight = $params['height'];
- $comment = isset( $params['descriptionUrl'] ) ? "File source: ". $params['descriptionUrl'] : '';
- $srcWidth = $image->getWidth();
- $srcHeight = $image->getHeight();
- $mimeType = $image->getMimeType();
- $srcPath = $image->getPath();
- $retval = 0;
- wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
+ # Create a parameter array to pass to the scaler
+ $scalerParams = array(
+ # The size to which the image will be resized
+ 'physicalWidth' => $params['physicalWidth'],
+ 'physicalHeight' => $params['physicalHeight'],
+ 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}",
+ # The size of the image on the page
+ 'clientWidth' => $params['width'],
+ 'clientHeight' => $params['height'],
+ # Comment as will be added to the EXIF of the thumbnail
+ 'comment' => isset( $params['descriptionUrl'] ) ?
+ "File source: {$params['descriptionUrl']}" : '',
+ # Properties of the original image
+ 'srcWidth' => $image->getWidth(),
+ 'srcHeight' => $image->getHeight(),
+ 'mimeType' => $image->getMimeType(),
+ 'srcPath' => $image->getPath(),
+ 'dstPath' => $dstPath,
+ );
+
+ wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath\n" );
+
+ if ( !$image->mustRender() &&
+ $scalerParams['physicalWidth'] == $scalerParams['srcWidth']
+ && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] ) {
- if ( !$image->mustRender() && $physicalWidth == $srcWidth && $physicalHeight == $srcHeight ) {
# normaliseParams (or the user) wants us to return the unscaled image
- wfDebug( __METHOD__.": returning unscaled image\n" );
- return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
+ wfDebug( __METHOD__ . ": returning unscaled image\n" );
+ return $this->getClientScalingThumbnailImage( $image, $scalerParams );
}
+ # Determine scaler type
if ( !$dstPath ) {
- // No output path available, client side scaling only
+ # No output path available, client side scaling only
$scaler = 'client';
- } elseif( !$wgUseImageResize ) {
+ } elseif ( !$wgUseImageResize ) {
$scaler = 'client';
} elseif ( $wgUseImageMagick ) {
$scaler = 'im';
@@ -88,166 +107,289 @@ class BitmapHandler extends ImageHandler {
} else {
$scaler = 'client';
}
- wfDebug( __METHOD__.": scaler $scaler\n" );
+ wfDebug( __METHOD__ . ": scaler $scaler\n" );
if ( $scaler == 'client' ) {
# Client-side image scaling, use the source URL
# Using the destination URL in a TRANSFORM_LATER request would be incorrect
- return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
+ return $this->getClientScalingThumbnailImage( $image, $scalerParams );
}
if ( $flags & self::TRANSFORM_LATER ) {
- wfDebug( __METHOD__.": Transforming later per flags.\n" );
- return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
+ wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
+ return new ThumbnailImage( $image, $dstUrl, $scalerParams['clientWidth'],
+ $scalerParams['clientHeight'], $dstPath );
}
+ # Try to make a target path for the thumbnail
if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
- wfDebug( __METHOD__.": Unable to create thumbnail destination directory, falling back to client scaling\n" );
- return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
- }
-
- if ( $scaler == 'im' ) {
- # use ImageMagick
-
- $quality = '';
- $sharpen = '';
- $scene = false;
- $animation = '';
- if ( $mimeType == 'image/jpeg' ) {
- $quality = "-quality 80"; // 80%
- # Sharpening, see bug 6193
- if ( ( $physicalWidth + $physicalHeight ) / ( $srcWidth + $srcHeight ) < $wgSharpenReductionThreshold ) {
- $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
- }
- } elseif ( $mimeType == 'image/png' ) {
- $quality = "-quality 95"; // zlib 9, adaptive filtering
- } elseif( $mimeType == 'image/gif' ) {
- if( $srcWidth * $srcHeight > $wgMaxAnimatedGifArea ) {
- // Extract initial frame only; we're so big it'll
- // be a total drag. :P
- $scene = 0;
- } else {
- // Coalesce is needed to scale animated GIFs properly (bug 1017).
- $animation = ' -coalesce ';
+ wfDebug( __METHOD__ . ": Unable to create thumbnail destination directory, falling back to client scaling\n" );
+ return $this->getClientScalingThumbnailImage( $image, $scalerParams );
+ }
+
+ switch ( $scaler ) {
+ case 'im':
+ $err = $this->transformImageMagick( $image, $scalerParams );
+ break;
+ case 'custom':
+ $err = $this->transformCustom( $image, $scalerParams );
+ break;
+ case 'gd':
+ default:
+ $err = $this->transformGd( $image, $scalerParams );
+ break;
+ }
+
+ # Remove the file if a zero-byte thumbnail was created, or if there was an error
+ $removed = $this->removeBadFile( $dstPath, (bool)$err );
+ if ( $err ) {
+ # transform returned MediaTransforError
+ return $err;
+ } elseif ( $removed ) {
+ # Thumbnail was zero-byte and had to be removed
+ return new MediaTransformError( 'thumbnail_error',
+ $scalerParams['clientWidth'], $scalerParams['clientHeight'] );
+ } else {
+ return new ThumbnailImage( $image, $dstUrl, $scalerParams['clientWidth'],
+ $scalerParams['clientHeight'], $dstPath );
+ }
+ }
+
+ /**
+ * Get a ThumbnailImage that respresents an image that will be scaled
+ * client side
+ *
+ * @param $image File File associated with this thumbnail
+ * @param $params array Array with scaler params
+ * @return ThumbnailImage
+ */
+ protected function getClientScalingThumbnailImage( $image, $params ) {
+ return new ThumbnailImage( $image, $image->getURL(),
+ $params['clientWidth'], $params['clientHeight'], $params['srcPath'] );
+ }
+
+ /**
+ * Transform an image using ImageMagick
+ *
+ * @param $image File File associated with this thumbnail
+ * @param $params array Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occured, false (=no error) otherwise
+ */
+ protected function transformImageMagick( $image, $params ) {
+ # use ImageMagick
+ global $wgSharpenReductionThreshold, $wgSharpenParameter,
+ $wgMaxAnimatedGifArea,
+ $wgImageMagickTempDir, $wgImageMagickConvertCommand;
+
+ $quality = '';
+ $sharpen = '';
+ $scene = false;
+ $animation_pre = '';
+ $animation_post = '';
+ $decoderHint = '';
+ if ( $params['mimeType'] == 'image/jpeg' ) {
+ $quality = "-quality 80"; // 80%
+ # Sharpening, see bug 6193
+ if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
+ / ( $params['srcWidth'] + $params['srcHeight'] )
+ < $wgSharpenReductionThreshold ) {
+ $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
+ }
+ // JPEG decoder hint to reduce memory, available since IM 6.5.6-2
+ $decoderHint = "-define jpeg:size={$params['physicalDimensions']}";
+
+ } elseif ( $params['mimeType'] == 'image/png' ) {
+ $quality = "-quality 95"; // zlib 9, adaptive filtering
+
+ } elseif ( $params['mimeType'] == 'image/gif' ) {
+ if ( $this->getImageArea( $image, $params['srcWidth'],
+ $params['srcHeight'] ) > $wgMaxAnimatedGifArea ) {
+ // Extract initial frame only; we're so big it'll
+ // be a total drag. :P
+ $scene = 0;
+
+ } elseif ( $this->isAnimatedImage( $image ) ) {
+ // Coalesce is needed to scale animated GIFs properly (bug 1017).
+ $animation_pre = '-coalesce';
+ // We optimize the output, but -optimize is broken,
+ // use optimizeTransparency instead (bug 11822)
+ if ( version_compare( $this->getMagickVersion(), "6.3.5" ) >= 0 ) {
+ $animation_post = '-fuzz 5% -layers optimizeTransparency +map';
}
}
+ }
- if ( strval( $wgImageMagickTempDir ) !== '' ) {
- $tempEnv = 'MAGICK_TMPDIR=' . wfEscapeShellArg( $wgImageMagickTempDir ) . ' ';
- } else {
- $tempEnv = '';
- }
+ // Use one thread only, to avoid deadlock bugs on OOM
+ $env = array( 'OMP_NUM_THREADS' => 1 );
+ if ( strval( $wgImageMagickTempDir ) !== '' ) {
+ $env['MAGICK_TMPDIR'] = $wgImageMagickTempDir;
+ }
- # Specify white background color, will be used for transparent images
- # in Internet Explorer/Windows instead of default black.
-
- # Note, we specify "-size {$physicalWidth}" and NOT "-size {$physicalWidth}x{$physicalHeight}".
- # It seems that ImageMagick has a bug wherein it produces thumbnails of
- # the wrong size in the second case.
-
- $cmd =
- $tempEnv .
- wfEscapeShellArg( $wgImageMagickConvertCommand ) .
- " {$quality} -background white -size {$physicalWidth} ".
- wfEscapeShellArg( $this->escapeMagickInput( $srcPath, $scene ) ) .
- $animation .
- // For the -resize option a "!" is needed to force exact size,
- // or ImageMagick may decide your ratio is wrong and slice off
- // a pixel.
- " -thumbnail " . wfEscapeShellArg( "{$physicalWidth}x{$physicalHeight}!" ) .
- // Add the source url as a comment to the thumb.
- " -set comment " . wfEscapeShellArg( $this->escapeMagickProperty( $comment ) ) .
- " -depth 8 $sharpen " .
- wfEscapeShellArg( $this->escapeMagickOutput( $dstPath ) ) . " 2>&1";
- wfDebug( __METHOD__.": running ImageMagick: $cmd\n" );
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
- } elseif( $scaler == 'custom' ) {
- # Use a custom convert command
- # Variables: %s %d %w %h
- $src = wfEscapeShellArg( $srcPath );
- $dst = wfEscapeShellArg( $dstPath );
- $cmd = $wgCustomConvertCommand;
- $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
- $cmd = str_replace( '%h', $physicalHeight, str_replace( '%w', $physicalWidth, $cmd ) ); # Size
- wfDebug( __METHOD__.": Running custom convert command $cmd\n" );
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
- } else /* $scaler == 'gd' */ {
- # Use PHP's builtin GD library functions.
- #
- # First find out what kind of file this is, and select the correct
- # input routine for this.
-
- $typemap = array(
- 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
- 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
- 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
- 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
- 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
- );
- if( !isset( $typemap[$mimeType] ) ) {
- $err = 'Image type not supported';
- wfDebug( "$err\n" );
- $errMsg = wfMsg ( 'thumbnail_image-type' );
- return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $errMsg );
- }
- list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType];
+ $cmd =
+ wfEscapeShellArg( $wgImageMagickConvertCommand ) .
+ // Specify white background color, will be used for transparent images
+ // in Internet Explorer/Windows instead of default black.
+ " {$quality} -background white" .
+ " {$decoderHint} " .
+ wfEscapeShellArg( $this->escapeMagickInput( $params['srcPath'], $scene ) ) .
+ " {$animation_pre}" .
+ // For the -thumbnail option a "!" is needed to force exact size,
+ // or ImageMagick may decide your ratio is wrong and slice off
+ // a pixel.
+ " -thumbnail " . wfEscapeShellArg( "{$params['physicalDimensions']}!" ) .
+ // Add the source url as a comment to the thumb, but don't add the flag if there's no comment
+ ( $params['comment'] !== ''
+ ? " -set comment " . wfEscapeShellArg( $this->escapeMagickProperty( $params['comment'] ) )
+ : '' ) .
+ " -depth 8 $sharpen" .
+ " {$animation_post} " .
+ wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ) . " 2>&1";
+
+ wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
+ wfProfileIn( 'convert' );
+ $retval = 0;
+ $err = wfShellExec( $cmd, $retval, $env );
+ wfProfileOut( 'convert' );
- if( !function_exists( $loader ) ) {
- $err = "Incomplete GD library configuration: missing function $loader";
- wfDebug( "$err\n" );
- $errMsg = wfMsg ( 'thumbnail_gd-library', $loader );
- return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $errMsg );
- }
+ if ( $retval !== 0 ) {
+ $this->logErrorForExternalProcess( $retval, $err, $cmd );
+ return $this->getMediaTransformError( $params, $err );
+ }
- if ( !file_exists( $srcPath ) ) {
- $err = "File seems to be missing: $srcPath";
- wfDebug( "$err\n" );
- $errMsg = wfMsg ( 'thumbnail_image-missing', $srcPath );
- return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $errMsg );
- }
+ return false; # No error
+ }
- $src_image = call_user_func( $loader, $srcPath );
- $dst_image = imagecreatetruecolor( $physicalWidth, $physicalHeight );
-
- // Initialise the destination image to transparent instead of
- // the default solid black, to support PNG and GIF transparency nicely
- $background = imagecolorallocate( $dst_image, 0, 0, 0 );
- imagecolortransparent( $dst_image, $background );
- imagealphablending( $dst_image, false );
-
- if( $colorStyle == 'palette' ) {
- // Don't resample for paletted GIF images.
- // It may just uglify them, and completely breaks transparency.
- imagecopyresized( $dst_image, $src_image,
- 0,0,0,0,
- $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
- } else {
- imagecopyresampled( $dst_image, $src_image,
- 0,0,0,0,
- $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
- }
+ /**
+ * Transform an image using a custom command
+ *
+ * @param $image File File associated with this thumbnail
+ * @param $params array Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occured, false (=no error) otherwise
+ */
+ protected function transformCustom( $image, $params ) {
+ # Use a custom convert command
+ global $wgCustomConvertCommand;
+
+ # Variables: %s %d %w %h
+ $src = wfEscapeShellArg( $params['srcPath'] );
+ $dst = wfEscapeShellArg( $params['dstPath'] );
+ $cmd = $wgCustomConvertCommand;
+ $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
+ $cmd = str_replace( '%h', $params['physicalHeight'],
+ str_replace( '%w', $params['physicalWidth'], $cmd ) ); # Size
+ wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" );
+ wfProfileIn( 'convert' );
+ $retval = 0;
+ $err = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'convert' );
- imagesavealpha( $dst_image, true );
+ if ( $retval !== 0 ) {
+ $this->logErrorForExternalProcess( $retval, $err, $cmd );
+ return $this->getMediaTransformError( $params, $err );
+ }
+ return false; # No error
+ }
- call_user_func( $saveType, $dst_image, $dstPath );
- imagedestroy( $dst_image );
- imagedestroy( $src_image );
- $retval = 0;
+ /**
+ * Log an error that occured in an external process
+ *
+ * @param $retval int
+ * @param $err int
+ * @param $cmd string
+ */
+ protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
+ wfDebugLog( 'thumbnail',
+ sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
+ wfHostname(), $retval, trim( $err ), $cmd ) );
+ }
+ /**
+ * Get a MediaTransformError with error 'thumbnail_error'
+ *
+ * @param $params array Parameter array as passed to the transform* functions
+ * @param $errMsg string Error message
+ * @return MediaTransformError
+ */
+ protected function getMediaTransformError( $params, $errMsg ) {
+ return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
+ $params['clientHeight'], $errMsg );
+ }
+
+ /**
+ * Transform an image using the built in GD library
+ *
+ * @param $image File File associated with this thumbnail
+ * @param $params array Array with scaler params
+ *
+ * @return MediaTransformError Error object if error occured, false (=no error) otherwise
+ */
+ protected function transformGd( $image, $params ) {
+ # Use PHP's builtin GD library functions.
+ #
+ # First find out what kind of file this is, and select the correct
+ # input routine for this.
+
+ $typemap = array(
+ 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
+ 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
+ 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
+ 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
+ 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
+ );
+ if ( !isset( $typemap[$params['mimeType']] ) ) {
+ $err = 'Image type not supported';
+ wfDebug( "$err\n" );
+ $errMsg = wfMsg ( 'thumbnail_image-type' );
+ return $this->getMediaTransformError( $params, $errMsg );
+ }
+ list( $loader, $colorStyle, $saveType ) = $typemap[$params['mimeType']];
+
+ if ( !function_exists( $loader ) ) {
+ $err = "Incomplete GD library configuration: missing function $loader";
+ wfDebug( "$err\n" );
+ $errMsg = wfMsg ( 'thumbnail_gd-library', $loader );
+ return $this->getMediaTransformError( $params, $errMsg );
+ }
+
+ if ( !file_exists( $params['srcPath'] ) ) {
+ $err = "File seems to be missing: {$params['srcPath']}";
+ wfDebug( "$err\n" );
+ $errMsg = wfMsg ( 'thumbnail_image-missing', $params['srcPath'] );
+ return $this->getMediaTransformError( $params, $errMsg );
}
- $removed = $this->removeBadFile( $dstPath, $retval );
- if ( $retval != 0 || $removed ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim($err), $cmd ) );
- return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+ $src_image = call_user_func( $loader, $params['srcPath'] );
+ $dst_image = imagecreatetruecolor( $params['physicalWidth'],
+ $params['physicalHeight'] );
+
+ // Initialise the destination image to transparent instead of
+ // the default solid black, to support PNG and GIF transparency nicely
+ $background = imagecolorallocate( $dst_image, 0, 0, 0 );
+ imagecolortransparent( $dst_image, $background );
+ imagealphablending( $dst_image, false );
+
+ if ( $colorStyle == 'palette' ) {
+ // Don't resample for paletted GIF images.
+ // It may just uglify them, and completely breaks transparency.
+ imagecopyresized( $dst_image, $src_image,
+ 0, 0, 0, 0,
+ $params['physicalWidth'], $params['physicalHeight'],
+ imagesx( $src_image ), imagesy( $src_image ) );
} else {
- return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
+ imagecopyresampled( $dst_image, $src_image,
+ 0, 0, 0, 0,
+ $params['physicalWidth'], $params['physicalHeight'],
+ imagesx( $src_image ), imagesy( $src_image ) );
}
+
+ imagesavealpha( $dst_image, true );
+
+ call_user_func( $saveType, $dst_image, $params['dstPath'] );
+ imagedestroy( $dst_image );
+ imagedestroy( $src_image );
+
+ return false; # No error
}
/**
@@ -267,14 +409,14 @@ class BitmapHandler extends ImageHandler {
}
/**
- * Escape a string for ImageMagick's input filenames. See ExpandFilenames()
+ * Escape a string for ImageMagick's input filenames. See ExpandFilenames()
* and GetPathComponent() in magick/utility.c.
*
* This won't work with an initial ~ or @, so input files should be prefixed
- * with the directory name.
+ * with the directory name.
*
* Glob character unescaping is broken in ImageMagick before 6.6.1-5, but
- * it's broken in a way that doesn't involve trying to convert every file
+ * it's broken in a way that doesn't involve trying to convert every file
* in a directory, so we're better off escaping and waiting for the bugfix
* to filter down to users.
*
@@ -285,7 +427,7 @@ class BitmapHandler extends ImageHandler {
# Die on initial metacharacters (caller should prepend path)
$firstChar = substr( $path, 0, 1 );
if ( $firstChar === '~' || $firstChar === '@' ) {
- throw new MWException( __METHOD__.': cannot escape this path name' );
+ throw new MWException( __METHOD__ . ': cannot escape this path name' );
}
# Escape glob chars
@@ -295,7 +437,7 @@ class BitmapHandler extends ImageHandler {
}
/**
- * Escape a string for ImageMagick's output filename. See
+ * Escape a string for ImageMagick's output filename. See
* InterpretImageFilename() in magick/image.c.
*/
function escapeMagickOutput( $path, $scene = false ) {
@@ -304,7 +446,7 @@ class BitmapHandler extends ImageHandler {
}
/**
- * Armour a string against ImageMagick's GetPathComponent(). This is a
+ * Armour a string against ImageMagick's GetPathComponent(). This is a
* helper function for escapeMagickInput() and escapeMagickOutput().
*
* @param $path string The file path
@@ -318,11 +460,11 @@ class BitmapHandler extends ImageHandler {
// OK, it's a drive letter
// ImageMagick has a similar exception, see IsMagickConflict()
} else {
- throw new MWException( __METHOD__.': unexpected colon character in path name' );
+ throw new MWException( __METHOD__ . ': unexpected colon character in path name' );
}
}
- # If there are square brackets, add a do-nothing scene specification
+ # If there are square brackets, add a do-nothing scene specification
# to force a literal interpretation
if ( $scene === false ) {
if ( strpos( $path, '[' ) !== false ) {
@@ -334,6 +476,33 @@ class BitmapHandler extends ImageHandler {
return $path;
}
+ /**
+ * Retrieve the version of the installed ImageMagick
+ * You can use PHPs version_compare() to use this value
+ * Value is cached for one hour.
+ * @return String representing the IM version.
+ */
+ protected function getMagickVersion() {
+ global $wgMemc;
+
+ $cache = $wgMemc->get( "imagemagick-version" );
+ if ( !$cache ) {
+ global $wgImageMagickConvertCommand;
+ $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . ' -version';
+ wfDebug( __METHOD__ . ": Running convert -version\n" );
+ $retval = '';
+ $return = wfShellExec( $cmd, $retval );
+ $x = preg_match( '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches );
+ if ( $x != 1 ) {
+ wfDebug( __METHOD__ . ": ImageMagick version check failed\n" );
+ return null;
+ }
+ $wgMemc->set( "imagemagick-version", $matches[1], 3600 );
+ return $matches[1];
+ }
+ return $cache;
+ }
+
static function imageJpegWrapper( $dst_image, $thumbPath ) {
imageinterlace( $dst_image );
imagejpeg( $dst_image, $thumbPath, 95 );
@@ -342,7 +511,7 @@ class BitmapHandler extends ImageHandler {
function getMetadata( $image, $filename ) {
global $wgShowEXIF;
- if( $wgShowEXIF && file_exists( $filename ) ) {
+ if ( $wgShowEXIF && file_exists( $filename ) ) {
$exif = new Exif( $filename );
$data = $exif->getFilteredData();
if ( $data ) {
@@ -370,12 +539,14 @@ class BitmapHandler extends ImageHandler {
# Special value indicating that there is no EXIF data in the file
return true;
}
- $exif = @unserialize( $metadata );
+ wfSuppressWarnings();
+ $exif = unserialize( $metadata );
+ wfRestoreWarnings();
if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
$exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
{
# Wrong version
- wfDebug( __METHOD__.": wrong version\n" );
+ wfDebug( __METHOD__ . ": wrong version\n" );
return false;
}
return true;
@@ -391,9 +562,9 @@ class BitmapHandler extends ImageHandler {
function visibleMetadataFields() {
$fields = array();
$lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) );
- foreach( $lines as $line ) {
+ foreach ( $lines as $line ) {
$matches = array();
- if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
+ if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
$fields[] = $matches[1];
}
}
diff --git a/includes/media/Bitmap_ClientOnly.php b/includes/media/Bitmap_ClientOnly.php
index 9801f9be..9f6f7b33 100644
--- a/includes/media/Bitmap_ClientOnly.php
+++ b/includes/media/Bitmap_ClientOnly.php
@@ -1,5 +1,19 @@
<?php
+/**
+ * Handler for bitmap images that will be resized by clients
+ *
+ * @file
+ * @ingroup Media
+ */
+/**
+ * Handler for bitmap images that will be resized by clients.
+ *
+ * This is not used by default but can be assigned to some image types
+ * using $wgMediaHandlers.
+ *
+ * @ingroup Media
+ */
class BitmapHandler_ClientOnly extends BitmapHandler {
function normaliseParams( $image, &$params ) {
return ImageHandler::normaliseParams( $image, $params );
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
index 38c16c21..cc3f1db5 100644
--- a/includes/media/DjVu.php
+++ b/includes/media/DjVu.php
@@ -1,10 +1,14 @@
<?php
/**
+ * Handler for DjVu images
+ *
* @file
* @ingroup Media
*/
/**
+ * Handler for DjVu images
+ *
* @ingroup Media
*/
class DjVuHandler extends ImageHandler {
@@ -104,6 +108,7 @@ class DjVuHandler extends ImageHandler {
$cmd .= ' > ' . wfEscapeShellArg($dstPath) . ') 2>&1';
wfProfileIn( 'ddjvu' );
wfDebug( __METHOD__.": $cmd\n" );
+ $retval = '';
$err = wfShellExec( $cmd, $retval );
wfProfileOut( 'ddjvu' );
@@ -181,7 +186,7 @@ class DjVuHandler extends ImageHandler {
return $this->getDjVuImage( $image, $path )->getImageSize();
}
- function getThumbType( $ext, $mime ) {
+ function getThumbType( $ext, $mime, $params = null ) {
global $wgDjvuOutputExtension;
static $mime;
if ( !isset( $mime ) ) {
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
index dbe5f813..c4ede331 100644
--- a/includes/media/GIF.php
+++ b/includes/media/GIF.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Handler for GIF images.
+ *
* @file
* @ingroup Media
*/
@@ -12,7 +14,7 @@
class GIFHandler extends BitmapHandler {
function getMetadata( $image, $filename ) {
- if ( !isset($image->parsedGIFMetadata) ) {
+ if ( !isset( $image->parsedGIFMetadata ) ) {
try {
$image->parsedGIFMetadata = GIFMetadataExtractor::getMetadata( $filename );
} catch( Exception $e ) {
@@ -22,7 +24,7 @@ class GIFHandler extends BitmapHandler {
}
}
- return serialize($image->parsedGIFMetadata);
+ return serialize( $image->parsedGIFMetadata );
}
@@ -39,22 +41,41 @@ class GIFHandler extends BitmapHandler {
return $width * $height;
}
}
+
+ function isAnimatedImage( $image ) {
+ $ser = $image->getMetadata();
+ if ($ser) {
+ $metadata = unserialize($ser);
+ if( $metadata['frameCount'] > 1 ) return true;
+ }
+ return false;
+ }
function getMetadataType( $image ) {
return 'parsed-gif';
}
+ function isMetadataValid( $image, $metadata ) {
+ wfSuppressWarnings();
+ $data = unserialize( $metadata );
+ wfRestoreWarnings();
+ return (boolean) $data;
+ }
+
function getLongDesc( $image ) {
- global $wgUser, $wgLang;
- $sk = $wgUser->getSkin();
-
- $metadata = @unserialize($image->getMetadata());
+ global $wgLang;
+
+ $original = parent::getLongDesc( $image );
+
+ wfSuppressWarnings();
+ $metadata = unserialize($image->getMetadata());
+ wfRestoreWarnings();
- if (!$metadata) return parent::getLongDesc( $image );
+ if (!$metadata || $metadata['frameCount'] <= 1)
+ return $original;
$info = array();
- $info[] = $image->getMimeType();
- $info[] = $sk->formatSize( $image->getSize() );
+ $info[] = $original;
if ($metadata['looped'])
$info[] = wfMsgExt( 'file-info-gif-looped', 'parseinline' );
@@ -65,8 +86,6 @@ class GIFHandler extends BitmapHandler {
if ($metadata['duration'])
$info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
- $infoString = $wgLang->commaList( $info );
-
- return "($infoString)";
+ return $wgLang->commaList( $info );
}
}
diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php
index fac9012b..bc1a4804 100644
--- a/includes/media/GIFMetadataExtractor.php
+++ b/includes/media/GIFMetadataExtractor.php
@@ -1,12 +1,21 @@
<?php
/**
- * GIF frame counter.
- * Originally written in Perl by Steve Sanbeg.
- * Ported to PHP by Andrew Garrett
- * Deliberately not using MWExceptions to avoid external dependencies, encouraging
- * redistribution.
- */
+ * GIF frame counter.
+ *
+ * Originally written in Perl by Steve Sanbeg.
+ * Ported to PHP by Andrew Garrett
+ * Deliberately not using MWExceptions to avoid external dependencies, encouraging
+ * redistribution.
+ *
+ * @file
+ * @ingroup Media
+ */
+/**
+ * GIF frame counter.
+ *
+ * @ingroup Media
+ */
class GIFMetadataExtractor {
static $gif_frame_sep;
static $gif_extension_sep;
diff --git a/includes/media/Generic.php b/includes/media/Generic.php
index 8a4d7054..fa4e731a 100644
--- a/includes/media/Generic.php
+++ b/includes/media/Generic.php
@@ -1,6 +1,7 @@
<?php
/**
* Media-handling base classes and generic functionality
+ *
* @file
* @ingroup Media
*/
@@ -72,7 +73,7 @@ abstract class MediaHandler {
* can't be determined.
*
* @param $image File: the image object, or false if there isn't one
- * @param $fileName String: the filename
+ * @param $path String: the filename
* @return Array
*/
abstract function getImageSize( $image, $path );
@@ -80,7 +81,8 @@ abstract class MediaHandler {
/**
* Get handler-specific metadata which will be saved in the img_metadata field.
*
- * @param $image File: the image object, or false if there isn't one
+ * @param $image File: the image object, or false if there isn't one.
+ * Warning, File::getPropsFromPath might pass an (object)array() instead (!)
* @param $path String: the filename
* @return String
*/
@@ -139,7 +141,7 @@ abstract class MediaHandler {
* Get the thumbnail extension and MIME type for a given source MIME type
* @return array thumbnail extension and MIME type
*/
- function getThumbType( $ext, $mime ) {
+ function getThumbType( $ext, $mime, $params = null ) {
return array( $ext, $mime );
}
@@ -161,6 +163,10 @@ abstract class MediaHandler {
*/
function pageCount( $file ) { return false; }
/**
+ * The material is vectorized and thus scaling is lossless
+ */
+ function isVectorized( $file ) { return false; }
+ /**
* False if the handler is disabled for all files
*/
function isEnabled() { return true; }
@@ -235,8 +241,8 @@ abstract class MediaHandler {
function getShortDesc( $file ) {
global $wgLang;
- $nbytes = '(' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $file->getSize() ) ) . ')';
+ $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
+ $wgLang->formatNum( $file->getSize() ) );
return "$nbytes";
}
@@ -250,8 +256,8 @@ abstract class MediaHandler {
static function getGeneralShortDesc( $file ) {
global $wgLang;
- $nbytes = '(' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $file->getSize() ) ) . ')';
+ $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
+ $wgLang->formatNum( $file->getSize() ) );
return "$nbytes";
}
@@ -273,6 +279,20 @@ abstract class MediaHandler {
function parserTransformHook( $parser, $file ) {}
/**
+ * File validation hook called on upload.
+ *
+ * If the file at the given local path is not valid, or its MIME type does not
+ * match the handler class, a Status object should be returned containing
+ * relevant errors.
+ *
+ * @param $fileName The local path to the file.
+ * @return Status object
+ */
+ function verifyUpload( $fileName ) {
+ return Status::newGood();
+ }
+
+ /**
* Check for zero-sized thumbnails. These can be generated when
* no disk space is available or some other error occurs
*
@@ -357,9 +377,19 @@ abstract class ImageHandler extends MediaHandler {
if ( !isset( $params['width'] ) ) {
return false;
}
+
if ( !isset( $params['page'] ) ) {
$params['page'] = 1;
+ } else {
+ if ( $params['page'] > $image->pageCount() ) {
+ $params['page'] = $image->pageCount();
+ }
+
+ if ( $params['page'] < 1 ) {
+ $params['page'] = 1;
+ }
}
+
$srcWidth = $image->getWidth( $params['page'] );
$srcHeight = $image->getHeight( $params['page'] );
if ( isset( $params['height'] ) && $params['height'] != -1 ) {
@@ -386,6 +416,9 @@ abstract class ImageHandler extends MediaHandler {
*
* @param $width Integer: specified width (input/output)
* @param $height Integer: height (output only)
+ * @param $srcWidth Integer: width of the source image
+ * @param $srcHeight Integer: height of the source image
+ * @param $mimeType Unused
* @return false to indicate that an error should be returned to the user.
*/
function validateThumbParams( &$width, &$height, $srcWidth, $srcHeight, $mimeType ) {
@@ -424,6 +457,10 @@ abstract class ImageHandler extends MediaHandler {
return $gis;
}
+ function isAnimatedImage( $image ) {
+ return false;
+ }
+
function getShortDesc( $file ) {
global $wgLang;
$nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
diff --git a/includes/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php
index a3fcc96e..c441f06c 100644
--- a/includes/MediaTransformOutput.php
+++ b/includes/media/MediaTransformOutput.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Base class for the output of file transformation methods.
+ *
* @file
* @ingroup Media
*/
@@ -34,7 +36,7 @@ abstract class MediaTransformOutput {
}
/**
- * @return string Destination file path (local filesystem)
+ * @return String: destination file path (local filesystem)
*/
function getPath() {
return $this->path;
@@ -43,7 +45,7 @@ abstract class MediaTransformOutput {
/**
* Fetch HTML for this transform output
*
- * @param array $options Associative array of options. Boolean options
+ * @param $options Associative array of options. Boolean options
* should be indicated with a value of true for true, and false or
* absent for false.
*
@@ -103,12 +105,17 @@ abstract class MediaTransformOutput {
* @ingroup Media
*/
class ThumbnailImage extends MediaTransformOutput {
+
/**
- * @param string $path Filesystem path to the thumb
- * @param string $url URL path to the thumb
+ * @param $file File object
+ * @param $url String: URL path to the thumb
+ * @param $width Integer: file's width
+ * @param $height Integer: file's height
+ * @param $path String: filesystem path to the thumb
+ * @param $page Integer: page number, for multipage files
* @private
*/
- function ThumbnailImage( $file, $url, $width, $height, $path = false, $page = false ) {
+ function __construct( $file, $url, $width, $height, $path = false, $page = false ) {
$this->file = $file;
$this->url = $url;
# These should be integers when they get here.
@@ -124,7 +131,7 @@ class ThumbnailImage extends MediaTransformOutput {
* Return HTML <img ... /> tag for the thumbnail, will include
* width and height attributes and a blank alt text (as required).
*
- * @param array $options Associative array of options. Boolean options
+ * @param $options Associative array of options. Boolean options
* should be indicated with a value of true for true, and false or
* absent for false.
*
@@ -133,16 +140,16 @@ class ThumbnailImage extends MediaTransformOutput {
* desc-link Boolean, show a description link
* file-link Boolean, show a file download link
* valign vertical-align property, if the output is an inline element
- * img-class Class applied to the <img> tag, if there is such a tag
+ * img-class Class applied to the \<img\> tag, if there is such a tag
* desc-query String, description link query params
* custom-url-link Custom URL to link to
* custom-title-link Custom Title object to link to
+ * custom target-link Value of the target attribute, for custom-target-link
*
* For images, desc-link and file-link are implemented as a click-through. For
* sounds and videos, they may be displayed in other ways.
*
* @return string
- * @public
*/
function toHtml( $options = array() ) {
if ( count( func_get_args() ) == 2 ) {
@@ -158,6 +165,9 @@ class ThumbnailImage extends MediaTransformOutput {
if ( !empty( $options['title'] ) ) {
$linkAttribs['title'] = $options['title'];
}
+ if ( !empty( $options['custom-target-link'] ) ) {
+ $linkAttribs['target'] = $options['custom-target-link'];
+ }
} elseif ( !empty( $options['custom-title-link'] ) ) {
$title = $options['custom-title-link'];
$linkAttribs = array(
@@ -211,10 +221,10 @@ class MediaTransformError extends MediaTransformOutput {
}
function toHtml( $options = array() ) {
- return "<table class=\"MediaTransformError\" style=\"" .
- "width: {$this->width}px; height: {$this->height}px;\"><tr><td>" .
+ return "<div class=\"MediaTransformError\" style=\"" .
+ "width: {$this->width}px; height: {$this->height}px; display:inline-block;\">" .
$this->htmlMsg .
- "</td></tr></table>";
+ "</div>";
}
function toText() {
@@ -238,8 +248,8 @@ class MediaTransformError extends MediaTransformOutput {
class TransformParameterError extends MediaTransformError {
function __construct( $params ) {
parent::__construct( 'thumbnail_error',
- max( isset( $params['width'] ) ? $params['width'] : 0, 180 ),
- max( isset( $params['height'] ) ? $params['height'] : 0, 180 ),
+ max( isset( $params['width'] ) ? $params['width'] : 0, 120 ),
+ max( isset( $params['height'] ) ? $params['height'] : 0, 120 ),
wfMsg( 'thumbnail_invalid_params' ) );
}
}
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
new file mode 100644
index 00000000..5197282c
--- /dev/null
+++ b/includes/media/PNG.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Handler for PNG images.
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for PNG images.
+ *
+ * @ingroup Media
+ */
+class PNGHandler extends BitmapHandler {
+
+ function getMetadata( $image, $filename ) {
+ if ( !isset($image->parsedPNGMetadata) ) {
+ try {
+ $image->parsedPNGMetadata = PNGMetadataExtractor::getMetadata( $filename );
+ } catch( Exception $e ) {
+ // Broken file?
+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+ return '0';
+ }
+ }
+
+ return serialize($image->parsedPNGMetadata);
+
+ }
+
+ function formatMetadata( $image ) {
+ return false;
+ }
+
+ function isAnimatedImage( $image ) {
+ $ser = $image->getMetadata();
+ if ($ser) {
+ $metadata = unserialize($ser);
+ if( $metadata['frameCount'] > 1 ) return true;
+ }
+ return false;
+ }
+
+ function getMetadataType( $image ) {
+ return 'parsed-png';
+ }
+
+ function isMetadataValid( $image, $metadata ) {
+ wfSuppressWarnings();
+ $data = unserialize( $metadata );
+ wfRestoreWarnings();
+ return (boolean) $data;
+ }
+ function getLongDesc( $image ) {
+ global $wgLang;
+ $original = parent::getLongDesc( $image );
+
+ wfSuppressWarnings();
+ $metadata = unserialize($image->getMetadata());
+ wfRestoreWarnings();
+
+ if( !$metadata || $metadata['frameCount'] <= 0 )
+ return $original;
+
+ $info = array();
+ $info[] = $original;
+
+ if ($metadata['loopCount'] == 0)
+ $info[] = wfMsgExt( 'file-info-png-looped', 'parseinline' );
+ elseif ($metadata['loopCount'] > 1)
+ $info[] = wfMsgExt( 'file-info-png-repeat', 'parseinline', $metadata['loopCount'] );
+
+ if ($metadata['frameCount'] > 0)
+ $info[] = wfMsgExt( 'file-info-png-frames', 'parseinline', $metadata['frameCount'] );
+
+ if ($metadata['duration'])
+ $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
+
+ return $wgLang->commaList( $info );
+ }
+
+}
diff --git a/includes/media/PNGMetadataExtractor.php b/includes/media/PNGMetadataExtractor.php
new file mode 100644
index 00000000..6a931e6c
--- /dev/null
+++ b/includes/media/PNGMetadataExtractor.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * PNG frame counter.
+ * Slightly derived from GIFMetadataExtractor.php
+ * Deliberately not using MWExceptions to avoid external dependencies, encouraging
+ * redistribution.
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * PNG frame counter.
+ *
+ * @ingroup Media
+ */
+class PNGMetadataExtractor {
+ static $png_sig;
+ static $CRC_size;
+
+ static function getMetadata( $filename ) {
+ self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
+ self::$CRC_size = 4;
+
+ $frameCount = 0;
+ $loopCount = 1;
+ $duration = 0.0;
+
+ if (!$filename)
+ throw new Exception( __METHOD__ . ": No file name specified" );
+ elseif ( !file_exists($filename) || is_dir($filename) )
+ throw new Exception( __METHOD__ . ": File $filename does not exist" );
+
+ $fh = fopen( $filename, 'r' );
+
+ if (!$fh) {
+ throw new Exception( __METHOD__ . ": Unable to open file $filename" );
+ }
+
+ // Check for the PNG header
+ $buf = fread( $fh, 8 );
+ if ( $buf != self::$png_sig ) {
+ throw new Exception( __METHOD__ . ": Not a valid PNG file; header: $buf" );
+ }
+
+ // Read chunks
+ while( !feof( $fh ) ) {
+ $buf = fread( $fh, 4 );
+ if( !$buf ) {
+ throw new Exception( __METHOD__ . ": Read error" );
+ }
+ $chunk_size = unpack( "N", $buf);
+ $chunk_size = $chunk_size[1];
+
+ $chunk_type = fread( $fh, 4 );
+ if( !$chunk_type ) {
+ throw new Exception( __METHOD__ . ": Read error" );
+ }
+
+ if ( $chunk_type == "acTL" ) {
+ $buf = fread( $fh, $chunk_size );
+ if( !$buf ) {
+ throw new Exception( __METHOD__ . ": Read error" );
+ }
+
+ $actl = unpack( "Nframes/Nplays", $buf );
+ $frameCount = $actl['frames'];
+ $loopCount = $actl['plays'];
+ } elseif ( $chunk_type == "fcTL" ) {
+ $buf = fread( $fh, $chunk_size );
+ if( !$buf ) {
+ throw new Exception( __METHOD__ . ": Read error" );
+ }
+ $buf = substr( $buf, 20 );
+
+ $fctldur = unpack( "ndelay_num/ndelay_den", $buf );
+ if( $fctldur['delay_den'] == 0 ) $fctldur['delay_den'] = 100;
+ if( $fctldur['delay_num'] ) {
+ $duration += $fctldur['delay_num'] / $fctldur['delay_den'];
+ }
+ } elseif ( ( $chunk_type == "IDAT" || $chunk_type == "IEND" ) && $frameCount == 0 ) {
+ // Not a valid animated image. No point in continuing.
+ break;
+ } elseif ( $chunk_type == "IEND" ) {
+ break;
+ } else {
+ fseek( $fh, $chunk_size, SEEK_CUR );
+ }
+ fseek( $fh, self::$CRC_size, SEEK_CUR );
+ }
+ fclose( $fh );
+
+ if( $loopCount > 1 ) {
+ $duration *= $loopCount;
+ }
+
+ return array(
+ 'frameCount' => $frameCount,
+ 'loopCount' => $loopCount,
+ 'duration' => $duration
+ );
+
+ }
+}
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
index 4cc66fb1..9a8484f1 100644
--- a/includes/media/SVG.php
+++ b/includes/media/SVG.php
@@ -1,13 +1,19 @@
<?php
/**
+ * Handler for SVG images.
+ *
* @file
* @ingroup Media
*/
/**
+ * Handler for SVG images.
+ *
* @ingroup Media
*/
class SvgHandler extends ImageHandler {
+ const SVG_METADATA_VERSION = 2;
+
function isEnabled() {
global $wgSVGConverters, $wgSVGConverter;
if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
@@ -22,6 +28,22 @@ class SvgHandler extends ImageHandler {
return true;
}
+ function isVectorized( $file ) {
+ return true;
+ }
+
+ function isAnimatedImage( $file ) {
+ # TODO: detect animated SVGs
+ $metadata = $file->getMetadata();
+ if ( $metadata ) {
+ $metadata = $this->unpackMetadata( $metadata );
+ if( isset( $metadata['animated'] ) ) {
+ return $metadata['animated'];
+ }
+ }
+ return false;
+ }
+
function normaliseParams( $image, &$params ) {
global $wgSVGMaxSize;
if ( !parent::normaliseParams( $image, $params ) ) {
@@ -57,7 +79,7 @@ class SvgHandler extends ImageHandler {
return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
wfMsg( 'thumbnail_dest_directory' ) );
}
-
+
$status = $this->rasterize( $srcPath, $dstPath, $physicalWidth, $physicalHeight );
if( $status === true ) {
return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
@@ -65,7 +87,7 @@ class SvgHandler extends ImageHandler {
return $status; // MediaTransformError
}
}
-
+
/*
* Transform an SVG file to PNG
* This function can be called outside of thumbnail contexts
@@ -78,6 +100,7 @@ class SvgHandler extends ImageHandler {
public function rasterize( $srcPath, $dstPath, $width, $height ) {
global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
$err = false;
+ $retval = '';
if ( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
$cmd = str_replace(
array( '$path/', '$width', '$height', '$input', '$output' ),
@@ -102,11 +125,19 @@ class SvgHandler extends ImageHandler {
return true;
}
- function getImageSize( $image, $path ) {
- return wfGetSVGsize( $path );
+ function getImageSize( $file, $path, $metadata = false ) {
+ if ( $metadata === false ) {
+ $metadata = $file->getMetaData();
+ }
+ $metadata = $this->unpackMetaData( $metadata );
+
+ if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
+ return array( $metadata['width'], $metadata['height'], 'SVG',
+ "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" );
+ }
}
- function getThumbType( $ext, $mime ) {
+ function getThumbType( $ext, $mime, $params = null ) {
return array( 'png', 'image/png' );
}
@@ -117,4 +148,84 @@ class SvgHandler extends ImageHandler {
$wgLang->formatNum( $file->getHeight() ),
$wgLang->formatSize( $file->getSize() ) );
}
+
+ function getMetadata( $file, $filename ) {
+ try {
+ $metadata = SVGMetadataExtractor::getMetadata( $filename );
+ } catch( Exception $e ) {
+ // Broken file?
+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+ return '0';
+ }
+ $metadata['version'] = self::SVG_METADATA_VERSION;
+ return serialize( $metadata );
+ }
+
+ function unpackMetadata( $metadata ) {
+ $unser = @unserialize( $metadata );
+ if ( isset( $unser['version'] ) && $unser['version'] == self::SVG_METADATA_VERSION ) {
+ return $unser;
+ } else {
+ return false;
+ }
+ }
+
+ function getMetadataType( $image ) {
+ return 'parsed-svg';
+ }
+
+ function isMetadataValid( $image, $metadata ) {
+ return $this->unpackMetadata( $metadata ) !== false;
+ }
+
+ function visibleMetadataFields() {
+ $fields = array( 'title', 'description', 'animated' );
+ return $fields;
+ }
+
+ function formatMetadata( $file ) {
+ $result = array(
+ 'visible' => array(),
+ 'collapsed' => array()
+ );
+ $metadata = $file->getMetadata();
+ if ( !$metadata ) {
+ return false;
+ }
+ $metadata = $this->unpackMetadata( $metadata );
+ if ( !$metadata ) {
+ return false;
+ }
+ unset( $metadata['version'] );
+ unset( $metadata['metadata'] ); /* non-formatted XML */
+
+ /* TODO: add a formatter
+ $format = new FormatSVG( $metadata );
+ $formatted = $format->getFormattedData();
+ */
+
+ // Sort fields into visible and collapsed
+ $visibleFields = $this->visibleMetadataFields();
+
+ // Rename fields to be compatible with exif, so that
+ // the labels for these fields work.
+ $conversion = array( 'width' => 'imagewidth',
+ 'height' => 'imagelength',
+ 'description' => 'imagedescription',
+ 'title' => 'objectname',
+ );
+ foreach ( $metadata as $name => $value ) {
+ $tag = strtolower( $name );
+ if ( isset( $conversion[$tag] ) ) {
+ $tag = $conversion[$tag];
+ }
+ self::addMeta( $result,
+ in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
+ 'exif',
+ $tag,
+ $value
+ );
+ }
+ return $result;
+ }
}
diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php
new file mode 100644
index 00000000..66ae1edf
--- /dev/null
+++ b/includes/media/SVGMetadataExtractor.php
@@ -0,0 +1,313 @@
+<?php
+/**
+ * SVGMetadataExtractor.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 Media
+ * @author Derk-Jan Hartman <hartman _at_ videolan d0t org>
+ * @author Brion Vibber
+ * @copyright Copyright © 2010-2010 Brion Vibber, Derk-Jan Hartman
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ */
+
+class SVGMetadataExtractor {
+ static function getMetadata( $filename ) {
+ $svg = new SVGReader( $filename );
+ return $svg->getMetadata();
+ }
+}
+
+class SVGReader {
+ const DEFAULT_WIDTH = 512;
+ const DEFAULT_HEIGHT = 512;
+ const NS_SVG = 'http://www.w3.org/2000/svg';
+
+ private $reader = null;
+ private $mDebug = false;
+ private $metadata = Array();
+
+ /**
+ * Constructor
+ *
+ * Creates an SVGReader drawing from the source provided
+ * @param $source String: URI from which to read
+ */
+ function __construct( $source ) {
+ global $wgSVGMetadataCutoff;
+ $this->reader = new XMLReader();
+
+ // Don't use $file->getSize() since file object passed to SVGHandler::getMetadata is bogus.
+ $size = filesize( $source );
+ if ( $size === false ) {
+ throw new MWException( "Error getting filesize of SVG." );
+ }
+
+ if ( $size > $wgSVGMetadataCutoff ) {
+ $this->debug( "SVG is $size bytes, which is bigger than $wgSVGMetadataCutoff. Truncating." );
+ $contents = file_get_contents( $source, false, null, -1, $wgSVGMetadataCutoff );
+ if ($contents === false) {
+ throw new MWException( 'Error reading SVG file.' );
+ }
+ $this->reader->XML( $contents, null, LIBXML_NOERROR | LIBXML_NOWARNING );
+ } else {
+ $this->reader->open( $source, null, LIBXML_NOERROR | LIBXML_NOWARNING );
+ }
+
+ $this->metadata['width'] = self::DEFAULT_WIDTH;
+ $this->metadata['height'] = self::DEFAULT_HEIGHT;
+
+ // Because we cut off the end of the svg making an invalid one. Complicated
+ // try catch thing to make sure warnings get restored. Seems like there should
+ // be a better way.
+ wfSuppressWarnings();
+ try {
+ $this->read();
+ } catch( Exception $e ) {
+ wfRestoreWarnings();
+ throw $e;
+ }
+ wfRestoreWarnings();
+ }
+
+ /*
+ * @return Array with the known metadata
+ */
+ public function getMetadata() {
+ return $this->metadata;
+ }
+
+ /*
+ * Read the SVG
+ */
+ public function read() {
+ $keepReading = $this->reader->read();
+
+ /* Skip until first element */
+ while( $keepReading && $this->reader->nodeType != XmlReader::ELEMENT ) {
+ $keepReading = $this->reader->read();
+ }
+
+ if ( $this->reader->localName != 'svg' || $this->reader->namespaceURI != self::NS_SVG ) {
+ throw new MWException( "Expected <svg> tag, got ".
+ $this->reader->localName . " in NS " . $this->reader->namespaceURI );
+ }
+ $this->debug( "<svg> tag is correct." );
+ $this->handleSVGAttribs();
+
+ $exitDepth = $this->reader->depth;
+ $keepReading = $this->reader->read();
+ while ( $keepReading ) {
+ $tag = $this->reader->localName;
+ $type = $this->reader->nodeType;
+ $isSVG = ($this->reader->namespaceURI == self::NS_SVG);
+
+ $this->debug( "$tag" );
+
+ if ( $isSVG && $tag == 'svg' && $type == XmlReader::END_ELEMENT && $this->reader->depth <= $exitDepth ) {
+ break;
+ } elseif ( $isSVG && $tag == 'title' ) {
+ $this->readField( $tag, 'title' );
+ } elseif ( $isSVG && $tag == 'desc' ) {
+ $this->readField( $tag, 'description' );
+ } elseif ( $isSVG && $tag == 'metadata' && $type == XmlReader::ELEMENT ) {
+ $this->readXml( $tag, 'metadata' );
+ } elseif ( $tag !== '#text' ) {
+ $this->debug( "Unhandled top-level XML tag $tag" );
+
+ if ( !isset( $this->metadata['animated'] ) ) {
+ // Recurse into children of current tag, looking for animation.
+ $this->animateFilter( $tag );
+ }
+ }
+
+ // Goto next element, which is sibling of current (Skip children).
+ $keepReading = $this->reader->next();
+ }
+
+ return true;
+ }
+
+ /*
+ * Read a textelement from an element
+ *
+ * @param String $name of the element that we are reading from
+ * @param String $metafield that we will fill with the result
+ */
+ private function readField( $name, $metafield=null ) {
+ $this->debug ( "Read field $metafield" );
+ if( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
+ return;
+ }
+ $keepReading = $this->reader->read();
+ while( $keepReading ) {
+ if( $this->reader->localName == $name && $this->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
+ break;
+ } elseif( $this->reader->nodeType == XmlReader::TEXT ){
+ $this->metadata[$metafield] = trim( $this->reader->value );
+ }
+ $keepReading = $this->reader->read();
+ }
+ }
+
+ /*
+ * Read an XML snippet from an element
+ *
+ * @param String $metafield that we will fill with the result
+ */
+ private function readXml( $metafield=null ) {
+ $this->debug ( "Read top level metadata" );
+ if( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
+ return;
+ }
+ // TODO: find and store type of xml snippet. metadata['metadataType'] = "rdf"
+ $this->metadata[$metafield] = trim( $this->reader->readInnerXML() );
+ $this->reader->next();
+ }
+
+ /*
+ * Filter all children, looking for animate elements
+ *
+ * @param String $name of the element that we are reading from
+ */
+ private function animateFilter( $name ) {
+ $this->debug ( "animate filter" );
+ if( $this->reader->nodeType != XmlReader::ELEMENT ) {
+ return;
+ }
+ $exitDepth = $this->reader->depth;
+ $keepReading = $this->reader->read();
+ while( $keepReading ) {
+ if( $this->reader->localName == $name && $this->reader->depth <= $exitDepth
+ && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
+ break;
+ } elseif ( $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::ELEMENT ) {
+ switch( $this->reader->localName ) {
+ case 'animate':
+ case 'set':
+ case 'animateMotion':
+ case 'animateColor':
+ case 'animateTransform':
+ $this->debug( "HOUSTON WE HAVE ANIMATION" );
+ $this->metadata['animated'] = true;
+ break;
+ }
+ }
+ $keepReading = $this->reader->read();
+ }
+ }
+
+ private function throwXmlError( $err ) {
+ $this->debug( "FAILURE: $err" );
+ wfDebug( "SVGReader XML error: $err\n" );
+ }
+
+ private function debug( $data ) {
+ if( $this->mDebug ) {
+ wfDebug( "SVGReader: $data\n" );
+ }
+ }
+
+ private function warn( $data ) {
+ wfDebug( "SVGReader: $data\n" );
+ }
+
+ private function notice( $data ) {
+ wfDebug( "SVGReader WARN: $data\n" );
+ }
+
+ /*
+ * Parse the attributes of an SVG element
+ *
+ * The parser has to be in the start element of <svg>
+ */
+ private function handleSVGAttribs( ) {
+ $defaultWidth = self::DEFAULT_WIDTH;
+ $defaultHeight = self::DEFAULT_HEIGHT;
+ $aspect = 1.0;
+ $width = null;
+ $height = null;
+
+ if( $this->reader->getAttribute('viewBox') ) {
+ // min-x min-y width height
+ $viewBox = preg_split( '/\s+/', trim( $this->reader->getAttribute('viewBox') ) );
+ if( count( $viewBox ) == 4 ) {
+ $viewWidth = $this->scaleSVGUnit( $viewBox[2] );
+ $viewHeight = $this->scaleSVGUnit( $viewBox[3] );
+ if( $viewWidth > 0 && $viewHeight > 0 ) {
+ $aspect = $viewWidth / $viewHeight;
+ $defaultHeight = $defaultWidth / $aspect;
+ }
+ }
+ }
+ if( $this->reader->getAttribute('width') ) {
+ $width = $this->scaleSVGUnit( $this->reader->getAttribute('width'), $defaultWidth );
+ }
+ if( $this->reader->getAttribute('height') ) {
+ $height = $this->scaleSVGUnit( $this->reader->getAttribute('height'), $defaultHeight );
+ }
+
+ if( !isset( $width ) && !isset( $height ) ) {
+ $width = $defaultWidth;
+ $height = $width / $aspect;
+ } elseif( isset( $width ) && !isset( $height ) ) {
+ $height = $width / $aspect;
+ } elseif( isset( $height ) && !isset( $width ) ) {
+ $width = $height * $aspect;
+ }
+
+ if( $width > 0 && $height > 0 ) {
+ $this->metadata['width'] = intval( round( $width ) );
+ $this->metadata['height'] = intval( round( $height ) );
+ }
+ }
+
+ /**
+ * Return a rounded pixel equivalent for a labeled CSS/SVG length.
+ * http://www.w3.org/TR/SVG11/coords.html#UnitIdentifiers
+ *
+ * @param $length String: CSS/SVG length.
+ * @param $viewportSize: Float optional scale for percentage units...
+ * @return float: length in pixels
+ */
+ static function scaleSVGUnit( $length, $viewportSize=512 ) {
+ static $unitLength = array(
+ 'px' => 1.0,
+ 'pt' => 1.25,
+ 'pc' => 15.0,
+ 'mm' => 3.543307,
+ 'cm' => 35.43307,
+ 'in' => 90.0,
+ 'em' => 16.0, // fake it?
+ 'ex' => 12.0, // fake it?
+ '' => 1.0, // "User units" pixels by default
+ );
+ $matches = array();
+ if( preg_match( '/^\s*(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)\s*$/', $length, $matches ) ) {
+ $length = floatval( $matches[1] );
+ $unit = $matches[2];
+ if( $unit == '%' ) {
+ return $length * 0.01 * $viewportSize;
+ } else {
+ return $length * $unitLength[$unit];
+ }
+ } else {
+ // Assume pixels
+ return floatval( $length );
+ }
+ }
+}
diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php
index 9d3fbb78..8773201f 100644
--- a/includes/media/Tiff.php
+++ b/includes/media/Tiff.php
@@ -1,10 +1,14 @@
<?php
/**
+ * Handler for Tiff images.
+ *
* @file
* @ingroup Media
*/
/**
+ * Handler for Tiff images.
+ *
* @ingroup Media
*/
class TiffHandler extends BitmapHandler {
@@ -26,7 +30,7 @@ class TiffHandler extends BitmapHandler {
return true;
}
- function getThumbType( $ext, $mime ) {
+ function getThumbType( $ext, $mime, $params = null ) {
global $wgTiffThumbnailType;
return $wgTiffThumbnailType;
}
diff --git a/includes/memcached-client.php b/includes/memcached-client.php
index 3b0ae90d..53f0324f 100644
--- a/includes/memcached-client.php
+++ b/includes/memcached-client.php
@@ -1,40 +1,41 @@
<?php
-//
-// +---------------------------------------------------------------------------+
-// | memcached client, PHP |
-// +---------------------------------------------------------------------------+
-// | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net> |
-// | All rights reserved. |
-// | |
-// | Redistribution and use in source and binary forms, with or without |
-// | modification, are permitted provided that the following conditions |
-// | are met: |
-// | |
-// | 1. Redistributions of source code must retain the above copyright |
-// | notice, this list of conditions and the following disclaimer. |
-// | 2. Redistributions in binary form must reproduce the above copyright |
-// | notice, this list of conditions and the following disclaimer in the |
-// | documentation and/or other materials provided with the distribution. |
-// | |
-// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
-// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
-// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
-// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
-// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
-// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
-// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
-// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
-// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
-// +---------------------------------------------------------------------------+
-// | Author: Ryan T. Dean <rtdean@cytherianage.net> |
-// | Heavily influenced by the Perl memcached client by Brad Fitzpatrick. |
-// | Permission granted by Brad Fitzpatrick for relicense of ported Perl |
-// | client logic under 2-clause BSD license. |
-// +---------------------------------------------------------------------------+
-//
-// $TCAnet$
-//
+/**
+ * +---------------------------------------------------------------------------+
+ * | memcached client, PHP |
+ * +---------------------------------------------------------------------------+
+ * | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net> |
+ * | All rights reserved. |
+ * | |
+ * | Redistribution and use in source and binary forms, with or without |
+ * | modification, are permitted provided that the following conditions |
+ * | are met: |
+ * | |
+ * | 1. Redistributions of source code must retain the above copyright |
+ * | notice, this list of conditions and the following disclaimer. |
+ * | 2. Redistributions in binary form must reproduce the above copyright |
+ * | notice, this list of conditions and the following disclaimer in the |
+ * | documentation and/or other materials provided with the distribution. |
+ * | |
+ * | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
+ * | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
+ * | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
+ * | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
+ * | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
+ * | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+ * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+ * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+ * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
+ * | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+ * +---------------------------------------------------------------------------+
+ * | Author: Ryan T. Dean <rtdean@cytherianage.net> |
+ * | Heavily influenced by the Perl memcached client by Brad Fitzpatrick. |
+ * | Permission granted by Brad Fitzpatrick for relicense of ported Perl |
+ * | client logic under 2-clause BSD license. |
+ * +---------------------------------------------------------------------------+
+ *
+ * @file
+ * $TCAnet$
+ */
/**
* This is the PHP client for memcached - a distributed memory cache daemon.
@@ -239,17 +240,17 @@ class MWMemcached {
/**
* Memcache initializer
*
- * @param array $args Associative array of settings
+ * @param $args Array Associative array of settings
*
* @return mixed
*/
public function __construct( $args ) {
global $wgMemCachedTimeout;
- $this->set_servers( @$args['servers'] );
- $this->_debug = @$args['debug'];
+ $this->set_servers( isset( $args['servers'] ) ? $args['servers'] : array() );
+ $this->_debug = isset( $args['debug'] ) ? $args['debug'] : false;
$this->stats = array();
- $this->_compress_threshold = @$args['compress_threshold'];
- $this->_persistant = array_key_exists( 'persistant', $args ) ? ( @$args['persistant'] ) : false;
+ $this->_compress_threshold = isset( $args['compress_threshold'] ) ? $args['compress_threshold'] : 0;
+ $this->_persistant = isset( $args['persistant'] ) ? $args['persistant'] : false;
$this->_compress_enable = true;
$this->_have_zlib = function_exists( 'gzcompress' );
@@ -270,11 +271,15 @@ class MWMemcached {
* Adds a key/value to the memcache server if one isn't already set with
* that key
*
- * @param string $key Key to set with data
- * @param mixed $val Value to store
- * @param integer $exp (optional) Time to expire data at
+ * @param $key String: key to set with data
+ * @param $val Mixed: value to store
+ * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
+ * longer must be the timestamp of the time at which the mapping should expire. It
+ * is safe to use timestamps in all cases, regardless of exipration
+ * eg: strtotime("+3 hour")
*
- * @return boolean
+ * @return Boolean
*/
public function add( $key, $val, $exp = 0 ) {
return $this->_set( 'add', $key, $val, $exp );
@@ -284,12 +289,12 @@ class MWMemcached {
// {{{ decr()
/**
- * Decriment a value stored on the memcache server
+ * Decrease a value stored on the memcache server
*
- * @param string $key Key to decriment
- * @param integer $amt (optional) Amount to decriment
+ * @param $key String: key to decrease
+ * @param $amt Integer: (optional) amount to decrease
*
- * @return mixed FALSE on failure, value on success
+ * @return Mixed: FALSE on failure, value on success
*/
public function decr( $key, $amt = 1 ) {
return $this->_incrdecr( 'decr', $key, $amt );
@@ -301,10 +306,10 @@ class MWMemcached {
/**
* Deletes a key from the server, optionally after $time
*
- * @param string $key Key to delete
- * @param integer $time (optional) How long to wait before deleting
+ * @param $key String: key to delete
+ * @param $time Integer: (optional) how long to wait before deleting
*
- * @return boolean TRUE on success, FALSE on failure
+ * @return Boolean: TRUE on success, FALSE on failure
*/
public function delete( $key, $time = 0 ) {
if ( !$this->_active ) {
@@ -318,7 +323,11 @@ class MWMemcached {
$key = is_array( $key ) ? $key[1] : $key;
- @$this->stats['delete']++;
+ if ( isset( $this->stats['delete'] ) ) {
+ $this->stats['delete']++;
+ } else {
+ $this->stats['delete'] = 1;
+ }
$cmd = "delete $key $time\r\n";
if( !$this->_safe_fwrite( $sock, $cmd, strlen( $cmd ) ) ) {
$this->_dead_sock( $sock );
@@ -356,7 +365,7 @@ class MWMemcached {
/**
* Enable / Disable compression
*
- * @param boolean $enable TRUE to enable, FALSE to disable
+ * @param $enable Boolean: TRUE to enable, FALSE to disable
*/
public function enable_compress( $enable ) {
$this->_compress_enable = $enable;
@@ -378,9 +387,9 @@ class MWMemcached {
/**
* Retrieves the value associated with the key from the memcache server
*
- * @param string $key Key to retrieve
+ * @param $key Mixed: key to retrieve
*
- * @return mixed
+ * @return Mixed
*/
public function get( $key ) {
wfProfileIn( __METHOD__ );
@@ -401,7 +410,11 @@ class MWMemcached {
return false;
}
- @$this->stats['get']++;
+ if ( isset( $this->stats['get'] ) ) {
+ $this->stats['get']++;
+ } else {
+ $this->stats['get'] = 1;
+ }
$cmd = "get $key\r\n";
if ( !$this->_safe_fwrite( $sock, $cmd, strlen( $cmd ) ) ) {
@@ -429,16 +442,20 @@ class MWMemcached {
/**
* Get multiple keys from the server(s)
*
- * @param array $keys Keys to retrieve
+ * @param $keys Array: keys to retrieve
*
- * @return array
+ * @return Array
*/
public function get_multi( $keys ) {
if ( !$this->_active ) {
return false;
}
- @$this->stats['get_multi']++;
+ if ( isset( $this->stats['get_multi'] ) ) {
+ $this->stats['get_multi']++;
+ } else {
+ $this->stats['get_multi'] = 1;
+ }
$sock_keys = array();
foreach ( $keys as $key ) {
@@ -490,10 +507,12 @@ class MWMemcached {
/**
* Increments $key (optionally) by $amt
*
- * @param string $key Key to increment
- * @param integer $amt (optional) amount to increment
+ * @param $key String: key to increment
+ * @param $amt Integer: (optional) amount to increment
*
- * @return integer New key value?
+ * @return Integer: null if the key does not exist yet (this does NOT
+ * create new mappings if the key does not exist). If the key does
+ * exist, this returns the new value for that key.
*/
public function incr( $key, $amt = 1 ) {
return $this->_incrdecr( 'incr', $key, $amt );
@@ -505,11 +524,15 @@ class MWMemcached {
/**
* Overwrites an existing value for key; only works if key is already set
*
- * @param string $key Key to set value as
- * @param mixed $value Value to store
- * @param integer $exp (optional) Experiation time
+ * @param $key String: key to set value as
+ * @param $value Mixed: value to store
+ * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
+ * longer must be the timestamp of the time at which the mapping should expire. It
+ * is safe to use timestamps in all cases, regardless of exipration
+ * eg: strtotime("+3 hour")
*
- * @return boolean
+ * @return Boolean
*/
public function replace( $key, $value, $exp = 0 ) {
return $this->_set( 'replace', $key, $value, $exp );
@@ -528,13 +551,12 @@ class MWMemcached {
* with a \n. This is with the PHP flag auto_detect_line_endings set
* to falase (the default).
*
- * @param resource $sock Socket to send command on
- * @param string $cmd Command to run
+ * @param $sock Ressource: socket to send command on
+ * @param $cmd String: command to run
*
- * @return array Output array
- * @access public
+ * @return Array: output array
*/
- function run_command( $sock, $cmd ) {
+ public function run_command( $sock, $cmd ) {
if ( !is_resource( $sock ) ) {
return array();
}
@@ -563,11 +585,15 @@ class MWMemcached {
* Unconditionally sets a key to a given value in the memcache. Returns true
* if set successfully.
*
- * @param string $key Key to set value as
- * @param mixed $value Value to set
- * @param integer $exp (optional) Experiation time
+ * @param $key String: key to set value as
+ * @param $value Mixed: value to set
+ * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
+ * longer must be the timestamp of the time at which the mapping should expire. It
+ * is safe to use timestamps in all cases, regardless of exipration
+ * eg: strtotime("+3 hour")
*
- * @return boolean TRUE on success
+ * @return Boolean: TRUE on success
*/
public function set( $key, $value, $exp = 0 ) {
return $this->_set( 'set', $key, $value, $exp );
@@ -579,7 +605,7 @@ class MWMemcached {
/**
* Sets the compression threshold
*
- * @param integer $thresh Threshold to compress if larger than
+ * @param $thresh Integer: threshold to compress if larger than
*/
public function set_compress_threshold( $thresh ) {
$this->_compress_threshold = $thresh;
@@ -591,7 +617,7 @@ class MWMemcached {
/**
* Sets the debug flag
*
- * @param boolean $dbg TRUE for debugging, FALSE otherwise
+ * @param $dbg Boolean: TRUE for debugging, FALSE otherwise
*
* @see MWMemcached::__construct
*/
@@ -605,7 +631,7 @@ class MWMemcached {
/**
* Sets the server list to distribute key gets and puts between
*
- * @param array $list Array of servers to connect to
+ * @param $list Array of servers to connect to
*
* @see MWMemcached::__construct()
*/
@@ -624,8 +650,8 @@ class MWMemcached {
/**
* Sets the timeout for new connections
*
- * @param integer $seconds Number of seconds
- * @param integer $microseconds Number of microseconds
+ * @param $seconds Integer: number of seconds
+ * @param $microseconds Integer: number of microseconds
*/
public function set_timeout( $seconds, $microseconds ) {
$this->_timeout_seconds = $seconds;
@@ -640,7 +666,7 @@ class MWMemcached {
/**
* Close the specified socket
*
- * @param string $sock Socket to close
+ * @param $sock String: socket to close
*
* @access private
*/
@@ -656,8 +682,8 @@ class MWMemcached {
/**
* Connects $sock to $host, timing out after $timeout
*
- * @param integer $sock Socket to connect
- * @param string $host Host:IP to connect to
+ * @param $sock Integer: socket to connect
+ * @param $host String: Host:IP to connect to
*
* @return boolean
* @access private
@@ -668,11 +694,13 @@ class MWMemcached {
$timeout = $this->_connect_timeout;
$errno = $errstr = null;
for( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
+ wfSuppressWarnings();
if ( $this->_persistant == 1 ) {
- $sock = @pfsockopen( $ip, $port, $errno, $errstr, $timeout );
+ $sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
} else {
- $sock = @fsockopen( $ip, $port, $errno, $errstr, $timeout );
+ $sock = fsockopen( $ip, $port, $errno, $errstr, $timeout );
}
+ wfRestoreWarnings();
}
if ( !$sock ) {
if ( $this->_debug ) {
@@ -693,7 +721,7 @@ class MWMemcached {
/**
* Marks a host as dead until 30-40 seconds in the future
*
- * @param string $sock Socket to mark as dead
+ * @param $sock String: socket to mark as dead
*
* @access private
*/
@@ -703,7 +731,8 @@ class MWMemcached {
}
function _dead_host( $host ) {
- @list( $ip, /* $port */) = explode( ':', $host );
+ $parts = explode( ':', $host );
+ $ip = $parts[0];
$this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) );
$this->_host_dead[$host] = $this->_host_dead[$ip];
unset( $this->_cache_sock[$host] );
@@ -715,10 +744,10 @@ class MWMemcached {
/**
* get_sock
*
- * @param string $key Key to retrieve value for;
+ * @param $key String: key to retrieve value for;
*
- * @return mixed resource on success, false on failure
- * @access private
+ * @return Mixed: resource on success, false on failure
+ * @access private
*/
function get_sock( $key ) {
if ( !$this->_active ) {
@@ -764,12 +793,12 @@ class MWMemcached {
// {{{ _hashfunc()
/**
- * Creates a hash integer based on the $key
+ * Creates a hash integer based on the $key
*
- * @param string $key Key to hash
+ * @param $key String: key to hash
*
- * @return integer Hash value
- * @access private
+ * @return Integer: hash value
+ * @access private
*/
function _hashfunc( $key ) {
# Hash function must on [0,0x7ffffff]
@@ -784,12 +813,12 @@ class MWMemcached {
/**
* Perform increment/decriment on $key
*
- * @param string $cmd Command to perform
- * @param string $key Key to perform it on
- * @param integer $amt Amount to adjust
+ * @param $cmd String: command to perform
+ * @param $key String: key to perform it on
+ * @param $amt Integer: amount to adjust
*
- * @return integer New value of $key
- * @access private
+ * @return Integer: new value of $key
+ * @access private
*/
function _incrdecr( $cmd, $key, $amt = 1 ) {
if ( !$this->_active ) {
@@ -802,7 +831,11 @@ class MWMemcached {
}
$key = is_array( $key ) ? $key[1] : $key;
- @$this->stats[$cmd]++;
+ if ( isset( $this->stats[$cmd] ) ) {
+ $this->stats[$cmd]++;
+ } else {
+ $this->stats[$cmd] = 1;
+ }
if ( !$this->_safe_fwrite( $sock, "$cmd $key $amt\r\n" ) ) {
return $this->_dead_sock( $sock );
}
@@ -821,10 +854,10 @@ class MWMemcached {
/**
* Load items into $ret from $sock
*
- * @param resource $sock Socket to read from
- * @param array $ret Returned values
+ * @param $sock Ressource: socket to read from
+ * @param $ret Array: returned values
*
- * @access private
+ * @access private
*/
function _load_items( $sock, &$ret ) {
while ( 1 ) {
@@ -844,7 +877,11 @@ class MWMemcached {
}
$offset += $n;
$bneed -= $n;
- @$ret[$rkey] .= $data;
+ if ( isset( $ret[$rkey] ) ) {
+ $ret[$rkey] .= $data;
+ } else {
+ $ret[$rkey] = $data;
+ }
}
if ( $offset != $len + 2 ) {
@@ -881,13 +918,17 @@ class MWMemcached {
/**
* Performs the requested storage operation to the memcache server
*
- * @param string $cmd Command to perform
- * @param string $key Key to act on
- * @param mixed $val What we need to store
- * @param integer $exp When it should expire
+ * @param $cmd String: command to perform
+ * @param $key String: key to act on
+ * @param $val Mixed: what we need to store
+ * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
+ * longer must be the timestamp of the time at which the mapping should expire. It
+ * is safe to use timestamps in all cases, regardless of exipration
+ * eg: strtotime("+3 hour")
*
- * @return boolean
- * @access private
+ * @return Boolean
+ * @access private
*/
function _set( $cmd, $key, $val, $exp ) {
if ( !$this->_active ) {
@@ -899,7 +940,11 @@ class MWMemcached {
return false;
}
- @$this->stats[$cmd]++;
+ if ( isset( $this->stats[$cmd] ) ) {
+ $this->stats[$cmd]++;
+ } else {
+ $this->stats[$cmd] = 1;
+ }
$flags = 0;
@@ -949,10 +994,10 @@ class MWMemcached {
/**
* Returns the socket for the host
*
- * @param string $host Host:IP to get socket for
+ * @param $host String: Host:IP to get socket for
*
- * @return mixed IO Stream or false
- * @access private
+ * @return Mixed: IO Stream or false
+ * @access private
*/
function sock_to_host( $host ) {
if ( isset( $this->_cache_sock[$host] ) ) {
@@ -987,7 +1032,7 @@ class MWMemcached {
/**
* Write to a stream, timing out after the correct amount of time
*
- * @return bool false on failure, true on success
+ * @return Boolean: false on failure, true on success
*/
/*
function _safe_fwrite( $f, $buf, $len = false ) {
diff --git a/includes/mime.info b/includes/mime.info
index 63b38f5a..3a32f5d9 100644
--- a/includes/mime.info
+++ b/includes/mime.info
@@ -16,9 +16,10 @@ image/x-icon [BITMAP]
image/x-rgb [BITMAP]
image/x-portable-pixmap [BITMAP]
image/x-portable-graymap image/x-portable-greymap [BITMAP]
-image/x-bmp image/bmp application/x-bmp application/bmp [BITMAP]
+image/x-bmp image/x-ms-bmp image/bmp application/x-bmp application/bmp [BITMAP]
image/x-photoshop image/psd image/x-psd image/photoshop [BITMAP]
image/vnd.djvu image/x.djvu image/x-djvu [BITMAP]
+image/webp [BITMAP]
image/svg+xml application/svg+xml application/svg image/svg [DRAWING]
application/postscript [DRAWING]
@@ -27,18 +28,23 @@ application/x-tex [DRAWING]
application/x-dia-diagram [DRAWING]
-audio/mp3 audio/mpeg3 audio/mpeg [AUDIO]
+audio/mpeg audio/mp3 audio/mpeg3 [AUDIO]
audio/wav audio/x-wav audio/wave [AUDIO]
audio/midi audio/mid [AUDIO]
audio/basic [AUDIO]
+audio/ogg [AUDIO]
audio/x-aiff [AUDIO]
audio/x-pn-realaudio [AUDIO]
audio/x-realaudio [AUDIO]
+audio/webm [AUDIO]
+audio/x-matroska [AUDIO]
video/mpeg application/mpeg [VIDEO]
video/ogg [VIDEO]
video/x-sgi-video [VIDEO]
video/x-flv [VIDEO]
+video/webm [VIDEO]
+video/x-matroska [VIDEO]
application/ogg application/x-ogg audio/ogg audio/x-ogg video/ogg video/x-ogg [MULTIMEDIA]
@@ -60,6 +66,7 @@ application/x-gzip [ARCHIVE]
application/x-bzip [ARCHIVE]
application/x-tar [ARCHIVE]
application/x-stuffit [ARCHIVE]
+application/x-opc+zip [ARCHIVE]
text/javascript application/x-javascript application/x-ecmascript text/ecmascript [EXECUTABLE]
@@ -77,3 +84,22 @@ application/vnd.ms-excel [OFFICE]
application/vnd.ms-powerpoint [OFFICE]
application/x-director [OFFICE]
text/rtf [OFFICE]
+
+application/vnd.openxmlformats-officedocument.wordprocessingml.document [OFFICE]
+application/vnd.openxmlformats-officedocument.wordprocessingml.template [OFFICE]
+application/vnd.ms-word.document.macroEnabled.12 [OFFICE]
+application/vnd.ms-word.template.macroEnabled.12 [OFFICE]
+application/vnd.openxmlformats-officedocument.presentationml.template [OFFICE]
+application/vnd.openxmlformats-officedocument.presentationml.slideshow [OFFICE]
+application/vnd.openxmlformats-officedocument.presentationml.presentation [OFFICE]
+application/vnd.ms-powerpoint.addin.macroEnabled.12 [OFFICE]
+application/vnd.ms-powerpoint.presentation.macroEnabled.12 [OFFICE]
+application/vnd.ms-powerpoint.presentation.macroEnabled.12 [OFFICE]
+application/vnd.ms-powerpoint.slideshow.macroEnabled.12 [OFFICE]
+application/vnd.openxmlformats-officedocument.spreadsheetml.sheet [OFFICE]
+application/vnd.openxmlformats-officedocument.spreadsheetml.template [OFFICE]
+application/vnd.ms-excel.sheet.macroEnabled.12 [OFFICE]
+application/vnd.ms-excel.template.macroEnabled.12 [OFFICE]
+application/vnd.ms-excel.addin.macroEnabled.12 [OFFICE]
+application/vnd.ms-excel.sheet.binary.macroEnabled.12 [OFFICE]
+
diff --git a/includes/mime.types b/includes/mime.types
index bd57cd40..bf8c6dd1 100644
--- a/includes/mime.types
+++ b/includes/mime.types
@@ -2,10 +2,10 @@ application/andrew-inset ez
application/mac-binhex40 hqx
application/mac-compactpro cpt
application/mathml+xml mathml
-application/msword doc docx docm dot dotx dotm
+application/msword doc dot
application/octet-stream bin dms lha lzh exe class so dll
application/oda oda
-application/ogg ogg ogm
+application/ogg ogx ogg ogm ogv oga spx
application/pdf pdf
application/postscript ai eps ps
application/rdf+xml rdf
@@ -13,8 +13,8 @@ application/smil smi smil
application/srgs gram
application/srgs+xml grxml
application/vnd.mif mif
-application/vnd.ms-excel xls xlsx xlsb xlam xltx xltm
-application/vnd.ms-powerpoint ppt pptm pptx pot potx potm ppsm ppam
+application/vnd.ms-excel xls xlt xla
+application/vnd.ms-powerpoint ppt pot pps ppa
application/vnd.wap.wbxml wbxml
application/vnd.wap.wmlc wmlc
application/vnd.wap.wmlscriptc wmlsc
@@ -63,10 +63,12 @@ application/x-rar rar
audio/basic au snd
audio/midi mid midi kar
audio/mpeg mpga mp2 mp3
-audio/ogg ogg
+audio/ogg oga ogg spx
+audio/webm webm
audio/x-aiff aif aiff aifc
+audio/x-matroska mka mkv
audio/x-mpegurl m3u
-audio/x-ogg ogg
+audio/x-ogg oga ogg spx
audio/x-pn-realaudio ram rm
audio/x-pn-realaudio-plugin rpm
audio/x-realaudio ra
@@ -78,13 +80,15 @@ image/cgm cgm
image/gif gif
image/ief ief
image/jpeg jpeg jpg jpe
-image/png png
+image/png png apng
image/svg+xml svg
image/tiff tiff tif
image/vnd.djvu djvu djv
image/vnd.wap.wbmp wbmp
+image/webp webp
image/x-cmu-raster ras
image/x-icon ico
+image/x-ms-bmp bmp
image/x-portable-anymap pnm
image/x-portable-bitmap pbm
image/x-portable-graymap pgm
@@ -110,12 +114,14 @@ text/vnd.wap.wmlscript wmls
text/xml xml xsl xslt rss rdf
text/x-setext etx
video/mpeg mpeg mpg mpe
-video/ogg ogm ogg
+video/ogg ogv ogm ogg
video/quicktime qt mov
video/vnd.mpegurl mxu
+video/webm webm
video/x-flv flv
+video/x-matroska mkv mka
video/x-msvideo avi
-video/x-ogg ogm ogg
+video/x-ogg ogv ogm ogg
video/x-sgi-movie movie
x-conference/x-cooltalk ice
application/vnd.oasis.opendocument.text odt
@@ -133,4 +139,24 @@ application/vnd.oasis.opendocument.image-template oti
application/vnd.oasis.opendocument.formula odf
application/vnd.oasis.opendocument.formula-template otf
application/vnd.oasis.opendocument.text-master odm
-application/vnd.oasis.opendocument.text-web oth \ No newline at end of file
+application/vnd.oasis.opendocument.text-web oth
+application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
+application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx
+application/vnd.ms-word.document.macroEnabled.12 docm
+application/vnd.ms-word.template.macroEnabled.12 dotm
+application/vnd.openxmlformats-officedocument.presentationml.template potx
+application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx
+application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
+application/vnd.ms-powerpoint.addin.macroEnabled.12 ppam
+application/vnd.ms-powerpoint.presentation.macroEnabled.12 pptm
+application/vnd.ms-powerpoint.presentation.macroEnabled.12 potm
+application/vnd.ms-powerpoint.slideshow.macroEnabled.12 ppsm
+application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
+application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx
+application/vnd.ms-excel.sheet.macroEnabled.12 xlsm
+application/vnd.ms-excel.template.macroEnabled.12 xltm
+application/vnd.ms-excel.addin.macroEnabled.12 xlam
+application/vnd.ms-excel.sheet.binary.macroEnabled.12 xlsb
+model/vnd.dwfx+xps dwfx
+application/vnd.ms-xpsdocument xps
+application/x-opc+zip docx dotx docm dotm potx ppsx pptx ppam pptm potm ppsm xlsx xltx xlsm xltm xlam xlsb dwfx xps
diff --git a/includes/normal/CleanUpTest.php b/includes/normal/CleanUpTest.php
index d14bcad1..549a0406 100644
--- a/includes/normal/CleanUpTest.php
+++ b/includes/normal/CleanUpTest.php
@@ -1,21 +1,28 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * Tests for UtfNormal::cleanUp() function.
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
if( php_sapi_name() != 'cli' ) {
@@ -29,7 +36,14 @@ if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
#ini_set( 'memory_limit', '40M' );
-require_once 'PHPUnit/Framework.php';
+require_once( 'PHPUnit/Runner/Version.php' );
+if( version_compare( PHPUnit_Runner_Version::id(), '3.5.0', '>=' ) ) {
+ # PHPUnit 3.5.0 introduced a nice autoloader based on class name
+ require_once( 'PHPUnit/Autoload.php' );
+} else {
+ # Keep the old pre PHPUnit 3.5.0 behaviour for compatibility
+ require_once 'PHPUnit/Framework.php';
+}
require_once 'PHPUnit/TextUI/TestRunner.php';
require_once 'UtfNormal.php';
@@ -85,7 +99,6 @@ class CleanUpTest extends PHPUnit_Framework_TestCase {
*/
function XtestAllChars() {
$rep = UTF8_REPLACEMENT;
- global $utfCanonicalComp, $utfCanonicalDecomp;
for( $i = 0x0; $i < UNICODE_MAX; $i++ ) {
$char = codepointToUtf8( $i );
$clean = UtfNormal::cleanUp( $char );
@@ -97,7 +110,7 @@ class CleanUpTest extends PHPUnit_Framework_TestCase {
($i > 0x001f && $i < UNICODE_SURROGATE_FIRST) ||
($i > UNICODE_SURROGATE_LAST && $i < 0xfffe ) ||
($i > 0xffff && $i <= UNICODE_MAX ) ) {
- if( isset( $utfCanonicalComp[$char] ) || isset( $utfCanonicalDecomp[$char] ) ) {
+ if( isset( UtfNormal::$utfCanonicalComp[$char] ) || isset( UtfNormal::$utfCanonicalDecomp[$char] ) ) {
$comp = UtfNormal::NFC( $char );
$this->assertEquals(
bin2hex( $comp ),
diff --git a/includes/normal/RandomTest.php b/includes/normal/RandomTest.php
index eb137574..f2ec460e 100644
--- a/includes/normal/RandomTest.php
+++ b/includes/normal/RandomTest.php
@@ -1,29 +1,29 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
* 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>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
* @ingroup UtfNormal
- * @access private
*/
if( php_sapi_name() != 'cli' ) {
diff --git a/includes/normal/Utf8Case.php b/includes/normal/Utf8Case.php
index 9734a922..89b1a892 100644
--- a/includes/normal/Utf8Case.php
+++ b/includes/normal/Utf8Case.php
@@ -1,19 +1,20 @@
<?php
/**
- * Simple 1:1 upper/lowercase switching arrays for utf-8 text
- * Won't get context-sensitive things yet
+ * Simple 1:1 upper/lowercase switching arrays for utf-8 text.
+ * Won't get context-sensitive things yet.
*
* Hack for bugs in ucfirst() and company
*
* These are pulled from memcached if possible, as this is faster than filling
* up a big array manually.
+ *
+ * @file
* @ingroup Language
*/
-/*
+/**
* Translation array to get upper case character
*/
-
$wikiUpperChars = array(
'a' => 'A',
'b' => 'B',
@@ -1048,7 +1049,7 @@ $wikiUpperChars = array(
'𐑏' => '𐐧'
);
-/*
+/**
* Translation array to get lower case character
*/
$wikiLowerChars = array(
diff --git a/includes/normal/Utf8CaseGenerate.php b/includes/normal/Utf8CaseGenerate.php
index 22994ba4..8dbff1db 100644
--- a/includes/normal/Utf8CaseGenerate.php
+++ b/includes/normal/Utf8CaseGenerate.php
@@ -1,32 +1,30 @@
<?php
-# Copyright (C) 2004,2008 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
- * This script generates Utf8Case.inc from the Unicode Character Database
+ * This script generates Utf8Case.php from the Unicode Character Database
* and supplementary files.
*
+ * Copyright © 2004,2008 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
* @ingroup UtfNormal
- * @access private
*/
-/** */
-
if( php_sapi_name() != 'cli' ) {
die( "Run me from the command line please.\n" );
}
@@ -67,23 +65,24 @@ if( $out ) {
$outLowerChars = escapeArray( $wikiLowerChars );
$outdata = "<" . "?php
/**
- * Simple 1:1 upper/lowercase switching arrays for utf-8 text
- * Won't get context-sensitive things yet
+ * Simple 1:1 upper/lowercase switching arrays for utf-8 text.
+ * Won't get context-sensitive things yet.
*
* Hack for bugs in ucfirst() and company
*
* These are pulled from memcached if possible, as this is faster than filling
* up a big array manually.
+ *
+ * @file
* @ingroup Language
*/
-/*
+/**
* Translation array to get upper case character
*/
-
\$wikiUpperChars = $outUpperChars;
-/*
+/**
* Translation array to get lower case character
*/
\$wikiLowerChars = $outLowerChars;\n";
diff --git a/includes/normal/Utf8Test.php b/includes/normal/Utf8Test.php
index 4c78b3db..53108bc4 100644
--- a/includes/normal/Utf8Test.php
+++ b/includes/normal/Utf8Test.php
@@ -1,28 +1,28 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
* 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>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
* @ingroup UtfNormal
- * @access private
*/
/** */
@@ -30,6 +30,7 @@ require_once 'UtfNormalUtil.php';
require_once 'UtfNormal.php';
mb_internal_encoding( "utf-8" );
+$verbose = false;
#$verbose = true;
if( php_sapi_name() != 'cli' ) {
die( "Run me from the command line please.\n" );
@@ -39,7 +40,7 @@ $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";
+ print "http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt\n";
exit(-1);
}
@@ -55,7 +56,7 @@ while( false !== ( $line = fgets( $in ) ) ) {
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";
+ print "http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt\n";
exit(-1);
}
@@ -102,10 +103,10 @@ while( false !== ( $line = fgets( $in ) ) ) {
if( in_array( $test, $longTests ) ) {
$line = fgets( $in );
for( $line = fgets( $in ); !preg_match( '/^\s+\|/', $line ); $line = fgets( $in ) ) {
- testLine( $test, $line, $total, $success, $failed );
+ testLine( $test, $line, $total, $success, $failed, $columns, $exceptions, $verbose );
}
} else {
- testLine( $test, $line, $total, $success, $failed );
+ testLine( $test, $line, $total, $success, $failed, $columns, $exceptions, $verbose );
}
}
}
@@ -120,7 +121,7 @@ echo "UTF-8 DECODER TEST SUCCESS!\n";
exit (0);
-function testLine( $test, $line, &$total, &$success, &$failed ) {
+function testLine( $test, $line, &$total, &$success, &$failed, $columns, $exceptions, $verbose ) {
$stripped = $line;
UtfNormal::quickisNFCVerify( $stripped );
@@ -130,10 +131,8 @@ function testLine( $test, $line, &$total, &$success, &$failed ) {
$len = strlen( substr( $stripped, 0, strpos( $stripped, '|' ) ) );
}
- global $columns;
$ok = $same ^ ($test >= 3 );
- global $exceptions;
$ok ^= in_array( $test, $exceptions );
$ok &= ($columns == $len);
@@ -144,7 +143,7 @@ function testLine( $test, $line, &$total, &$success, &$failed ) {
} else {
$failed++;
}
- global $verbose;
+
if( $verbose || !$ok ) {
print str_replace( "\n", "$len\n", $stripped );
}
diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php
index e1352fdb..116fb8f0 100644
--- a/includes/normal/UtfNormal.php
+++ b/includes/normal/UtfNormal.php
@@ -1,38 +1,35 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * Unicode normalization routines
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
/**
* @defgroup UtfNormal UtfNormal
*/
-/** */
require_once dirname(__FILE__).'/UtfNormalUtil.php';
-global $utfCombiningClass, $utfCanonicalComp, $utfCanonicalDecomp;
-$utfCombiningClass = null;
-$utfCanonicalComp = null;
-$utfCanonicalDecomp = null;
-
-# Load compatibility decompositions on demand if they are needed.
-global $utfCompatibilityDecomp;
-$utfCompatibilityDecomp = null;
-
/**
* For using the ICU wrapper
*/
@@ -45,6 +42,7 @@ define( 'UNORM_NFKC', 5 );
define( 'UNORM_FCD', 6 );
define( 'NORMALIZE_ICU', function_exists( 'utf8_normalize' ) );
+define( 'NORMALIZE_INTL', function_exists( 'normalizer_normalize' ) );
/**
* Unicode normalization routines for working with UTF-8 strings.
@@ -61,6 +59,15 @@ define( 'NORMALIZE_ICU', function_exists( 'utf8_normalize' ) );
* @ingroup UtfNormal
*/
class UtfNormal {
+ static $utfCombiningClass = null;
+ static $utfCanonicalComp = null;
+ static $utfCanonicalDecomp = null;
+
+ # Load compatibility decompositions on demand if they are needed.
+ static $utfCompatibilityDecomp = null;
+
+ static $utfCheckNFC;
+
/**
* The ultimate convenience function! Clean up invalid UTF-8 sequences,
* and convert to normal form C, canonical composition.
@@ -73,17 +80,29 @@ class UtfNormal {
*/
static function cleanUp( $string ) {
if( NORMALIZE_ICU ) {
- # We exclude a few chars that ICU would not.
- $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 );
+ $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", 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;
@@ -101,7 +120,9 @@ class UtfNormal {
* @return string a UTF-8 string in normal form C
*/
static function toNFC( $string ) {
- if( NORMALIZE_ICU )
+ if( NORMALIZE_INTL )
+ return normalizer_normalize( $string, Normalizer::FORM_C );
+ elseif( NORMALIZE_ICU )
return utf8_normalize( $string, UNORM_NFC );
elseif( UtfNormal::quickIsNFC( $string ) )
return $string;
@@ -117,7 +138,9 @@ class UtfNormal {
* @return string a UTF-8 string in normal form D
*/
static function toNFD( $string ) {
- if( NORMALIZE_ICU )
+ if( NORMALIZE_INTL )
+ return normalizer_normalize( $string, Normalizer::FORM_D );
+ elseif( NORMALIZE_ICU )
return utf8_normalize( $string, UNORM_NFD );
elseif( preg_match( '/[\x80-\xff]/', $string ) )
return UtfNormal::NFD( $string );
@@ -134,7 +157,9 @@ class UtfNormal {
* @return string a UTF-8 string in normal form KC
*/
static function toNFKC( $string ) {
- if( NORMALIZE_ICU )
+ if( NORMALIZE_INTL )
+ return normalizer_normalize( $string, Normalizer::FORM_KC );
+ elseif( NORMALIZE_ICU )
return utf8_normalize( $string, UNORM_NFKC );
elseif( preg_match( '/[\x80-\xff]/', $string ) )
return UtfNormal::NFKC( $string );
@@ -151,7 +176,9 @@ class UtfNormal {
* @return string a UTF-8 string in normal form KD
*/
static function toNFKD( $string ) {
- if( NORMALIZE_ICU )
+ if( NORMALIZE_INTL )
+ return normalizer_normalize( $string, Normalizer::FORM_KD );
+ elseif( NORMALIZE_ICU )
return utf8_normalize( $string, UNORM_NFKD );
elseif( preg_match( '/[\x80-\xff]/', $string ) )
return UtfNormal::NFKD( $string );
@@ -164,8 +191,7 @@ class UtfNormal {
* @private
*/
static function loadData() {
- global $utfCombiningClass;
- if( !isset( $utfCombiningClass ) ) {
+ if( !isset( self::$utfCombiningClass ) ) {
require_once( dirname(__FILE__) . '/UtfNormalData.inc' );
}
}
@@ -182,7 +208,6 @@ class UtfNormal {
if( !preg_match( '/[\x80-\xff]/', $string ) ) return true;
UtfNormal::loadData();
- global $utfCheckNFC, $utfCombiningClass;
$len = strlen( $string );
for( $i = 0; $i < $len; $i++ ) {
$c = $string{$i};
@@ -199,11 +224,11 @@ class UtfNormal {
$c = substr( $string, $i, 2 );
$i++;
}
- if( isset( $utfCheckNFC[$c] ) ) {
+ if( isset( self::$utfCheckNFC[$c] ) ) {
# If it's NO or MAYBE, bail and do the slow check.
return false;
}
- if( isset( $utfCombiningClass[$c] ) ) {
+ if( isset( self::$utfCombiningClass[$c] ) ) {
# Combining character? We might have to do sorting, at least.
return false;
}
@@ -229,9 +254,8 @@ class UtfNormal {
if( !isset( $checkit ) ) {
# Load/build some scary lookup tables...
UtfNormal::loadData();
- global $utfCheckNFC, $utfCombiningClass;
- $utfCheckOrCombining = array_merge( $utfCheckNFC, $utfCombiningClass );
+ $utfCheckOrCombining = array_merge( self::$utfCheckNFC, self::$utfCombiningClass );
# Head bytes for sequences which we should do further validity checks
$checkit = array_flip( array_map( 'chr',
@@ -295,7 +319,8 @@ class UtfNormal {
$len = $chunk + 1; # Counting down is faster. I'm *so* sorry.
for( $i = -1; --$len; ) {
- if( $remaining = $tailBytes[$c = $str{++$i}] ) {
+ $remaining = $tailBytes[$c = $str{++$i}];
+ if( $remaining ) {
# UTF-8 head byte!
$sequence = $head = $c;
do {
@@ -446,9 +471,9 @@ class UtfNormal {
*/
static function NFD( $string ) {
UtfNormal::loadData();
- global $utfCanonicalDecomp;
+
return UtfNormal::fastCombiningSort(
- UtfNormal::fastDecompose( $string, $utfCanonicalDecomp ) );
+ UtfNormal::fastDecompose( $string, self::$utfCanonicalDecomp ) );
}
/**
@@ -466,12 +491,11 @@ class UtfNormal {
* @private
*/
static function NFKD( $string ) {
- global $utfCompatibilityDecomp;
- if( !isset( $utfCompatibilityDecomp ) ) {
+ if( !isset( self::$utfCompatibilityDecomp ) ) {
require_once( 'UtfNormalDataK.inc' );
}
- return UtfNormal::fastCombiningSort(
- UtfNormal::fastDecompose( $string, $utfCompatibilityDecomp ) );
+ return self::fastCombiningSort(
+ self::fastDecompose( $string, self::$utfCompatibilityDecomp ) );
}
@@ -546,7 +570,6 @@ class UtfNormal {
*/
static function fastCombiningSort( $string ) {
UtfNormal::loadData();
- global $utfCombiningClass;
$len = strlen( $string );
$out = '';
$combiners = array();
@@ -565,8 +588,8 @@ class UtfNormal {
$c = substr( $string, $i, 2 );
$i++;
}
- if( isset( $utfCombiningClass[$c] ) ) {
- $lastClass = $utfCombiningClass[$c];
+ if( isset( self::$utfCombiningClass[$c] ) ) {
+ $lastClass = self::$utfCombiningClass[$c];
if( isset( $combiners[$lastClass] ) ) {
$combiners[$lastClass] .= $c;
} else {
@@ -599,7 +622,6 @@ class UtfNormal {
*/
static function fastCompose( $string ) {
UtfNormal::loadData();
- global $utfCanonicalComp, $utfCombiningClass;
$len = strlen( $string );
$out = '';
$lastClass = -1;
@@ -631,14 +653,14 @@ class UtfNormal {
}
$pair = $startChar . $c;
if( $n > 0x80 ) {
- if( isset( $utfCombiningClass[$c] ) ) {
+ if( isset( self::$utfCombiningClass[$c] ) ) {
# A combining char; see what we can do with it
- $class = $utfCombiningClass[$c];
+ $class = self::$utfCombiningClass[$c];
if( !empty( $startChar ) &&
$lastClass < $class &&
$class > 0 &&
- isset( $utfCanonicalComp[$pair] ) ) {
- $startChar = $utfCanonicalComp[$pair];
+ isset( self::$utfCanonicalComp[$pair] ) ) {
+ $startChar = self::$utfCanonicalComp[$pair];
$class = 0;
} else {
$combining .= $c;
@@ -650,8 +672,8 @@ class UtfNormal {
}
# New start char
if( $lastClass == 0 ) {
- if( isset( $utfCanonicalComp[$pair] ) ) {
- $startChar = $utfCanonicalComp[$pair];
+ if( isset( self::$utfCanonicalComp[$pair] ) ) {
+ $startChar = self::$utfCanonicalComp[$pair];
$lastHangul = 0;
continue;
}
@@ -737,4 +759,20 @@ class UtfNormal {
}
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
index 1bcd8334..2229dbb4 100644
--- a/includes/normal/UtfNormalBench.php
+++ b/includes/normal/UtfNormalBench.php
@@ -1,30 +1,29 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
* Approximate benchmark for some basic operations.
*
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
* @ingroup UtfNormal
- * @access private
*/
-/** */
if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
dl( 'php_utfnormal.so' );
}
@@ -86,11 +85,10 @@ function benchTime(){
}
function benchmarkForm( &$u, &$data, $form ) {
- global $utfCanonicalDecomp;
#$start = benchTime();
for( $i = 0; $i < BENCH_CYCLES; $i++ ) {
$start = benchTime();
- $out = $u->$form( $data, $utfCanonicalDecomp );
+ $out = $u->$form( $data, UtfNormal::$utfCanonicalDecomp );
$deltas[] = (benchTime() - $start);
}
#$delta = (benchTime() - $start) / BENCH_CYCLES;
diff --git a/includes/normal/UtfNormalData.inc b/includes/normal/UtfNormalData.inc
index 46a93947..1d6b4680 100644
--- a/includes/normal/UtfNormalData.inc
+++ b/includes/normal/UtfNormalData.inc
@@ -2,11 +2,12 @@
/**
* This file was automatically generated -- do not edit!
* Run UtfNormalGenerate.php to create this file again (make clean && make)
+ *
+ * @file
*/
-/** */
-global $utfCombiningClass, $utfCanonicalComp, $utfCanonicalDecomp, $utfCheckNFC;
-$utfCombiningClass = unserialize( 'a:594:{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: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: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: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: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: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: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: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;}' );
-$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:"𪘀";}' );
-$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:"𪘀";}' );
-$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";}' );
+
+UtfNormal::$utfCombiningClass = unserialize( 'a:501:{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: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: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: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: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: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: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: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:9;s:3:"꣄";i:9;s:3:"꤫";i:220;s:3:"꤬";i:220;s:3:"꤭";i:220;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: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:1862:{s:3:"À";s:2:"À";s:3:"Á";s:2:"Á";s:3:"Â";s:2:"Â";s:3:"Ã";s:2:"Ã";s:3:"Ä";s:2:"Ä";s:3:"Å";s:2:"Å";s:3:"Ç";s:2:"Ç";s:3:"È";s:2:"È";s:3:"É";s:2:"É";s:3:"Ê";s:2:"Ê";s:3:"Ë";s:2:"Ë";s:3:"Ì";s:2:"Ì";s:3:"Í";s:2:"Í";s:3:"Î";s:2:"Î";s:3:"Ï";s:2:"Ï";s:3:"Ñ";s:2:"Ñ";s:3:"Ò";s:2:"Ò";s:3:"Ó";s:2:"Ó";s:3:"Ô";s:2:"Ô";s:3:"Õ";s:2:"Õ";s:3:"Ö";s:2:"Ö";s:3:"Ù";s:2:"Ù";s:3:"Ú";s:2:"Ú";s:3:"Û";s:2:"Û";s:3:"Ü";s:2:"Ü";s:3:"Ý";s:2:"Ý";s:3:"à";s:2:"à";s:3:"á";s:2:"á";s:3:"â";s:2:"â";s:3:"ã";s:2:"ã";s:3:"ä";s:2:"ä";s:3:"å";s:2:"å";s:3:"ç";s:2:"ç";s:3:"è";s:2:"è";s:3:"é";s:2:"é";s:3:"ê";s:2:"ê";s:3:"ë";s:2:"ë";s:3:"ì";s:2:"ì";s:3:"í";s:2:"í";s:3:"î";s:2:"î";s:3:"ï";s:2:"ï";s:3:"ñ";s:2:"ñ";s:3:"ò";s:2:"ò";s:3:"ó";s:2:"ó";s:3:"ô";s:2:"ô";s:3:"õ";s:2:"õ";s:3:"ö";s:2:"ö";s:3:"ù";s:2:"ù";s:3:"ú";s:2:"ú";s:3:"û";s:2:"û";s:3:"ü";s:2:"ü";s:3:"ý";s:2:"ý";s:3:"ÿ";s:2:"ÿ";s:3:"Ā";s:2:"Ā";s:3:"ā";s:2:"ā";s:3:"Ă";s:2:"Ă";s:3:"ă";s:2:"ă";s:3:"Ą";s:2:"Ą";s:3:"ą";s:2:"ą";s:3:"Ć";s:2:"Ć";s:3:"ć";s:2:"ć";s:3:"Ĉ";s:2:"Ĉ";s:3:"ĉ";s:2:"ĉ";s:3:"Ċ";s:2:"Ċ";s:3:"ċ";s:2:"ċ";s:3:"Č";s:2:"Č";s:3:"č";s:2:"č";s:3:"Ď";s:2:"Ď";s:3:"ď";s:2:"ď";s:3:"Ē";s:2:"Ē";s:3:"ē";s:2:"ē";s:3:"Ĕ";s:2:"Ĕ";s:3:"ĕ";s:2:"ĕ";s:3:"Ė";s:2:"Ė";s:3:"ė";s:2:"ė";s:3:"Ę";s:2:"Ę";s:3:"ę";s:2:"ę";s:3:"Ě";s:2:"Ě";s:3:"ě";s:2:"ě";s:3:"Ĝ";s:2:"Ĝ";s:3:"ĝ";s:2:"ĝ";s:3:"Ğ";s:2:"Ğ";s:3:"ğ";s:2:"ğ";s:3:"Ġ";s:2:"Ġ";s:3:"ġ";s:2:"ġ";s:3:"Ģ";s:2:"Ģ";s:3:"ģ";s:2:"ģ";s:3:"Ĥ";s:2:"Ĥ";s:3:"ĥ";s:2:"ĥ";s:3:"Ĩ";s:2:"Ĩ";s:3:"ĩ";s:2:"ĩ";s:3:"Ī";s:2:"Ī";s:3:"ī";s:2:"ī";s:3:"Ĭ";s:2:"Ĭ";s:3:"ĭ";s:2:"ĭ";s:3:"Į";s:2:"Į";s:3:"į";s:2:"į";s:3:"İ";s:2:"İ";s:3:"Ĵ";s:2:"Ĵ";s:3:"ĵ";s:2:"ĵ";s:3:"Ķ";s:2:"Ķ";s:3:"ķ";s:2:"ķ";s:3:"Ĺ";s:2:"Ĺ";s:3:"ĺ";s:2:"ĺ";s:3:"Ļ";s:2:"Ļ";s:3:"ļ";s:2:"ļ";s:3:"Ľ";s:2:"Ľ";s:3:"ľ";s:2:"ľ";s:3:"Ń";s:2:"Ń";s:3:"ń";s:2:"ń";s:3:"Ņ";s:2:"Ņ";s:3:"ņ";s:2:"ņ";s:3:"Ň";s:2:"Ň";s:3:"ň";s:2:"ň";s:3:"Ō";s:2:"Ō";s:3:"ō";s:2:"ō";s:3:"Ŏ";s:2:"Ŏ";s:3:"ŏ";s:2:"ŏ";s:3:"Ő";s:2:"Ő";s:3:"ő";s:2:"ő";s:3:"Ŕ";s:2:"Ŕ";s:3:"ŕ";s:2:"ŕ";s:3:"Ŗ";s:2:"Ŗ";s:3:"ŗ";s:2:"ŗ";s:3:"Ř";s:2:"Ř";s:3:"ř";s:2:"ř";s:3:"Ś";s:2:"Ś";s:3:"ś";s:2:"ś";s:3:"Ŝ";s:2:"Ŝ";s:3:"ŝ";s:2:"ŝ";s:3:"Ş";s:2:"Ş";s:3:"ş";s:2:"ş";s:3:"Š";s:2:"Š";s:3:"š";s:2:"š";s:3:"Ţ";s:2:"Ţ";s:3:"ţ";s:2:"ţ";s:3:"Ť";s:2:"Ť";s:3:"ť";s:2:"ť";s:3:"Ũ";s:2:"Ũ";s:3:"ũ";s:2:"ũ";s:3:"Ū";s:2:"Ū";s:3:"ū";s:2:"ū";s:3:"Ŭ";s:2:"Ŭ";s:3:"ŭ";s:2:"ŭ";s:3:"Ů";s:2:"Ů";s:3:"ů";s:2:"ů";s:3:"Ű";s:2:"Ű";s:3:"ű";s:2:"ű";s:3:"Ų";s:2:"Ų";s:3:"ų";s:2:"ų";s:3:"Ŵ";s:2:"Ŵ";s:3:"ŵ";s:2:"ŵ";s:3:"Ŷ";s:2:"Ŷ";s:3:"ŷ";s:2:"ŷ";s:3:"Ÿ";s:2:"Ÿ";s:3:"Ź";s:2:"Ź";s:3:"ź";s:2:"ź";s:3:"Ż";s:2:"Ż";s:3:"ż";s:2:"ż";s:3:"Ž";s:2:"Ž";s:3:"ž";s:2:"ž";s:3:"Ơ";s:2:"Ơ";s:3:"ơ";s:2:"ơ";s:3:"Ư";s:2:"Ư";s:3:"ư";s:2:"ư";s:3:"Ǎ";s:2:"Ǎ";s:3:"ǎ";s:2:"ǎ";s: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: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: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:2043:{s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s: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:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s: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: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:1217:{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:4:"𝅗𝅥";s:1:"N";s:4:"𝅘𝅥";s:1:"N";s:4:"𝅘𝅥𝅮";s:1:"N";s:4:"𝅘𝅥𝅯";s:1:"N";s:4:"𝅘𝅥𝅰";s:1:"N";s:4:"𝅘𝅥𝅱";s:1:"N";s:4:"𝅘𝅥𝅲";s:1:"N";s:4:"𝆹𝅥";s:1:"N";s:4:"𝆺𝅥";s:1:"N";s:4:"𝆹𝅥𝅮";s:1:"N";s:4:"𝆺𝅥𝅮";s:1:"N";s:4:"𝆹𝅥𝅯";s:1:"N";s:4:"𝆺𝅥𝅯";s:1:"N";s:4:"丽";s:1:"N";s:4:"丸";s:1:"N";s:4:"乁";s:1:"N";s:4:"𠄢";s:1:"N";s:4:"你";s:1:"N";s:4:"侮";s:1:"N";s:4:"侻";s:1:"N";s:4:"倂";s:1:"N";s:4:"偺";s:1:"N";s:4:"備";s:1:"N";s:4:"僧";s:1:"N";s:4:"像";s:1:"N";s:4:"㒞";s:1:"N";s:4:"𠘺";s:1:"N";s:4:"免";s:1:"N";s:4:"兔";s:1:"N";s:4:"兤";s:1:"N";s:4:"具";s:1:"N";s:4:"𠔜";s:1:"N";s:4:"㒹";s:1:"N";s:4:"內";s:1:"N";s:4:"再";s:1:"N";s:4:"𠕋";s:1:"N";s:4:"冗";s:1:"N";s:4:"冤";s:1:"N";s:4:"仌";s:1:"N";s:4:"冬";s:1:"N";s:4:"况";s:1:"N";s:4:"𩇟";s:1:"N";s:4:"凵";s:1:"N";s:4:"刃";s:1:"N";s:4:"㓟";s:1:"N";s:4:"刻";s:1:"N";s:4:"剆";s:1:"N";s:4:"割";s:1:"N";s:4:"剷";s:1:"N";s:4:"㔕";s:1:"N";s:4:"勇";s:1:"N";s:4:"勉";s:1:"N";s:4:"勤";s:1:"N";s:4:"勺";s:1:"N";s:4:"包";s:1:"N";s:4:"匆";s:1:"N";s:4:"北";s:1:"N";s:4:"卉";s:1:"N";s:4:"卑";s:1:"N";s:4:"博";s:1:"N";s:4:"即";s:1:"N";s:4:"卽";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"𠨬";s:1:"N";s:4:"灰";s:1:"N";s:4:"及";s:1:"N";s:4:"叟";s:1:"N";s:4:"𠭣";s:1:"N";s:4:"叫";s:1:"N";s:4:"叱";s:1:"N";s:4:"吆";s:1:"N";s:4:"咞";s:1:"N";s:4:"吸";s:1:"N";s:4:"呈";s:1:"N";s:4:"周";s:1:"N";s:4:"咢";s:1:"N";s:4:"哶";s:1:"N";s:4:"唐";s:1:"N";s:4:"啓";s:1:"N";s:4:"啣";s:1:"N";s:4:"善";s:1:"N";s:4:"善";s:1:"N";s:4:"喙";s:1:"N";s:4:"喫";s:1:"N";s:4:"喳";s:1:"N";s:4:"嗂";s:1:"N";s:4:"圖";s:1:"N";s:4:"嘆";s:1:"N";s:4:"圗";s:1:"N";s:4:"噑";s:1:"N";s:4:"噴";s:1:"N";s:4:"切";s:1:"N";s:4:"壮";s:1:"N";s:4:"城";s:1:"N";s:4:"埴";s:1:"N";s:4:"堍";s:1:"N";s:4:"型";s:1:"N";s:4:"堲";s:1:"N";s:4:"報";s:1:"N";s:4:"墬";s:1:"N";s:4:"𡓤";s:1:"N";s:4:"売";s:1:"N";s:4:"壷";s:1:"N";s:4:"夆";s:1:"N";s:4:"多";s:1:"N";s:4:"夢";s:1:"N";s:4:"奢";s:1:"N";s:4:"𡚨";s:1:"N";s:4:"𡛪";s:1:"N";s:4:"姬";s:1:"N";s:4:"娛";s:1:"N";s:4:"娧";s:1:"N";s:4:"姘";s:1:"N";s:4:"婦";s:1:"N";s:4:"㛮";s:1:"N";s:4:"㛼";s:1:"N";s:4:"嬈";s:1:"N";s:4:"嬾";s:1:"N";s:4:"嬾";s:1:"N";s:4:"𡧈";s:1:"N";s:4:"寃";s:1:"N";s:4:"寘";s:1:"N";s:4:"寧";s:1:"N";s:4:"寳";s:1:"N";s:4:"𡬘";s:1:"N";s:4:"寿";s:1:"N";s:4:"将";s:1:"N";s:4:"当";s:1:"N";s:4:"尢";s:1:"N";s:4:"㞁";s:1:"N";s:4:"屠";s:1:"N";s:4:"屮";s:1:"N";s:4:"峀";s:1:"N";s:4:"岍";s:1:"N";s:4:"𡷤";s:1:"N";s:4:"嵃";s:1:"N";s:4:"𡷦";s:1:"N";s:4:"嵮";s:1:"N";s:4:"嵫";s:1:"N";s:4:"嵼";s:1:"N";s:4:"巡";s:1:"N";s:4:"巢";s:1:"N";s:4:"㠯";s:1:"N";s:4:"巽";s:1:"N";s:4:"帨";s:1:"N";s:4:"帽";s:1:"N";s:4:"幩";s:1:"N";s:4:"㡢";s:1:"N";s:4:"𢆃";s:1:"N";s:4:"㡼";s:1:"N";s:4:"庰";s:1:"N";s:4:"庳";s:1:"N";s:4:"庶";s:1:"N";s:4:"廊";s:1:"N";s:4:"𪎒";s:1:"N";s:4:"廾";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"舁";s:1:"N";s:4:"弢";s:1:"N";s:4:"弢";s:1:"N";s:4:"㣇";s:1:"N";s:4:"𣊸";s:1:"N";s:4:"𦇚";s:1:"N";s:4:"形";s:1:"N";s:4:"彫";s:1:"N";s:4:"㣣";s:1:"N";s:4:"徚";s:1:"N";s:4:"忍";s:1:"N";s:4:"志";s:1:"N";s:4:"忹";s:1:"N";s:4:"悁";s:1:"N";s:4:"㤺";s:1:"N";s:4:"㤜";s:1:"N";s:4:"悔";s:1:"N";s:4:"𢛔";s:1:"N";s:4:"惇";s:1:"N";s:4:"慈";s:1:"N";s:4:"慌";s:1:"N";s:4:"慎";s:1:"N";s:4:"慌";s:1:"N";s:4:"慺";s:1:"N";s:4:"憎";s:1:"N";s:4:"憲";s:1:"N";s:4:"憤";s:1:"N";s:4:"憯";s:1:"N";s:4:"懞";s:1:"N";s:4:"懲";s:1:"N";s:4:"懶";s:1:"N";s:4:"成";s:1:"N";s:4:"戛";s:1:"N";s:4:"扝";s:1:"N";s:4:"抱";s:1:"N";s:4:"拔";s:1:"N";s:4:"捐";s:1:"N";s:4:"𢬌";s:1:"N";s:4:"挽";s:1:"N";s:4:"拼";s:1:"N";s:4:"捨";s:1:"N";s:4:"掃";s:1:"N";s:4:"揤";s:1:"N";s:4:"𢯱";s:1:"N";s:4:"搢";s:1:"N";s:4:"揅";s:1:"N";s:4:"掩";s:1:"N";s:4:"㨮";s:1:"N";s:4:"摩";s:1:"N";s:4:"摾";s:1:"N";s:4:"撝";s:1:"N";s:4:"摷";s:1:"N";s:4:"㩬";s:1:"N";s:4:"敏";s:1:"N";s:4:"敬";s:1:"N";s:4:"𣀊";s:1:"N";s:4:"旣";s:1:"N";s:4:"書";s:1:"N";s:4:"晉";s:1:"N";s:4:"㬙";s:1:"N";s:4:"暑";s:1:"N";s:4:"㬈";s:1:"N";s:4:"㫤";s:1:"N";s:4:"冒";s:1:"N";s:4:"冕";s:1:"N";s:4:"最";s:1:"N";s:4:"暜";s:1:"N";s:4:"肭";s:1:"N";s:4:"䏙";s:1:"N";s:4:"朗";s:1:"N";s:4:"望";s:1:"N";s:4:"朡";s:1:"N";s:4:"杞";s:1:"N";s:4:"杓";s:1:"N";s:4:"𣏃";s:1:"N";s:4:"㭉";s:1:"N";s:4:"柺";s:1:"N";s:4:"枅";s:1:"N";s:4:"桒";s:1:"N";s:4:"梅";s:1:"N";s:4:"𣑭";s:1:"N";s:4:"梎";s:1:"N";s:4:"栟";s:1:"N";s:4:"椔";s:1:"N";s:4:"㮝";s:1:"N";s:4:"楂";s:1:"N";s:4:"榣";s:1:"N";s:4:"槪";s:1:"N";s:4:"檨";s:1:"N";s:4:"𣚣";s:1:"N";s:4:"櫛";s:1:"N";s:4:"㰘";s:1:"N";s:4:"次";s:1:"N";s:4:"𣢧";s:1:"N";s:4:"歔";s:1:"N";s:4:"㱎";s:1:"N";s:4:"歲";s:1:"N";s:4:"殟";s:1:"N";s:4:"殺";s:1:"N";s:4:"殻";s:1:"N";s:4:"𣪍";s:1:"N";s:4:"𡴋";s:1:"N";s:4:"𣫺";s:1:"N";s:4:"汎";s:1:"N";s:4:"𣲼";s:1:"N";s:4:"沿";s:1:"N";s:4:"泍";s:1:"N";s:4:"汧";s:1:"N";s:4:"洖";s:1:"N";s:4:"派";s:1:"N";s:4:"海";s:1:"N";s:4:"流";s:1:"N";s:4:"浩";s:1:"N";s:4:"浸";s:1:"N";s:4:"涅";s:1:"N";s:4:"𣴞";s:1:"N";s:4:"洴";s:1:"N";s:4:"港";s:1:"N";s:4:"湮";s:1:"N";s:4:"㴳";s:1:"N";s:4:"滋";s:1:"N";s:4:"滇";s:1:"N";s:4:"𣻑";s:1:"N";s:4:"淹";s:1:"N";s:4:"潮";s:1:"N";s:4:"𣽞";s:1:"N";s:4:"𣾎";s:1:"N";s:4:"濆";s:1:"N";s:4:"瀹";s:1:"N";s:4:"瀞";s:1:"N";s:4:"瀛";s:1:"N";s:4:"㶖";s:1:"N";s:4:"灊";s:1:"N";s:4:"災";s:1:"N";s:4:"灷";s:1:"N";s:4:"炭";s:1:"N";s:4:"𠔥";s:1:"N";s:4:"煅";s:1:"N";s:4:"𤉣";s:1:"N";s:4:"熜";s:1:"N";s:4:"𤎫";s:1:"N";s:4:"爨";s:1:"N";s:4:"爵";s:1:"N";s:4:"牐";s:1:"N";s:4:"𤘈";s:1:"N";s:4:"犀";s:1:"N";s:4:"犕";s:1:"N";s:4:"𤜵";s:1:"N";s:4:"𤠔";s:1:"N";s:4:"獺";s:1:"N";s:4:"王";s:1:"N";s:4:"㺬";s:1:"N";s:4:"玥";s:1:"N";s:4:"㺸";s:1:"N";s:4:"㺸";s:1:"N";s:4:"瑇";s:1:"N";s:4:"瑜";s:1:"N";s:4:"瑱";s:1:"N";s:4:"璅";s:1:"N";s:4:"瓊";s:1:"N";s:4:"㼛";s:1:"N";s:4:"甤";s:1:"N";s:4:"𤰶";s:1:"N";s:4:"甾";s:1:"N";s:4:"𤲒";s:1:"N";s:4:"異";s:1:"N";s:4:"𢆟";s:1:"N";s:4:"瘐";s:1:"N";s:4:"𤾡";s:1:"N";s:4:"𤾸";s:1:"N";s:4:"𥁄";s:1:"N";s:4:"㿼";s:1:"N";s:4:"䀈";s:1:"N";s:4:"直";s:1:"N";s:4:"𥃳";s:1:"N";s:4:"𥃲";s:1:"N";s:4:"𥄙";s:1:"N";s:4:"𥄳";s:1:"N";s:4:"眞";s:1:"N";s:4:"真";s:1:"N";s:4:"真";s:1:"N";s:4:"睊";s:1:"N";s:4:"䀹";s:1:"N";s:4:"瞋";s:1:"N";s:4:"䁆";s:1:"N";s:4:"䂖";s:1:"N";s:4:"𥐝";s:1:"N";s:4:"硎";s:1:"N";s:4:"碌";s:1:"N";s:4:"磌";s:1:"N";s:4:"䃣";s:1:"N";s:4:"𥘦";s:1:"N";s:4:"祖";s:1:"N";s:4:"𥚚";s:1:"N";s:4:"𥛅";s:1:"N";s:4:"福";s:1:"N";s:4:"秫";s:1:"N";s:4:"䄯";s:1:"N";s:4:"穀";s:1:"N";s:4:"穊";s:1:"N";s:4:"穏";s:1:"N";s:4:"𥥼";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"竮";s:1:"N";s:4:"䈂";s:1:"N";s:4:"𥮫";s:1:"N";s:4:"篆";s:1:"N";s:4:"築";s:1:"N";s:4:"䈧";s:1:"N";s:4:"𥲀";s:1:"N";s:4:"糒";s:1:"N";s:4:"䊠";s:1:"N";s:4:"糨";s:1:"N";s:4:"糣";s:1:"N";s:4:"紀";s:1:"N";s:4:"𥾆";s:1:"N";s:4:"絣";s:1:"N";s:4:"䌁";s:1:"N";s:4:"緇";s:1:"N";s:4:"縂";s:1:"N";s:4:"繅";s:1:"N";s:4:"䌴";s:1:"N";s:4:"𦈨";s:1:"N";s:4:"𦉇";s:1:"N";s:4:"䍙";s:1:"N";s:4:"𦋙";s:1:"N";s:4:"罺";s:1:"N";s:4:"𦌾";s:1:"N";s:4:"羕";s:1:"N";s:4:"翺";s:1:"N";s:4:"者";s:1:"N";s:4:"𦓚";s:1:"N";s:4:"𦔣";s:1:"N";s:4:"聠";s:1:"N";s:4:"𦖨";s:1:"N";s:4:"聰";s:1:"N";s:4:"𣍟";s:1:"N";s:4:"䏕";s:1:"N";s:4:"育";s:1:"N";s:4:"脃";s:1:"N";s:4:"䐋";s:1:"N";s:4:"脾";s:1:"N";s:4:"媵";s:1:"N";s:4:"𦞧";s:1:"N";s:4:"𦞵";s:1:"N";s:4:"𣎓";s:1:"N";s:4:"𣎜";s:1:"N";s:4:"舁";s:1:"N";s:4:"舄";s:1:"N";s:4:"辞";s:1:"N";s:4:"䑫";s:1:"N";s:4:"芑";s:1:"N";s:4:"芋";s:1:"N";s:4:"芝";s:1:"N";s:4:"劳";s:1:"N";s:4:"花";s:1:"N";s:4:"芳";s:1:"N";s:4:"芽";s:1:"N";s:4:"苦";s:1:"N";s:4:"𦬼";s:1:"N";s:4:"若";s:1:"N";s:4:"茝";s:1:"N";s:4:"荣";s:1:"N";s:4:"莭";s:1:"N";s:4:"茣";s:1:"N";s:4:"莽";s:1:"N";s:4:"菧";s:1:"N";s:4:"著";s:1:"N";s:4:"荓";s:1:"N";s:4:"菊";s:1:"N";s:4:"菌";s:1:"N";s:4:"菜";s:1:"N";s:4:"𦰶";s:1:"N";s:4:"𦵫";s:1:"N";s:4:"𦳕";s:1:"N";s:4:"䔫";s:1:"N";s:4:"蓱";s:1:"N";s:4:"蓳";s:1:"N";s:4:"蔖";s:1:"N";s:4:"𧏊";s:1:"N";s:4:"蕤";s:1:"N";s:4:"𦼬";s:1:"N";s:4:"䕝";s:1:"N";s:4:"䕡";s:1:"N";s:4:"𦾱";s:1:"N";s:4:"𧃒";s:1:"N";s:4:"䕫";s:1:"N";s:4:"虐";s:1:"N";s:4:"虜";s:1:"N";s:4:"虧";s:1:"N";s:4:"虩";s:1:"N";s:4:"蚩";s:1:"N";s:4:"蚈";s:1:"N";s:4:"蜎";s:1:"N";s:4:"蛢";s:1:"N";s:4:"蝹";s:1:"N";s:4:"蜨";s:1:"N";s:4:"蝫";s:1:"N";s:4:"螆";s:1:"N";s:4:"䗗";s:1:"N";s:4:"蟡";s:1:"N";s:4:"蠁";s:1:"N";s:4:"䗹";s:1:"N";s:4:"衠";s:1:"N";s:4:"衣";s:1:"N";s:4:"𧙧";s:1:"N";s:4:"裗";s:1:"N";s:4:"裞";s:1:"N";s:4:"䘵";s:1:"N";s:4:"裺";s:1:"N";s:4:"㒻";s:1:"N";s:4:"𧢮";s:1:"N";s:4:"𧥦";s:1:"N";s:4:"䚾";s:1:"N";s:4:"䛇";s:1:"N";s:4:"誠";s:1:"N";s:4:"諭";s:1:"N";s:4:"變";s:1:"N";s:4:"豕";s:1:"N";s:4:"𧲨";s:1:"N";s:4:"貫";s:1:"N";s:4:"賁";s:1:"N";s:4:"贛";s:1:"N";s:4:"起";s:1:"N";s:4:"𧼯";s:1:"N";s:4:"𠠄";s:1:"N";s:4:"跋";s:1:"N";s:4:"趼";s:1:"N";s:4:"跰";s:1:"N";s:4:"𠣞";s:1:"N";s:4:"軔";s:1:"N";s:4:"輸";s:1:"N";s:4:"𨗒";s:1:"N";s:4:"𨗭";s:1:"N";s:4:"邔";s:1:"N";s:4:"郱";s:1:"N";s:4:"鄑";s:1:"N";s:4:"𨜮";s:1:"N";s:4:"鄛";s:1:"N";s:4:"鈸";s:1:"N";s:4:"鋗";s:1:"N";s:4:"鋘";s:1:"N";s:4:"鉼";s:1:"N";s:4:"鏹";s:1:"N";s:4:"鐕";s:1:"N";s:4:"𨯺";s:1:"N";s:4:"開";s:1:"N";s:4:"䦕";s:1:"N";s:4:"閷";s:1:"N";s:4:"𨵷";s:1:"N";s:4:"䧦";s:1:"N";s:4:"雃";s:1:"N";s:4:"嶲";s:1:"N";s:4:"霣";s:1:"N";s:4:"𩅅";s:1:"N";s:4:"𩈚";s:1:"N";s:4:"䩮";s:1:"N";s:4:"䩶";s:1:"N";s:4:"韠";s:1:"N";s:4:"𩐊";s:1:"N";s:4:"䪲";s:1:"N";s:4:"𩒖";s:1:"N";s:4:"頋";s:1:"N";s:4:"頋";s:1:"N";s:4:"頩";s:1:"N";s:4:"𩖶";s:1:"N";s:4:"飢";s:1:"N";s:4:"䬳";s:1:"N";s:4:"餩";s:1:"N";s:4:"馧";s:1:"N";s:4:"駂";s:1:"N";s:4:"駾";s:1:"N";s:4:"䯎";s:1:"N";s:4:"𩬰";s:1:"N";s:4:"鬒";s:1:"N";s:4:"鱀";s:1:"N";s:4:"鳽";s:1:"N";s:4:"䳎";s:1:"N";s:4:"䳭";s:1:"N";s:4:"鵧";s:1:"N";s:4:"𪃎";s:1:"N";s:4:"䳸";s:1:"N";s:4:"𪄅";s:1:"N";s:4:"𪈎";s:1:"N";s:4:"𪊑";s:1:"N";s:4:"麻";s:1:"N";s:4:"䵖";s:1:"N";s:4:"黹";s:1:"N";s:4:"黾";s:1:"N";s:4:"鼅";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";}' );
diff --git a/includes/normal/UtfNormalDataK.inc b/includes/normal/UtfNormalDataK.inc
index ad1b4bf2..9d30f282 100644
--- a/includes/normal/UtfNormalDataK.inc
+++ b/includes/normal/UtfNormalDataK.inc
@@ -2,8 +2,9 @@
/**
* This file was automatically generated -- do not edit!
* Run UtfNormalGenerate.php to create this file again (make clean && make)
+ *
+ * @file
*/
-/** */
-global $utfCompatibilityDecomp;
-$utfCompatibilityDecomp = unserialize( 'a:5516:{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: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:"B";s:4:"🄽";s:1:"N";s:4:"🄿";s:1:"P";s:4:"🅂";s:1:"S";s:4:"🅆";s:1:"W";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:"DJ";s:4:"🈀";s:6:"ほか";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: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: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::$utfCompatibilityDecomp = unserialize( 'a:5405:{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: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⁄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:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s: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:"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:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s: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: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:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
diff --git a/includes/normal/UtfNormalDefines.php b/includes/normal/UtfNormalDefines.php
index 419f6f8c..d759c64c 100644
--- a/includes/normal/UtfNormalDefines.php
+++ b/includes/normal/UtfNormalDefines.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Some constant definitions for the unicode normalization module
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
define( 'UNICODE_HANGUL_FIRST', 0xac00 );
define( 'UNICODE_HANGUL_LAST', 0xd7a3 );
diff --git a/includes/normal/UtfNormalGenerate.php b/includes/normal/UtfNormalGenerate.php
index 3b1e9e73..a5e2885a 100644
--- a/includes/normal/UtfNormalGenerate.php
+++ b/includes/normal/UtfNormalGenerate.php
@@ -1,32 +1,30 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
* This script generates UniNormalData.inc from the Unicode Character Database
* and supplementary files.
*
+ * Copyright (C) 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
* @ingroup UtfNormal
- * @access private
*/
-/** */
-
if( php_sapi_name() != 'cli' ) {
die( "Run me from the command line please.\n" );
}
@@ -175,13 +173,14 @@ if( $out ) {
/**
* This file was automatically generated -- do not edit!
* Run UtfNormalGenerate.php to create this file again (make clean && make)
+ *
+ * @file
*/
-/** */
-global \$utfCombiningClass, \$utfCanonicalComp, \$utfCanonicalDecomp, \$utfCheckNFC;
-\$utfCombiningClass = unserialize( '$serCombining' );
-\$utfCanonicalComp = unserialize( '$serComp' );
-\$utfCanonicalDecomp = unserialize( '$serCanon' );
-\$utfCheckNFC = unserialize( '$serCheckNFC' );
+
+UtfNormal::\$utfCombiningClass = unserialize( '$serCombining' );
+UtfNormal::\$utfCanonicalComp = unserialize( '$serComp' );
+UtfNormal::\$utfCanonicalDecomp = unserialize( '$serCanon' );
+UtfNormal::\$utfCheckNFC = unserialize( '$serCheckNFC' );
\n";
fputs( $out, $outdata );
fclose( $out );
@@ -199,10 +198,11 @@ if( $out ) {
/**
* This file was automatically generated -- do not edit!
* Run UtfNormalGenerate.php to create this file again (make clean && make)
+ *
+ * @file
*/
-/** */
-global \$utfCompatibilityDecomp;
-\$utfCompatibilityDecomp = unserialize( '$serCompat' );
+
+UtfNormal::\$utfCompatibilityDecomp = unserialize( '$serCompat' );
\n";
fputs( $out, $outdata );
fclose( $out );
diff --git a/includes/normal/UtfNormalTest.php b/includes/normal/UtfNormalTest.php
index ee1da4d0..f78775ce 100644
--- a/includes/normal/UtfNormalTest.php
+++ b/includes/normal/UtfNormalTest.php
@@ -1,29 +1,30 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
* Implements the conformance test at:
* http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
* @ingroup UtfNormal
*/
-/** */
$verbose = true;
#define( 'PRETTY_UTF8', true );
@@ -87,7 +88,7 @@ while( false !== ( $line = fgets( $in ) ) ) {
$testedChars[$columns[1]] = true;
$total++;
- if( testNormals( $normalizer, $columns, $comment ) ) {
+ if( testNormals( $normalizer, $columns, $comment, $verbose ) ) {
$success++;
} else {
$failure++;
@@ -119,7 +120,7 @@ while( false !== ($line = fgets( $in ) ) ) {
}
if( empty( $testedChars[$char] ) ) {
$total++;
- if( testInvariant( $normalizer, $char, $desc ) ) {
+ if( testInvariant( $normalizer, $char, $desc, $verbose ) ) {
$success++;
} else {
$failure++;
@@ -154,17 +155,16 @@ function reportResults( &$total, &$success, &$failure ) {
return $ok;
}
-function testNormals( &$u, $c, $comment, $reportFailure = false ) {
+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;
- global $verbose;
if( $verbose && !$result && !$reportFailure ) {
print $comment;
- testNormals( $u, $c, $comment, true );
+ testNormals( $u, $c, $comment, $verbose, true );
}
return $result;
}
@@ -232,16 +232,16 @@ function testNFKD( &$u, $c, $comment, $verbose ) {
return $result;
}
-function testInvariant( &$u, $char, $desc, $reportFailure = false ) {
+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;
- global $verbose;
+
if( $verbose && !$result && !$reportFailure ) {
print $desc;
- testInvariant( $u, $char, $desc, true );
+ testInvariant( $u, $char, $desc, $verbose, true );
}
return $result;
}
diff --git a/includes/normal/UtfNormalTest2.php b/includes/normal/UtfNormalTest2.php
new file mode 100644
index 00000000..fafd5475
--- /dev/null
+++ b/includes/normal/UtfNormalTest2.php
@@ -0,0 +1,239 @@
+#!/usr/bin/php
+<?php
+/**
+ * Other tests for the unicode normalization module
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
+
+if( php_sapi_name() != '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
+ */
+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 ) {
+ global $col, $lineNo;
+ 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);
+ } else if ($c <= 0x7FF) {
+ return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
+ } else if ($c <= 0xFFFF) {
+ return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
+ . chr(0x80 | $c & 0x3F);
+ } else if ($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/normal/UtfNormalUtil.php b/includes/normal/UtfNormalUtil.php
index d772a203..0c78e5ec 100644
--- a/includes/normal/UtfNormalUtil.php
+++ b/includes/normal/UtfNormalUtil.php
@@ -1,31 +1,30 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
* Some of these functions are adapted from places in MediaWiki.
* Should probably merge them for consistency.
*
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
* @ingroup UtfNormal
- * @public
*/
-/** */
require_once dirname(__FILE__).'/UtfNormalDefines.php';
/**
diff --git a/includes/parser/CoreLinkFunctions.php b/includes/parser/CoreLinkFunctions.php
index d6d11880..913ec22b 100644
--- a/includes/parser/CoreLinkFunctions.php
+++ b/includes/parser/CoreLinkFunctions.php
@@ -1,4 +1,9 @@
<?php
+/**
+ * Link functions provided by MediaWiki core; experimental
+ *
+ * @file
+ */
/**
* Various core link functions, registered in Parser::firstCallInit()
diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php
index 8abcc04f..94949221 100644
--- a/includes/parser/CoreParserFunctions.php
+++ b/includes/parser/CoreParserFunctions.php
@@ -1,4 +1,9 @@
<?php
+/**
+ * Parser functions provided by MediaWiki core
+ *
+ * @file
+ */
/**
* Various core parser functions, registered in Parser::firstCallInit()
@@ -11,7 +16,7 @@ class CoreParserFunctions {
# Syntax for arguments (see self::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 SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
# instead of {{#int:...}})
$parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), SFH_NO_HASH );
@@ -81,7 +86,7 @@ class CoreParserFunctions {
static function intFunction( $parser, $part1 = '' /*, ... */ ) {
if ( strval( $part1 ) !== '' ) {
$args = array_slice( func_get_args(), 2 );
- $message = wfMsgGetKey( $part1, true, false, false );
+ $message = wfMsgGetKey( $part1, true, $parser->getOptions()->getUserLang(), false );
$message = wfMsgReplaceArgs( $message, $args );
$message = $parser->replaceVariables( $message ); // like $wgMessageCache->transform()
return $message;
@@ -95,7 +100,7 @@ class CoreParserFunctions {
$date = trim( $date );
- $pref = $parser->mOptions->getDateFormat();
+ $pref = $parser->getOptions()->getDateFormat();
// Specify a different default date format other than the the normal default
// iff the user has 'default' for their setting
@@ -124,8 +129,37 @@ class CoreParserFunctions {
return wfUrlencode( str_replace( ' ', '_', self::ns( $parser, $part1 ) ) );
}
- static function urlencode( $parser, $s = '' ) {
- return urlencode( $s );
+ /**
+ * urlencodes a string according to one of three patterns: (bug 22474)
+ *
+ * By default (for HTTP "query" strings), spaces are encoded as '+'.
+ * Or to encode a value for the HTTP "path", spaces are encoded as '%20'.
+ * For links to "wiki"s, or similar software, spaces are encoded as '_',
+ *
+ * @param $parser Parser object
+ * @param $s String: The text to encode.
+ * @param $arg String (optional): The type of encoding.
+ */
+ static function urlencode( $parser, $s = '', $arg = null ) {
+ static $magicWords = null;
+ if ( is_null( $magicWords ) ) {
+ $magicWords = new MagicWordArray( array( 'url_path', 'url_query', 'url_wiki' ) );
+ }
+ switch( $magicWords->matchStartToEnd( $arg ) ) {
+
+ // Encode as though it's a wiki page, '_' for ' '.
+ case 'url_wiki':
+ return wfUrlencode( str_replace( ' ', '_', $s ) );
+
+ // Encode for an HTTP Path, '%20' for ' '.
+ case 'url_path':
+ return rawurlencode( $s );
+
+ // Encode for HTTP query, '+' for ' '.
+ case 'url_query':
+ default:
+ return urlencode( $s );
+ }
}
static function lcfirst( $parser, $s = '' ) {
@@ -214,7 +248,7 @@ class CoreParserFunctions {
$user = User::newFromName( $user );
if ( $user ) {
$gender = $user->getOption( 'gender' );
- } elseif ( $parser->mOptions->getInterfaceMessage() ) {
+ } elseif ( $parser->getOptions()->getInterfaceMessage() ) {
global $wgUser;
$gender = $wgUser->getOption( 'gender' );
}
@@ -553,17 +587,14 @@ class CoreParserFunctions {
}
static function anchorencode( $parser, $text ) {
- $a = urlencode( $text );
- $a = strtr( $a, array( '%' => '.', '+' => '_' ) );
- # leave colons alone, however
- $a = str_replace( '.3A', ':', $a );
- return $a;
+ return substr( $parser->guessSectionNameFromWikiText( $text ), 1);
}
static function special( $parser, $text ) {
- $title = SpecialPage::getTitleForAlias( $text );
- if ( $title ) {
- return $title->getPrefixedText();
+ list( $page, $subpage ) = SpecialPage::resolveAliasWithSubpage( $text );
+ if ( $page ) {
+ $title = SpecialPage::getTitleFor( $page, $subpage );
+ return $title;
} else {
return wfMsgForContent( 'nosuchspecialpage' );
}
@@ -579,7 +610,7 @@ class CoreParserFunctions {
return '';
else
return( '<span class="error">' .
- wfMsg( 'duplicate-defaultsort',
+ wfMsgForContent( 'duplicate-defaultsort',
htmlspecialchars( $old ),
htmlspecialchars( $text ) ) .
'</span>' );
@@ -602,7 +633,6 @@ class CoreParserFunctions {
* Parser function to extension tag adaptor
*/
public static function tagObj( $parser, $frame, $args ) {
- $xpath = false;
if ( !count( $args ) ) {
return '';
}
@@ -617,7 +647,7 @@ class CoreParserFunctions {
$stripList = $parser->getStripList();
if ( !in_array( $tagName, $stripList ) ) {
return '<span class="error">' .
- wfMsg( 'unknown_extension_tag', $tagName ) .
+ wfMsgForContent( 'unknown_extension_tag', $tagName ) .
'</span>';
}
diff --git a/includes/parser/CoreTagHooks.php b/includes/parser/CoreTagHooks.php
index 7cc8260e..33f3c824 100644
--- a/includes/parser/CoreTagHooks.php
+++ b/includes/parser/CoreTagHooks.php
@@ -1,5 +1,14 @@
<?php
+/**
+ * Tag hooks provided by MediaWiki core
+ *
+ * @file
+ */
+/**
+ * Various tag hooks, registered in Parser::firstCallInit()
+ * @ingroup Parser
+ */
class CoreTagHooks {
static function register( $parser ) {
global $wgRawHtml, $wgUseTeX;
@@ -40,7 +49,7 @@ class CoreTagHooks {
static function math( $content, $attributes, $parser ) {
global $wgContLang;
- return $wgContLang->armourMath( MathRenderer::renderMath( $content, $attributes ) );
+ return $wgContLang->armourMath( MathRenderer::renderMath( $content, $attributes, $parser->getOptions() ) );
}
static function gallery( $content, $attributes, $parser ) {
diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php
index 602bcff3..cf510171 100644
--- a/includes/parser/DateFormatter.php
+++ b/includes/parser/DateFormatter.php
@@ -1,4 +1,9 @@
<?php
+/**
+ * Date formatter
+ *
+ * @file
+ */
/**
* Date formatter, recognises dates in plain text and formats them accoding to user preferences.
@@ -29,7 +34,7 @@ class DateFormatter
/**
* @todo document
*/
- function DateFormatter() {
+ function __construct() {
global $wgContLang;
$this->monthNames = $this->getMonthRegex();
@@ -116,6 +121,7 @@ class DateFormatter
/**
* @param $preference String: User preference
* @param $text String: Text to reformat
+ * @param $options Array: can contain 'linked' and/or 'match-whole'
*/
function reformat( $preference, $text, $options = array('linked') ) {
@@ -265,7 +271,7 @@ class DateFormatter
$isoBits[] = $bits['y'];
$isoBits[] = $bits['m'];
$isoBits[] = $bits['d'];
- $isoDate = implode( '-', $isoBits );;
+ $isoDate = implode( '-', $isoBits );
// Output is not strictly HTML (it's wikitext), but <span> is whitelisted.
$text = Html::rawElement( 'span',
diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php
index 4f382a4f..19313b80 100644
--- a/includes/parser/LinkHolderArray.php
+++ b/includes/parser/LinkHolderArray.php
@@ -1,5 +1,13 @@
<?php
-
+/**
+ * Holder of replacement pairs for wiki links
+ *
+ * @file
+ */
+
+/**
+ * @ingroup Parser
+ */
class LinkHolderArray {
var $internals = array(), $interwikis = array();
var $size = 0;
@@ -99,7 +107,7 @@ class LinkHolderArray {
function getStubThreshold() {
global $wgUser;
if ( !isset( $this->stubThreshold ) ) {
- $this->stubThreshold = $wgUser->getOption('stubthreshold');
+ $this->stubThreshold = $wgUser->getStubThreshold();
}
return $this->stubThreshold;
}
@@ -132,7 +140,7 @@ class LinkHolderArray {
global $wgContLang;
$colours = array();
- $sk = $this->parent->getOptions()->getSkin();
+ $sk = $this->parent->getOptions()->getSkin( $this->parent->mTitle );
$linkCache = LinkCache::singleton();
$output = $this->parent->getOutput();
@@ -144,12 +152,13 @@ class LinkHolderArray {
# Sort by namespace
ksort( $this->internals );
+ $linkcolour_ids = array();
+
# Generate query
$query = false;
$current = null;
foreach ( $this->internals as $ns => $entries ) {
- foreach ( $entries as $index => $entry ) {
- $key = "$ns:$index";
+ foreach ( $entries as $entry ) {
$title = $entry['title'];
$pdbk = $entry['pdbk'];
@@ -162,16 +171,19 @@ class LinkHolderArray {
# Check if it's a static known link, e.g. interwiki
if ( $title->isAlwaysKnown() ) {
$colours[$pdbk] = '';
+ } elseif ( $ns == NS_SPECIAL ) {
+ $colours[$pdbk] = 'new';
} elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
$colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
$output->addLink( $title, $id );
+ $linkcolour_ids[$id] = $pdbk;
} elseif ( $linkCache->isBadLink( $pdbk ) ) {
$colours[$pdbk] = 'new';
} else {
# Not in the link cache, add it to the query
if ( !isset( $current ) ) {
$current = $ns;
- $query = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
+ $query = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len, page_latest";
$query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN(";
} elseif ( $current != $ns ) {
$current = $ns;
@@ -191,11 +203,10 @@ class LinkHolderArray {
# Fetch data and form into an associative array
# non-existent = broken
- $linkcolour_ids = array();
- while ( $s = $dbr->fetchObject($res) ) {
+ foreach ( $res as $s ) {
$title = Title::makeTitle( $s->page_namespace, $s->page_title );
$pdbk = $title->getPrefixedDBkey();
- $linkCache->addGoodLinkObj( $s->page_id, $title, $s->page_len, $s->page_is_redirect );
+ $linkCache->addGoodLinkObj( $s->page_id, $title, $s->page_len, $s->page_is_redirect, $s->page_latest );
$output->addLink( $title, $s->page_id );
# FIXME: convoluted data flow
# The redirect status and length is passed to getLinkColour via the LinkCache
@@ -205,6 +216,8 @@ class LinkHolderArray {
$linkcolour_ids[$s->page_id] = $pdbk;
}
unset( $res );
+ }
+ if ( count($linkcolour_ids) ) {
//pass an array of page_ids to an extension
wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
}
@@ -265,10 +278,12 @@ class LinkHolderArray {
wfProfileIn( __METHOD__ );
# Make interwiki link HTML
- $sk = $this->parent->getOptions()->getSkin();
+ $sk = $this->parent->getOptions()->getSkin( $this->parent->mTitle );
+ $output = $this->parent->getOutput();
$replacePairs = array();
foreach( $this->interwikis as $key => $link ) {
$replacePairs[$key] = $sk->link( $link['title'], $link['text'] );
+ $output->addInterwikiLink( $link['title'] );
}
$replacer = new HashtableReplacer( $replacePairs, 1 );
@@ -288,30 +303,52 @@ class LinkHolderArray {
$variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders)
$output = $this->parent->getOutput();
$linkCache = LinkCache::singleton();
- $sk = $this->parent->getOptions()->getSkin();
+ $sk = $this->parent->getOptions()->getSkin( $this->parent->mTitle );
$threshold = $this->getStubThreshold();
-
- // Add variants of links to link batch
+ $titlesToBeConverted = '';
+ $titlesAttrs = array();
+
+ // Concatenate titles to a single string, thus we only need auto convert the
+ // single string to all variants. This would improve parser's performance
+ // significantly.
foreach ( $this->internals as $ns => $entries ) {
foreach ( $entries as $index => $entry ) {
- $key = "$ns:$index";
$pdbk = $entry['pdbk'];
- $title = $entry['title'];
- $titleText = $title->getText();
-
- // generate all variants of the link title text
- $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText);
-
- // if link was not found (in first query), add all variants to query
- if ( !isset($colours[$pdbk]) ){
- foreach($allTextVariants as $textVariant){
- if($textVariant != $titleText){
- $variantTitle = Title::makeTitle( $ns, $textVariant );
- if(is_null($variantTitle)) continue;
- $linkBatch->addObj( $variantTitle );
- $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
- }
+ // we only deal with new links (in its first query)
+ if ( !isset( $colours[$pdbk] ) ) {
+ $title = $entry['title'];
+ $titleText = $title->getText();
+ $titlesAttrs[] = array(
+ 'ns' => $ns,
+ 'key' => "$ns:$index",
+ 'titleText' => $titleText,
+ );
+ // separate titles with \0 because it would never appears
+ // in a valid title
+ $titlesToBeConverted .= $titleText . "\0";
+ }
+ }
+ }
+
+ // Now do the conversion and explode string to text of titles
+ $titlesAllVariants = $wgContLang->autoConvertToAllVariants( $titlesToBeConverted );
+ $allVariantsName = array_keys( $titlesAllVariants );
+ foreach ( $titlesAllVariants as &$titlesVariant ) {
+ $titlesVariant = explode( "\0", $titlesVariant );
+ }
+ $l = count( $titlesAttrs );
+ // Then add variants of links to link batch
+ for ( $i = 0; $i < $l; $i ++ ) {
+ foreach ( $allVariantsName as $variantName ) {
+ $textVariant = $titlesAllVariants[$variantName][$i];
+ extract( $titlesAttrs[$i] );
+ if($textVariant != $titleText){
+ $variantTitle = Title::makeTitle( $ns, $textVariant );
+ if( is_null( $variantTitle ) ) {
+ continue;
}
+ $linkBatch->addObj( $variantTitle );
+ $variantMap[$variantTitle->getPrefixedDBkey()][] = $titlesAttrs[$i]['key'];
}
}
}
@@ -320,7 +357,7 @@ class LinkHolderArray {
$categoryMap = array(); // maps $category_variant => $category (dbkeys)
$varCategories = array(); // category replacements oldDBkey => newDBkey
foreach( $output->getCategoryLinks() as $category ){
- $variants = $wgContLang->convertLinkToAllVariants($category);
+ $variants = $wgContLang->autoConvertToAllVariants( $category );
foreach($variants as $variant){
if($variant != $category){
$variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) );
@@ -343,7 +380,7 @@ class LinkHolderArray {
$linkcolour_ids = array();
// for each found variants, figure out link holders and replace
- while ( $s = $dbr->fetchObject($varRes) ) {
+ foreach ( $varRes as $s ) {
$variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title );
$varPdbk = $variantTitle->getPrefixedDBkey();
@@ -403,8 +440,9 @@ class LinkHolderArray {
/**
* Replace <!--LINK--> link placeholders with plain text of links
* (not HTML-formatted).
- * @param string $text
- * @return string
+ *
+ * @param $text String
+ * @return String
*/
function replaceText( $text ) {
wfProfileIn( __METHOD__ );
@@ -419,7 +457,9 @@ class LinkHolderArray {
}
/**
- * @param array $matches
+ * Callback for replaceText()
+ *
+ * @param $matches Array
* @return string
* @private
*/
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 4f672f5b..4a3aa03b 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -17,15 +17,17 @@
* <pre>
* There are five main entry points into the Parser class:
* parse()
- * produces HTML output
+ * produces HTML output
* preSaveTransform().
- * produces altered wiki markup.
+ * produces altered wiki markup.
* preprocess()
- * removes HTML comments and expands templates
- * cleanSig()
- * Cleans a signature before saving it to preferences
+ * removes HTML comments and expands templates
+ * cleanSig() / cleanSigInSig()
+ * Cleans a signature before saving it to preferences
* extractSections()
- * Extracts sections from an article for section editing
+ * Extracts sections from an article for section editing
+ * getPreloadText()
+ * Removes <noinclude> sections, and <includeonly> tags.
*
* Globals used:
* objects: $wgLang, $wgContLang
@@ -43,8 +45,7 @@
*
* @ingroup Parser
*/
-class Parser
-{
+class Parser {
/**
* Update this version number when the ParserOutput format
* changes in an incompatible way, so the parser cache
@@ -63,7 +64,7 @@ class Parser
const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F]+)
\\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sx';
- // State constants for the definition list colon extraction
+ # State constants for the definition list colon extraction
const COLON_STATE_TEXT = 0;
const COLON_STATE_TAG = 1;
const COLON_STATE_TAGSTART = 2;
@@ -73,27 +74,25 @@ class Parser
const COLON_STATE_COMMENTDASH = 6;
const COLON_STATE_COMMENTDASHDASH = 7;
- // Flags for preprocessToDom
+ # Flags for preprocessToDom
const PTD_FOR_INCLUSION = 1;
- // Allowed values for $this->mOutputType
- // Parameter to startExternalParse().
- const OT_HTML = 1;
- const OT_WIKI = 2;
- const OT_PREPROCESS = 3;
+ # Allowed values for $this->mOutputType
+ # Parameter to startExternalParse().
+ const OT_HTML = 1; # like parse()
+ const OT_WIKI = 2; # like preSaveTransform()
+ const OT_PREPROCESS = 3; # like preprocess()
const OT_MSG = 3;
+ const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
- // Marker Suffix needs to be accessible staticly.
+ # Marker Suffix needs to be accessible staticly.
const MARKER_SUFFIX = "-QINU\x7f";
- /**#@+
- * @private
- */
# Persistent:
- var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables,
- $mSubstWords, $mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerIndex,
- $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols, $mDefaultStripList,
- $mVarCache, $mConf, $mFunctionTagHooks;
+ var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables;
+ var $mSubstWords, $mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerIndex;
+ var $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols, $mDefaultStripList;
+ var $mVarCache, $mConf, $mFunctionTagHooks;
# Cleared with clearState():
@@ -101,21 +100,19 @@ class Parser
var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
var $mLinkHolders, $mLinkID;
var $mIncludeSizes, $mPPNodeCount, $mDefaultSort;
- var $mTplExpandCache; // empty-frame expansion cache
+ var $mTplExpandCache; # empty-frame expansion cache
var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
- var $mExpensiveFunctionCount; // number of expensive parser function calls
+ var $mExpensiveFunctionCount; # number of expensive parser function calls
# Temporary
# These are variables reset at least once per parse regardless of $clearState
- var $mOptions, // ParserOptions object
- $mTitle, // Title context, used for self-link rendering and similar things
- $mOutputType, // Output type, one of the OT_xxx constants
- $ot, // Shortcut alias, see setOutputType()
- $mRevisionId, // ID to display in {{REVISIONID}} tags
- $mRevisionTimestamp, // The timestamp of the specified revision ID
- $mRevIdForTs; // The revision ID which was used to fetch the timestamp
-
- /**#@-*/
+ var $mOptions; # ParserOptions object
+ var $mTitle; # Title context, used for self-link rendering and similar things
+ var $mOutputType; # Output type, one of the OT_xxx constants
+ var $ot; # Shortcut alias, see setOutputType()
+ var $mRevisionId; # ID to display in {{REVISIONID}} tags
+ var $mRevisionTimestamp; # The timestamp of the specified revision ID
+ var $mRevIdForTs; # The revision ID which was used to fetch the timestamp
/**
* Constructor
@@ -132,12 +129,12 @@ class Parser
$this->mDefaultStripList = $this->mStripList = array();
$this->mUrlProtocols = wfUrlProtocols();
$this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'.
- '[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x0a\\x0d]*?)\]/S';
+ '[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/S';
$this->mVarCache = array();
if ( isset( $conf['preprocessorClass'] ) ) {
$this->mPreprocessorClass = $conf['preprocessorClass'];
} elseif ( extension_loaded( 'domxml' ) ) {
- // PECL extension that conflicts with the core DOM extension (bug 13770)
+ # PECL extension that conflicts with the core DOM extension (bug 13770)
wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
$this->mPreprocessorClass = 'Preprocessor_Hash';
} elseif ( extension_loaded( 'dom' ) ) {
@@ -191,6 +188,7 @@ class Parser
$this->firstCallInit();
}
$this->mOutput = new ParserOutput;
+ $this->mOptions->registerWatcher( array( $this->mOutput, 'recordOption' ) );
$this->mAutonumber = 0;
$this->mLastSection = '';
$this->mDTopen = false;
@@ -213,7 +211,7 @@ class Parser
* Must not consist of all title characters, or else it will change
* the behaviour of <nowiki> in a link.
*/
- #$this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString();
+ # $this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString();
# Changed to \x7f to allow XML double-parsing -- TS
$this->mUniqPrefix = "\x7fUNIQ" . self::getRandomString();
@@ -242,51 +240,6 @@ class Parser
wfProfileOut( __METHOD__ );
}
- function setOutputType( $ot ) {
- $this->mOutputType = $ot;
- // Shortcut alias
- $this->ot = array(
- 'html' => $ot == self::OT_HTML,
- 'wiki' => $ot == self::OT_WIKI,
- 'pre' => $ot == self::OT_PREPROCESS,
- );
- }
-
- /**
- * Set the context title
- */
- function setTitle( $t ) {
- if ( !$t || $t instanceof FakeTitle ) {
- $t = Title::newFromText( 'NO TITLE' );
- }
-
- if ( strval( $t->getFragment() ) !== '' ) {
- # Strip the fragment to avoid various odd effects
- $this->mTitle = clone $t;
- $this->mTitle->setFragment( '' );
- } else {
- $this->mTitle = $t;
- }
- }
-
- /**
- * Accessor for mUniqPrefix.
- *
- * @public
- */
- function uniqPrefix() {
- if( !isset( $this->mUniqPrefix ) ) {
- // @todo Fixme: this is probably *horribly wrong*
- // LanguageConverter seems to want $wgParser's uniqPrefix, however
- // if this is called for a parser cache hit, the parser may not
- // have ever been initialized in the first place.
- // Not really sure what the heck is supposed to be going on here.
- return '';
- //throw new MWException( "Accessing uninitialized mUniqPrefix" );
- }
- return $this->mUniqPrefix;
- }
-
/**
* Convert wikitext to HTML
* Do not call this function recursively.
@@ -310,16 +263,16 @@ class Parser
wfProfileIn( __METHOD__ );
wfProfileIn( $fname );
+ $this->mOptions = $options;
if ( $clearState ) {
$this->clearState();
}
- $this->mOptions = $options;
- $this->setTitle( $title ); // Page title has to be set for the pre-processor
+ $this->setTitle( $title ); # Page title has to be set for the pre-processor
$oldRevisionId = $this->mRevisionId;
$oldRevisionTimestamp = $this->mRevisionTimestamp;
- if( $revid !== null ) {
+ if ( $revid !== null ) {
$this->mRevisionId = $revid;
$this->mRevisionTimestamp = null;
}
@@ -335,12 +288,12 @@ class Parser
$fixtags = array(
# french spaces, last one Guillemet-left
# only if there is something before the space
- '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&nbsp;\\2',
+ '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;\\2',
# french spaces, Guillemet-right
- '/(\\302\\253) /' => '\\1&nbsp;',
- '/&nbsp;(!\s*important)/' => ' \\1', #Beware of CSS magic word !important, bug #11874.
+ '/(\\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 = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
$text = $this->doBlockLevels( $text, $linestart );
@@ -364,18 +317,11 @@ class Parser
}
/**
- * A page get its title converted except:
- * a) Language conversion is globally disabled
- * b) Title convert is globally disabled
- * c) The page is a redirect page
- * d) User request with a "linkconvert" set to "no"
- * e) A "nocontentconvert" magic word has been set
- * f) A "notitleconvert" magic word has been set
- * g) User sets "noconvertlink" in his/her preference
- *
- * Note that if a user tries to set a title in a conversion
- * rule but content conversion was not done, then the parser
- * won't pick it up. This is probably expected behavior.
+ * A converted title will be provided in the output object if title and
+ * content conversion are enabled, the article text does not contain
+ * a conversion-suppressing double-underscore tag, and no
+ * {{DISPLAYTITLE:...}} is present. DISPLAYTITLE takes precedence over
+ * automatic link conversion.
*/
if ( !( $wgDisableLangConversion
|| $wgDisableTitleConversion
@@ -401,14 +347,13 @@ class Parser
$uniq_prefix = $this->mUniqPrefix;
$matches = array();
$elements = array_keys( $this->mTransparentTagHooks );
- $text = self::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix );
+ $text = $this->extractTagsAndParams( $elements, $text, $matches, $uniq_prefix );
- foreach( $matches as $marker => $data ) {
+ foreach ( $matches as $marker => $data ) {
list( $element, $content, $params, $tag ) = $data;
$tagName = strtolower( $element );
- if( isset( $this->mTransparentTagHooks[$tagName] ) ) {
- $output = call_user_func_array( $this->mTransparentTagHooks[$tagName],
- array( $content, $params, $this ) );
+ if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
+ $output = call_user_func_array( $this->mTransparentTagHooks[$tagName], array( $content, $params, $this ) );
} else {
$output = $tag;
}
@@ -418,7 +363,7 @@ class Parser
$text = Sanitizer::normalizeCharReferences( $text );
- if ( ( $wgUseTidy && $this->mOptions->mTidy ) || $wgAlwaysUseTidy ) {
+ if ( ( $wgUseTidy && $this->mOptions->getTidy() ) || $wgAlwaysUseTidy ) {
$text = MWTidy::tidy( $text );
} else {
# attempt to sanitize at least some nesting problems
@@ -460,7 +405,7 @@ class Parser
$PFreport = "Expensive parser function count: {$this->mExpensiveFunctionCount}/$wgExpensiveParserFunctionLimit\n";
$limitReport =
"NewPP limit report\n" .
- "Preprocessor node count: {$this->mPPNodeCount}/{$this->mOptions->mMaxPPNodeCount}\n" .
+ "Preprocessor node count: {$this->mPPNodeCount}/{$this->mOptions->getMaxPPNodeCount()}\n" .
"Post-expand include size: {$this->mIncludeSizes['post-expand']}/$max bytes\n" .
"Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n".
$PFreport;
@@ -484,7 +429,7 @@ class Parser
* If $frame is not provided, then template variables (e.g., {{{1}}}) within $text are not expanded
*
* @param $text String: text extension wants to have parsed
- * @param PPFrame $frame: The frame to use for expanding any template variables
+ * @param $frame PPFrame: The frame to use for expanding any template variables
*/
function recursiveTagParse( $text, $frame=false ) {
wfProfileIn( __METHOD__ );
@@ -499,13 +444,13 @@ class Parser
* Expand templates and variables in the text, producing valid, static wikitext.
* Also removes comments.
*/
- function preprocess( $text, $title, $options, $revid = null ) {
+ function preprocess( $text, Title $title, ParserOptions $options, $revid = null ) {
wfProfileIn( __METHOD__ );
+ $this->mOptions = $options;
$this->clearState();
$this->setOutputType( self::OT_PREPROCESS );
- $this->mOptions = $options;
$this->setTitle( $title );
- if( $revid !== null ) {
+ if ( $revid !== null ) {
$this->mRevisionId = $revid;
}
wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
@@ -517,20 +462,146 @@ class Parser
}
/**
+ * Process the wikitext for the ?preload= feature. (bug 5210)
+ *
+ * <noinclude>, <includeonly> etc. are parsed as for template transclusion,
+ * comments, templates, arguments, tags hooks and parser functions are untouched.
+ */
+ public function getPreloadText( $text, Title $title, ParserOptions $options ) {
+ # Parser (re)initialisation
+ $this->mOptions = $options;
+ $this->clearState();
+ $this->setOutputType( self::OT_PLAIN );
+ $this->setTitle( $title );
+
+ $flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES;
+ $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
+ $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
+ $text = $this->mStripState->unstripBoth( $text );
+ return $text;
+ }
+
+ /**
* Get a random string
*
* @private
* @static
*/
- function getRandomString() {
- return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff));
+ static private function getRandomString() {
+ return dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) );
+ }
+
+ /**
+ * Accessor for mUniqPrefix.
+ *
+ * @return String
+ */
+ public function uniqPrefix() {
+ if ( !isset( $this->mUniqPrefix ) ) {
+ # @todo Fixme: this is probably *horribly wrong*
+ # LanguageConverter seems to want $wgParser's uniqPrefix, however
+ # if this is called for a parser cache hit, the parser may not
+ # have ever been initialized in the first place.
+ # Not really sure what the heck is supposed to be going on here.
+ return '';
+ # throw new MWException( "Accessing uninitialized mUniqPrefix" );
+ }
+ return $this->mUniqPrefix;
+ }
+
+ /**
+ * Set the context title
+ */
+ function setTitle( $t ) {
+ if ( !$t || $t instanceof FakeTitle ) {
+ $t = Title::newFromText( 'NO TITLE' );
+ }
+
+ if ( strval( $t->getFragment() ) !== '' ) {
+ # Strip the fragment to avoid various odd effects
+ $this->mTitle = clone $t;
+ $this->mTitle->setFragment( '' );
+ } else {
+ $this->mTitle = $t;
+ }
+ }
+
+ /**
+ * Accessor for the Title object
+ *
+ * @return Title object
+ */
+ function getTitle() {
+ return $this->mTitle;
+ }
+
+ /**
+ * Accessor/mutator for the Title object
+ *
+ * @param $x New Title object or null to just get the current one
+ * @return Title object
+ */
+ function Title( $x = null ) {
+ return wfSetVar( $this->mTitle, $x );
+ }
+
+ /**
+ * Set the output type
+ *
+ * @param $ot Integer: new value
+ */
+ function setOutputType( $ot ) {
+ $this->mOutputType = $ot;
+ # Shortcut alias
+ $this->ot = array(
+ 'html' => $ot == self::OT_HTML,
+ 'wiki' => $ot == self::OT_WIKI,
+ 'pre' => $ot == self::OT_PREPROCESS,
+ 'plain' => $ot == self::OT_PLAIN,
+ );
}
- function &getTitle() { return $this->mTitle; }
- function getOptions() { return $this->mOptions; }
- function getRevisionId() { return $this->mRevisionId; }
- function getOutput() { return $this->mOutput; }
- function nextLinkID() { return $this->mLinkID++; }
+ /**
+ * Accessor/mutator for the output type
+ *
+ * @param $x New value or null to just get the current one
+ * @return Integer
+ */
+ function OutputType( $x = null ) {
+ return wfSetVar( $this->mOutputType, $x );
+ }
+
+ /**
+ * Get the ParserOutput object
+ *
+ * @return ParserOutput object
+ */
+ function getOutput() {
+ return $this->mOutput;
+ }
+
+ /**
+ * Get the ParserOptions object
+ *
+ * @return ParserOptions object
+ */
+ function getOptions() {
+ return $this->mOptions;
+ }
+
+ /**
+ * Accessor/mutator for the ParserOptions object
+ *
+ * @param $x New value or null to just get the current one
+ * @return Current ParserOptions object
+ */
+ function Options( $x = null ) {
+ return wfSetVar( $this->mOptions, $x );
+ }
+
+ function nextLinkID() {
+ return $this->mLinkID++;
+ }
function getFunctionLang() {
global $wgLang, $wgContLang;
@@ -545,6 +616,8 @@ class Parser
/**
* Get a preprocessor object
+ *
+ * @return Preprocessor instance
*/
function getPreprocessor() {
if ( !isset( $this->mPreprocessor ) ) {
@@ -567,12 +640,13 @@ class Parser
*
* @param $elements list of element names. Comments are always extracted.
* @param $text Source text string.
+ * @param $matches Out parameter, Array: extracted tags
* @param $uniq_prefix
+ * @return String: stripped text
*
- * @public
* @static
*/
- function extractTagsAndParams($elements, $text, &$matches, $uniq_prefix = ''){
+ public function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) {
static $n = 1;
$stripped = '';
$matches = array();
@@ -583,40 +657,40 @@ class Parser
while ( $text != '' ) {
$p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
$stripped .= $p[0];
- if( count( $p ) < 5 ) {
+ if ( count( $p ) < 5 ) {
break;
}
- if( count( $p ) > 5 ) {
- // comment
+ if ( count( $p ) > 5 ) {
+ # comment
$element = $p[4];
$attributes = '';
$close = '';
$inside = $p[5];
} else {
- // tag
+ # tag
$element = $p[1];
$attributes = $p[2];
$close = $p[3];
$inside = $p[4];
}
- $marker = "$uniq_prefix-$element-" . sprintf('%08X', $n++) . self::MARKER_SUFFIX;
+ $marker = "$uniq_prefix-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
$stripped .= $marker;
if ( $close === '/>' ) {
- // Empty element tag, <tag />
+ # Empty element tag, <tag />
$content = null;
$text = $inside;
$tail = null;
} else {
- if( $element === '!--' ) {
+ if ( $element === '!--' ) {
$end = '/(-->)/';
} else {
$end = "/(<\\/$element\\s*>)/i";
}
$q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
$content = $q[0];
- if( count( $q ) < 3 ) {
+ if ( count( $q ) < 3 ) {
# No end tag -- let it run out to the end of the text.
$tail = '';
$text = '';
@@ -644,7 +718,7 @@ class Parser
/**
* @deprecated use replaceVariables
*/
- function strip( $text, $state, $stripcomments = false , $dontstrip = array () ) {
+ function strip( $text, $state, $stripcomments = false , $dontstrip = array() ) {
return $text;
}
@@ -704,34 +778,35 @@ class Parser
*
* @private
*/
- function doTableStuff ( $text ) {
+ function doTableStuff( $text ) {
wfProfileIn( __METHOD__ );
-
+
$lines = StringUtils::explode( "\n", $text );
$out = '';
- $td_history = array (); // Is currently a td tag open?
- $last_tag_history = array (); // Save history of last lag activated (td, th or caption)
- $tr_history = array (); // Is currently a tr tag open?
- $tr_attributes = array (); // history of tr attributes
- $has_opened_tr = array(); // Did this table open a <tr> element?
- $indent_level = 0; // indent level of the table
+ $td_history = array(); # Is currently a td tag open?
+ $last_tag_history = array(); # Save history of last lag activated (td, th or caption)
+ $tr_history = array(); # Is currently a tr tag open?
+ $tr_attributes = array(); # history of tr attributes
+ $has_opened_tr = array(); # Did this table open a <tr> element?
+ $indent_level = 0; # indent level of the table
foreach ( $lines as $outLine ) {
$line = trim( $outLine );
- if( $line == '' ) { // empty line, go to next line
+ if ( $line === '' ) { # empty line, go to next line
$out .= $outLine."\n";
continue;
}
+
$first_character = $line[0];
$matches = array();
if ( preg_match( '/^(:*)\{\|(.*)$/', $line , $matches ) ) {
- // First check if we are starting a new table
+ # First check if we are starting a new table
$indent_level = strlen( $matches[1] );
$attributes = $this->mStripState->unstripBoth( $matches[2] );
- $attributes = Sanitizer::fixTagAttributes ( $attributes , 'table' );
+ $attributes = Sanitizer::fixTagAttributes( $attributes , 'table' );
$outLine = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>";
array_push( $td_history , false );
@@ -739,156 +814,152 @@ class Parser
array_push( $tr_history , false );
array_push( $tr_attributes , '' );
array_push( $has_opened_tr , false );
- } else if ( count ( $td_history ) == 0 ) {
- // Don't do any of the following
+ } elseif ( count( $td_history ) == 0 ) {
+ # Don't do any of the following
$out .= $outLine."\n";
continue;
- } else if ( substr ( $line , 0 , 2 ) === '|}' ) {
- // We are ending a table
- $line = '</table>' . substr ( $line , 2 );
- $last_tag = array_pop ( $last_tag_history );
+ } elseif ( substr( $line , 0 , 2 ) === '|}' ) {
+ # We are ending a table
+ $line = '</table>' . substr( $line , 2 );
+ $last_tag = array_pop( $last_tag_history );
- if ( !array_pop ( $has_opened_tr ) ) {
+ if ( !array_pop( $has_opened_tr ) ) {
$line = "<tr><td></td></tr>{$line}";
}
- if ( array_pop ( $tr_history ) ) {
+ if ( array_pop( $tr_history ) ) {
$line = "</tr>{$line}";
}
- if ( array_pop ( $td_history ) ) {
+ if ( array_pop( $td_history ) ) {
$line = "</{$last_tag}>{$line}";
}
- array_pop ( $tr_attributes );
+ array_pop( $tr_attributes );
$outLine = $line . str_repeat( '</dd></dl>' , $indent_level );
- } else if ( substr ( $line , 0 , 2 ) === '|-' ) {
- // Now we have a table row
+ } elseif ( substr( $line , 0 , 2 ) === '|-' ) {
+ # Now we have a table row
$line = preg_replace( '#^\|-+#', '', $line );
- // Whats after the tag is now only attributes
+ # Whats after the tag is now only attributes
$attributes = $this->mStripState->unstripBoth( $line );
$attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
array_pop( $tr_attributes );
array_push( $tr_attributes, $attributes );
$line = '';
- $last_tag = array_pop ( $last_tag_history );
- array_pop ( $has_opened_tr );
- array_push ( $has_opened_tr , true );
+ $last_tag = array_pop( $last_tag_history );
+ array_pop( $has_opened_tr );
+ array_push( $has_opened_tr , true );
- if ( array_pop ( $tr_history ) ) {
+ if ( array_pop( $tr_history ) ) {
$line = '</tr>';
}
- if ( array_pop ( $td_history ) ) {
+ if ( array_pop( $td_history ) ) {
$line = "</{$last_tag}>{$line}";
}
$outLine = $line;
- array_push ( $tr_history , false );
- array_push ( $td_history , false );
- array_push ( $last_tag_history , '' );
- }
- else if ( $first_character === '|' || $first_character === '!' || substr ( $line , 0 , 2 ) === '|+' ) {
- // This might be cell elements, td, th or captions
- if ( substr ( $line , 0 , 2 ) === '|+' ) {
+ array_push( $tr_history , false );
+ array_push( $td_history , false );
+ array_push( $last_tag_history , '' );
+ } elseif ( $first_character === '|' || $first_character === '!' || substr( $line , 0 , 2 ) === '|+' ) {
+ # This might be cell elements, td, th or captions
+ if ( substr( $line , 0 , 2 ) === '|+' ) {
$first_character = '+';
- $line = substr ( $line , 1 );
+ $line = substr( $line , 1 );
}
- $line = substr ( $line , 1 );
+ $line = substr( $line , 1 );
if ( $first_character === '!' ) {
- $line = str_replace ( '!!' , '||' , $line );
+ $line = str_replace( '!!' , '||' , $line );
}
- // Split up multiple cells on the same line.
- // FIXME : This can result in improper nesting of tags processed
- // by earlier parser steps, but should avoid splitting up eg
- // attribute values containing literal "||".
+ # Split up multiple cells on the same line.
+ # FIXME : This can result in improper nesting of tags processed
+ # by earlier parser steps, but should avoid splitting up eg
+ # attribute values containing literal "||".
$cells = StringUtils::explodeMarkup( '||' , $line );
$outLine = '';
- // Loop through each table cell
- foreach ( $cells as $cell )
- {
+ # Loop through each table cell
+ foreach ( $cells as $cell ) {
$previous = '';
- if ( $first_character !== '+' )
- {
- $tr_after = array_pop ( $tr_attributes );
- if ( !array_pop ( $tr_history ) ) {
+ if ( $first_character !== '+' ) {
+ $tr_after = array_pop( $tr_attributes );
+ if ( !array_pop( $tr_history ) ) {
$previous = "<tr{$tr_after}>\n";
}
- array_push ( $tr_history , true );
- array_push ( $tr_attributes , '' );
- array_pop ( $has_opened_tr );
- array_push ( $has_opened_tr , true );
+ array_push( $tr_history , true );
+ array_push( $tr_attributes , '' );
+ array_pop( $has_opened_tr );
+ array_push( $has_opened_tr , true );
}
- $last_tag = array_pop ( $last_tag_history );
+ $last_tag = array_pop( $last_tag_history );
- if ( array_pop ( $td_history ) ) {
- $previous = "</{$last_tag}>{$previous}";
+ if ( array_pop( $td_history ) ) {
+ $previous = "</{$last_tag}>\n{$previous}";
}
if ( $first_character === '|' ) {
$last_tag = 'td';
- } else if ( $first_character === '!' ) {
+ } elseif ( $first_character === '!' ) {
$last_tag = 'th';
- } else if ( $first_character === '+' ) {
+ } elseif ( $first_character === '+' ) {
$last_tag = 'caption';
} else {
$last_tag = '';
}
- array_push ( $last_tag_history , $last_tag );
+ array_push( $last_tag_history , $last_tag );
- // A cell could contain both parameters and data
- $cell_data = explode ( '|' , $cell , 2 );
+ # A cell could contain both parameters and data
+ $cell_data = explode( '|' , $cell , 2 );
- // Bug 553: Note that a '|' inside an invalid link should not
- // be mistaken as delimiting cell parameters
+ # Bug 553: Note that a '|' inside an invalid link should not
+ # be mistaken as delimiting cell parameters
if ( strpos( $cell_data[0], '[[' ) !== false ) {
$cell = "{$previous}<{$last_tag}>{$cell}";
- } else if ( count ( $cell_data ) == 1 )
+ } elseif ( count( $cell_data ) == 1 ) {
$cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
- else {
+ } else {
$attributes = $this->mStripState->unstripBoth( $cell_data[0] );
$attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag );
$cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
}
$outLine .= $cell;
- array_push ( $td_history , true );
+ array_push( $td_history , true );
}
}
$out .= $outLine . "\n";
}
- // Closing open td, tr && table
- while ( count ( $td_history ) > 0 )
- {
- if ( array_pop ( $td_history ) ) {
+ # Closing open td, tr && table
+ while ( count( $td_history ) > 0 ) {
+ if ( array_pop( $td_history ) ) {
$out .= "</td>\n";
}
- if ( array_pop ( $tr_history ) ) {
+ if ( array_pop( $tr_history ) ) {
$out .= "</tr>\n";
}
- if ( !array_pop ( $has_opened_tr ) ) {
+ if ( !array_pop( $has_opened_tr ) ) {
$out .= "<tr><td></td></tr>\n" ;
}
$out .= "</table>\n";
}
- // Remove trailing line-ending (b/c)
+ # Remove trailing line-ending (b/c)
if ( substr( $out, -1 ) === "\n" ) {
$out = substr( $out, 0, -1 );
}
- // special case: don't return empty table
- if( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
+ # special case: don't return empty table
+ if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
$out = '';
}
@@ -914,29 +985,29 @@ class Parser
return $text ;
}
- // if $frame is provided, then use $frame for replacing any variables
- if ($frame) {
- // use frame depth to infer how include/noinclude tags should be handled
- // depth=0 means this is the top-level document; otherwise it's an included document
- if( !$frame->depth )
+ # if $frame is provided, then use $frame for replacing any variables
+ if ( $frame ) {
+ # use frame depth to infer how include/noinclude tags should be handled
+ # depth=0 means this is the top-level document; otherwise it's an included document
+ if ( !$frame->depth ) {
$flag = 0;
- else
+ } else {
$flag = Parser::PTD_FOR_INCLUSION;
+ }
$dom = $this->preprocessToDom( $text, $flag );
$text = $frame->expand( $dom );
- }
- // if $frame is not provided, then use old-style replaceVariables
- else {
+ } else {
+ # if $frame is not provided, then use old-style replaceVariables
$text = $this->replaceVariables( $text );
}
$text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ), false, array_keys( $this->mTransparentTagHooks ) );
wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
- // Tables need to come after variable replacement for things to work
- // properly; putting them before other transformations should keep
- // exciting things like link expansions from showing up in surprising
- // places.
+ # Tables need to come after variable replacement for things to work
+ # properly; putting them before other transformations should keep
+ # exciting things like link expansions from showing up in surprising
+ # places.
$text = $this->doTableStuff( $text );
$text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
@@ -944,17 +1015,17 @@ class Parser
$text = $this->doDoubleUnderscore( $text );
$text = $this->doHeadings( $text );
- if( $this->mOptions->getUseDynamicDates() ) {
+ if ( $this->mOptions->getUseDynamicDates() ) {
$df = DateFormatter::getInstance();
$text = $df->reformat( $this->mOptions->getDateFormat(), $text );
}
- $text = $this->doAllQuotes( $text );
$text = $this->replaceInternalLinks( $text );
+ $text = $this->doAllQuotes( $text );
$text = $this->replaceExternalLinks( $text );
# replaceInternalLinks may sometimes leave behind
# absolute URLs, which have to be masked to hide them from replaceExternalLinks
- $text = str_replace($this->mUniqPrefix.'NOPARSE', '', $text);
+ $text = str_replace( $this->mUniqPrefix.'NOPARSE', '', $text );
$text = $this->doMagicLinks( $text );
$text = $this->formatHeadings( $text, $origText, $isMain );
@@ -976,7 +1047,7 @@ class Parser
$urlChar = self::EXT_LINK_URL_CLASS;
$text = preg_replace_callback(
'!(?: # Start cases
- (<a.*?</a>) | # m[1]: Skip link text
+ (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
(<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
(\\b(?:$prots)$urlChar+) | # m[3]: Free external links" . '
(?:RFC|PMID)\s+([0-9]+) | # m[4]: RFC or PMID, capture number
@@ -1002,7 +1073,6 @@ class Parser
return $this->makeFreeExternalLink( $m[0] );
} elseif ( isset( $m[4] ) && $m[4] !== '' ) {
# RFC or PMID
- $CssClass = '';
if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
$keyword = 'RFC';
$urlmsg = 'rfcurl';
@@ -1015,10 +1085,10 @@ class Parser
$id = $m[4];
} else {
throw new MWException( __METHOD__.': unrecognised match type "' .
- substr($m[0], 0, 20 ) . '"' );
+ substr( $m[0], 0, 20 ) . '"' );
}
- $url = wfMsg( $urlmsg, $id);
- $sk = $this->mOptions->getSkin();
+ $url = wfMsgForContent( $urlmsg, $id);
+ $sk = $this->mOptions->getSkin( $this->mTitle );
$la = $sk->getExternalLinkAttributes( "external $CssClass" );
return "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>";
} elseif ( isset( $m[5] ) && $m[5] !== '' ) {
@@ -1047,16 +1117,16 @@ class Parser
global $wgContLang;
wfProfileIn( __METHOD__ );
- $sk = $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin( $this->mTitle );
$trail = '';
# The characters '<' and '>' (which were escaped by
# removeHTMLtags()) should not be included in
# URLs, per RFC 2396.
$m2 = array();
- if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) {
- $trail = substr($url, $m2[0][1]) . $trail;
- $url = substr($url, 0, $m2[0][1]);
+ if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
+ $trail = substr( $url, $m2[0][1] ) . $trail;
+ $url = substr( $url, 0, $m2[0][1] );
}
# Move trailing punctuation to $trail
@@ -1118,7 +1188,7 @@ class Parser
foreach ( $lines as $line ) {
$outtext .= $this->doQuotes( $line ) . "\n";
}
- $outtext = substr($outtext, 0,-1);
+ $outtext = substr( $outtext, 0,-1 );
wfProfileOut( __METHOD__ );
return $outtext;
}
@@ -1128,89 +1198,84 @@ class Parser
*/
public function doQuotes( $text ) {
$arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
- if ( count( $arr ) == 1 )
+ if ( count( $arr ) == 1 ) {
return $text;
- else
- {
+ } else {
# First, do some preliminary work. This may shift some apostrophes from
# being mark-up to being text. It also counts the number of occurrences
# of bold and italics mark-ups.
- $i = 0;
$numbold = 0;
$numitalics = 0;
- foreach ( $arr as $r )
- {
- if ( ( $i % 2 ) == 1 )
- {
+ for ( $i = 0; $i < count( $arr ); $i++ ) {
+ if ( ( $i % 2 ) == 1 ) {
# If there are ever four apostrophes, assume the first is supposed to
# be text, and the remaining three constitute mark-up for bold text.
- if ( strlen( $arr[$i] ) == 4 )
- {
+ if ( strlen( $arr[$i] ) == 4 ) {
$arr[$i-1] .= "'";
$arr[$i] = "'''";
- }
- # If there are more than 5 apostrophes in a row, assume they're all
- # text except for the last 5.
- else if ( strlen( $arr[$i] ) > 5 )
- {
+ } elseif ( strlen( $arr[$i] ) > 5 ) {
+ # If there are more than 5 apostrophes in a row, assume they're all
+ # text except for the last 5.
$arr[$i-1] .= str_repeat( "'", strlen( $arr[$i] ) - 5 );
$arr[$i] = "'''''";
}
# Count the number of occurrences of bold and italics mark-ups.
# We are not counting sequences of five apostrophes.
- if ( strlen( $arr[$i] ) == 2 ) { $numitalics++; }
- else if ( strlen( $arr[$i] ) == 3 ) { $numbold++; }
- else if ( strlen( $arr[$i] ) == 5 ) { $numitalics++; $numbold++; }
+ if ( strlen( $arr[$i] ) == 2 ) {
+ $numitalics++;
+ } elseif ( strlen( $arr[$i] ) == 3 ) {
+ $numbold++;
+ } elseif ( strlen( $arr[$i] ) == 5 ) {
+ $numitalics++;
+ $numbold++;
+ }
}
- $i++;
}
# If there is an odd number of both bold and italics, it is likely
# that one of the bold ones was meant to be an apostrophe followed
# by italics. Which one we cannot know for certain, but it is more
# likely to be one that has a single-letter word before it.
- if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) )
- {
+ if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
$i = 0;
$firstsingleletterword = -1;
$firstmultiletterword = -1;
$firstspace = -1;
- foreach ( $arr as $r )
- {
- if ( ( $i % 2 == 1 ) and ( strlen( $r ) == 3 ) )
- {
- $x1 = substr ($arr[$i-1], -1);
- $x2 = substr ($arr[$i-1], -2, 1);
- if ($x1 === ' ') {
- if ($firstspace == -1) $firstspace = $i;
- } else if ($x2 === ' ') {
- if ($firstsingleletterword == -1) $firstsingleletterword = $i;
+ foreach ( $arr as $r ) {
+ if ( ( $i % 2 == 1 ) and ( strlen( $r ) == 3 ) ) {
+ $x1 = substr( $arr[$i-1], -1 );
+ $x2 = substr( $arr[$i-1], -2, 1 );
+ if ( $x1 === ' ' ) {
+ if ( $firstspace == -1 ) {
+ $firstspace = $i;
+ }
+ } elseif ( $x2 === ' ') {
+ if ( $firstsingleletterword == -1 ) {
+ $firstsingleletterword = $i;
+ }
} else {
- if ($firstmultiletterword == -1) $firstmultiletterword = $i;
+ if ( $firstmultiletterword == -1 ) {
+ $firstmultiletterword = $i;
+ }
}
}
$i++;
}
# If there is a single-letter word, use it!
- if ($firstsingleletterword > -1)
- {
- $arr [ $firstsingleletterword ] = "''";
- $arr [ $firstsingleletterword-1 ] .= "'";
- }
- # If not, but there's a multi-letter word, use that one.
- else if ($firstmultiletterword > -1)
- {
- $arr [ $firstmultiletterword ] = "''";
- $arr [ $firstmultiletterword-1 ] .= "'";
- }
- # ... otherwise use the first one that has neither.
- # (notice that it is possible for all three to be -1 if, for example,
- # there is only one pentuple-apostrophe in the line)
- else if ($firstspace > -1)
- {
- $arr [ $firstspace ] = "''";
- $arr [ $firstspace-1 ] .= "'";
+ if ( $firstsingleletterword > -1 ) {
+ $arr[$firstsingleletterword] = "''";
+ $arr[$firstsingleletterword-1] .= "'";
+ } elseif ( $firstmultiletterword > -1 ) {
+ # If not, but there's a multi-letter word, use that one.
+ $arr[$firstmultiletterword] = "''";
+ $arr[$firstmultiletterword-1] .= "'";
+ } elseif ( $firstspace > -1 ) {
+ # ... otherwise use the first one that has neither.
+ # (notice that it is possible for all three to be -1 if, for example,
+ # there is only one pentuple-apostrophe in the line)
+ $arr[$firstspace] = "''";
+ $arr[$firstspace-1] .= "'";
}
}
@@ -1219,71 +1284,70 @@ class Parser
$buffer = '';
$state = '';
$i = 0;
- foreach ($arr as $r)
- {
- if (($i % 2) == 0)
- {
- if ($state === 'both')
+ foreach ( $arr as $r ) {
+ if ( ( $i % 2 ) == 0 ) {
+ if ( $state === 'both' ) {
$buffer .= $r;
- else
+ } else {
$output .= $r;
- }
- else
- {
- if (strlen ($r) == 2)
- {
- if ($state === 'i')
- { $output .= '</i>'; $state = ''; }
- else if ($state === 'bi')
- { $output .= '</i>'; $state = 'b'; }
- else if ($state === 'ib')
- { $output .= '</b></i><b>'; $state = 'b'; }
- else if ($state === 'both')
- { $output .= '<b><i>'.$buffer.'</i>'; $state = 'b'; }
- else # $state can be 'b' or ''
- { $output .= '<i>'; $state .= 'i'; }
- }
- else if (strlen ($r) == 3)
- {
- if ($state === 'b')
- { $output .= '</b>'; $state = ''; }
- else if ($state === 'bi')
- { $output .= '</i></b><i>'; $state = 'i'; }
- else if ($state === 'ib')
- { $output .= '</b>'; $state = 'i'; }
- else if ($state === 'both')
- { $output .= '<i><b>'.$buffer.'</b>'; $state = 'i'; }
- else # $state can be 'i' or ''
- { $output .= '<b>'; $state .= 'b'; }
}
- else if (strlen ($r) == 5)
- {
- if ($state === 'b')
- { $output .= '</b><i>'; $state = 'i'; }
- else if ($state === 'i')
- { $output .= '</i><b>'; $state = 'b'; }
- else if ($state === 'bi')
- { $output .= '</i></b>'; $state = ''; }
- else if ($state === 'ib')
- { $output .= '</b></i>'; $state = ''; }
- else if ($state === 'both')
- { $output .= '<i><b>'.$buffer.'</b></i>'; $state = ''; }
- else # ($state == '')
- { $buffer = ''; $state = 'both'; }
+ } else {
+ if ( strlen( $r ) == 2 ) {
+ if ( $state === 'i' ) {
+ $output .= '</i>'; $state = '';
+ } elseif ( $state === 'bi' ) {
+ $output .= '</i>'; $state = 'b';
+ } elseif ( $state === 'ib' ) {
+ $output .= '</b></i><b>'; $state = 'b';
+ } elseif ( $state === 'both' ) {
+ $output .= '<b><i>'.$buffer.'</i>'; $state = 'b';
+ } else { # $state can be 'b' or ''
+ $output .= '<i>'; $state .= 'i';
+ }
+ } elseif ( strlen( $r ) == 3 ) {
+ if ( $state === 'b' ) {
+ $output .= '</b>'; $state = '';
+ } elseif ( $state === 'bi' ) {
+ $output .= '</i></b><i>'; $state = 'i';
+ } elseif ( $state === 'ib' ) {
+ $output .= '</b>'; $state = 'i';
+ } elseif ( $state === 'both' ) {
+ $output .= '<i><b>'.$buffer.'</b>'; $state = 'i';
+ } else { # $state can be 'i' or ''
+ $output .= '<b>'; $state .= 'b';
+ }
+ } elseif ( strlen( $r ) == 5 ) {
+ if ( $state === 'b' ) {
+ $output .= '</b><i>'; $state = 'i';
+ } elseif ( $state === 'i' ) {
+ $output .= '</i><b>'; $state = 'b';
+ } elseif ( $state === 'bi' ) {
+ $output .= '</i></b>'; $state = '';
+ } elseif ( $state === 'ib' ) {
+ $output .= '</b></i>'; $state = '';
+ } elseif ( $state === 'both' ) {
+ $output .= '<i><b>'.$buffer.'</b></i>'; $state = '';
+ } else { # ($state == '')
+ $buffer = ''; $state = 'both';
+ }
}
}
$i++;
}
# Now close all remaining tags. Notice that the order is important.
- if ($state === 'b' || $state === 'ib')
+ if ( $state === 'b' || $state === 'ib' ) {
$output .= '</b>';
- if ($state === 'i' || $state === 'bi' || $state === 'ib')
+ }
+ if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
$output .= '</i>';
- if ($state === 'bi')
+ }
+ if ( $state === 'bi' ) {
$output .= '</b>';
+ }
# There might be lonely ''''', so make sure we have a buffer
- if ($state === 'both' && $buffer)
+ if ( $state === 'both' && $buffer ) {
$output .= '<b><i>'.$buffer.'</i></b>';
+ }
return $output;
}
}
@@ -1300,7 +1364,7 @@ class Parser
global $wgContLang;
wfProfileIn( __METHOD__ );
- $sk = $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin( $this->mTitle );
$bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
$s = array_shift( $bits );
@@ -1316,9 +1380,9 @@ class Parser
# removeHTMLtags()) should not be included in
# URLs, per RFC 2396.
$m2 = array();
- if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) {
- $text = substr($url, $m2[0][1]) . ' ' . $text;
- $url = substr($url, 0, $m2[0][1]);
+ if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
+ $text = substr( $url, $m2[0][1] ) . ' ' . $text;
+ $url = substr( $url, 0, $m2[0][1] );
}
# If the link text is an image URL, replace it with an <img> tag
@@ -1331,12 +1395,12 @@ class Parser
$dtrail = '';
# Set linktype for CSS - if URL==text, link is essentially free
- $linktype = ($text === $url) ? 'free' : 'text';
+ $linktype = ( $text === $url ) ? 'free' : 'text';
# No link text, e.g. [http://domain.tld/some.link]
if ( $text == '' ) {
# Autonumber if allowed. See bug #5918
- if ( strpos( wfUrlProtocols(), substr($protocol, 0, strpos($protocol, ':')) ) !== false ) {
+ if ( strpos( wfUrlProtocols(), substr( $protocol, 0, strpos( $protocol, ':' ) ) ) !== false ) {
$langObj = $this->getFunctionLang();
$text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
$linktype = 'autonumber';
@@ -1351,13 +1415,13 @@ class Parser
list( $dtrail, $trail ) = Linker::splitTrail( $trail );
}
- $text = $wgContLang->markNoConversion($text);
+ $text = $wgContLang->markNoConversion( $text );
$url = Sanitizer::cleanUrl( $url );
# Use the encoded URL
# This means that users can paste URLs directly into the text
- # Funny characters like &ouml; aren't valid in URLs anyway
+ # Funny characters like ö aren't valid in URLs anyway
# This was changed in August 2004
$s .= $sk->makeExternalLink( $url, $text, false, $linktype,
$this->getExternalLinkAttribs( $url ) ) . $dtrail . $trail;
@@ -1379,15 +1443,15 @@ class Parser
* (depending on configuration, namespace, and the URL's domain) and/or a
* target attribute (depending on configuration).
*
- * @param string $url Optional URL, to extract the domain from for rel =>
+ * @param $url String: optional URL, to extract the domain from for rel =>
* nofollow if appropriate
- * @return array Associative array of HTML attributes
+ * @return Array: associative array of HTML attributes
*/
function getExternalLinkAttribs( $url = false ) {
$attribs = array();
global $wgNoFollowLinks, $wgNoFollowNsExceptions;
$ns = $this->mTitle->getNamespace();
- if( $wgNoFollowLinks && !in_array($ns, $wgNoFollowNsExceptions) ) {
+ if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions ) ) {
$attribs['rel'] = 'nofollow';
global $wgNoFollowDomainExceptions;
@@ -1395,8 +1459,7 @@ class Parser
$bits = wfParseUrl( $url );
if ( is_array( $bits ) && isset( $bits['host'] ) ) {
foreach ( $wgNoFollowDomainExceptions as $domain ) {
- if( substr( $bits['host'], -strlen( $domain ) )
- == $domain ) {
+ if ( substr( $bits['host'], -strlen( $domain ) ) == $domain ) {
unset( $attribs['rel'] );
break;
}
@@ -1413,9 +1476,10 @@ class Parser
/**
* Replace unusual URL escape codes with their equivalent characters
- * @param string
- * @return string
- * @static
+ *
+ * @param $url String
+ * @return String
+ *
* @todo This can merge genuinely required bits in the path or query string,
* breaking legit URLs. A proper fix would treat the various parts of
* the URL differently; as a workaround, just use the output for
@@ -1429,18 +1493,16 @@ class Parser
/**
* Callback function used in replaceUnusualEscapes().
* Replaces unusual URL escape codes with their equivalent character
- * @static
- * @private
*/
private static function replaceUnusualEscapesCallback( $matches ) {
$char = urldecode( $matches[0] );
$ord = ord( $char );
- // Is it an unsafe or HTTP reserved character according to RFC 1738?
+ # Is it an unsafe or HTTP reserved character according to RFC 1738?
if ( $ord > 32 && $ord < 127 && strpos( '<>"#{}|\^~[]`;/?', $char ) === false ) {
- // No, shouldn't be escaped
+ # No, shouldn't be escaped
return $char;
} else {
- // Yes, leave it escaped
+ # Yes, leave it escaped
return $matches[0];
}
}
@@ -1451,21 +1513,21 @@ class Parser
* @private
*/
function maybeMakeExternalImage( $url ) {
- $sk = $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin( $this->mTitle );
$imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
- $imagesexception = !empty($imagesfrom);
+ $imagesexception = !empty( $imagesfrom );
$text = false;
# $imagesfrom could be either a single string or an array of strings, parse out the latter
- if( $imagesexception && is_array( $imagesfrom ) ) {
+ if ( $imagesexception && is_array( $imagesfrom ) ) {
$imagematch = false;
- foreach( $imagesfrom as $match ) {
- if( strpos( $url, $match ) === 0 ) {
+ foreach ( $imagesfrom as $match ) {
+ if ( strpos( $url, $match ) === 0 ) {
$imagematch = true;
break;
}
}
- } elseif( $imagesexception ) {
- $imagematch = (strpos( $url, $imagesfrom ) === 0);
+ } elseif ( $imagesexception ) {
+ $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
} else {
$imagematch = false;
}
@@ -1476,14 +1538,15 @@ class Parser
$text = $sk->makeExternalImage( $url );
}
}
- if( !$text && $this->mOptions->getEnableImageWhitelist()
+ if ( !$text && $this->mOptions->getEnableImageWhitelist()
&& preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
$whitelist = explode( "\n", wfMsgForContent( 'external_image_whitelist' ) );
- foreach( $whitelist as $entry ) {
+ foreach ( $whitelist as $entry ) {
# Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
- if( strpos( $entry, '#' ) === 0 || $entry === '' )
+ if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
continue;
- if( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
+ }
+ if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
# Image matches a whitelist entry
$text = $sk->makeExternalImage( $url );
break;
@@ -1495,7 +1558,7 @@ class Parser
/**
* Process [[ ]] wikilinks
- * @return processed text
+ * @return String: processed text
*
* @private
*/
@@ -1526,12 +1589,12 @@ class Parser
$e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
}
- $sk = $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin( $this->mTitle );
$holders = new LinkHolderArray( $this );
- #split the entire text string on occurences of [[
+ # split the entire text string on occurences of [[
$a = StringUtils::explode( '[[', ' ' . $s );
- #get the first element (all text up to first [[), and remove the space we added
+ # get the first element (all text up to first [[), and remove the space we added
$s = $a->current();
$a->next();
$line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
@@ -1545,7 +1608,7 @@ class Parser
$e2 = wfMsgForContent( 'linkprefix' );
}
- if( is_null( $this->mTitle ) ) {
+ if ( is_null( $this->mTitle ) ) {
wfProfileOut( __METHOD__.'-setup' );
wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
@@ -1563,10 +1626,10 @@ class Parser
$prefix = '';
}
- if($wgContLang->hasVariants()) {
- $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText());
+ if ( $wgContLang->hasVariants() ) {
+ $selflink = $wgContLang->autoConvertToAllVariants( $this->mTitle->getPrefixedText() );
} else {
- $selflink = array($this->mTitle->getPrefixedText());
+ $selflink = array( $this->mTitle->getPrefixedText() );
}
$useSubpages = $this->areSubpagesAllowed();
wfProfileOut( __METHOD__.'-setup' );
@@ -1590,7 +1653,7 @@ class Parser
$prefix='';
}
# first link
- if($first_prefix) {
+ if ( $first_prefix ) {
$prefix = $first_prefix;
$first_prefix = false;
}
@@ -1610,25 +1673,25 @@ class Parser
# Still some problems for cases where the ] is meant to be outside punctuation,
# and no image is in sight. See bug 2095.
#
- if( $text !== '' &&
+ if ( $text !== '' &&
substr( $m[3], 0, 1 ) === ']' &&
- strpos($text, '[') !== false
+ strpos( $text, '[' ) !== false
)
{
$text .= ']'; # so that replaceExternalLinks($text) works later
$m[3] = substr( $m[3], 1 );
}
# fix up urlencoded title texts
- if( strpos( $m[1], '%' ) !== false ) {
+ if ( strpos( $m[1], '%' ) !== false ) {
# Should anchors '#' also be rejected?
- $m[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), urldecode($m[1]) );
+ $m[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), urldecode( $m[1] ) );
}
$trail = $m[3];
- } elseif( preg_match($e1_img, $line, $m) ) { # Invalid, but might be an image with a link in its caption
+ } elseif ( preg_match( $e1_img, $line, $m ) ) { # Invalid, but might be an image with a link in its caption
$might_be_img = true;
$text = $m[2];
if ( strpos( $m[1], '%' ) !== false ) {
- $m[1] = urldecode($m[1]);
+ $m[1] = urldecode( $m[1] );
}
$trail = "";
} else { # Invalid form; output directly
@@ -1655,8 +1718,8 @@ class Parser
$link = $m[1];
}
- $noforce = (substr( $m[1], 0, 1 ) !== ':');
- if (!$noforce) {
+ $noforce = ( substr( $m[1], 0, 1 ) !== ':' );
+ if ( !$noforce ) {
# Strip off leading ':'
$link = substr( $link, 1 );
}
@@ -1676,10 +1739,10 @@ class Parser
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
+ if ( $ns == NS_FILE && $noforce ) { # but might be an image
$found = false;
while ( true ) {
- #look at the next 'line' to see if we can close it there
+ # look at the next 'line' to see if we can close it there
$a->next();
$next_line = $a->current();
if ( $next_line === false || $next_line === null ) {
@@ -1693,24 +1756,24 @@ class Parser
$trail = $m[2];
break;
} elseif ( count( $m ) == 2 ) {
- #if there's exactly one ]] that's fine, we'll keep looking
+ # if there's exactly one ]] that's fine, we'll keep looking
$text .= "[[{$m[0]}]]{$m[1]}";
} else {
- #if $next_line is invalid too, we need look no further
+ # if $next_line is invalid too, we need look no further
$text .= '[[' . $next_line;
break;
}
}
if ( !$found ) {
# we couldn't find the end of this imageLink, so output it raw
- #but don't ignore what might be perfectly normal links in the text we've examined
+ # but don't ignore what might be perfectly normal links in the text we've examined
$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
+ } 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" );
@@ -1720,7 +1783,14 @@ class Parser
}
$wasblank = ( $text == '' );
- if ( $wasblank ) $text = $link;
+ if ( $wasblank ) {
+ $text = $link;
+ } else {
+ # Bug 4598 madness. Handle the quotes only if they come from the alternate part
+ # [[Lista d''e paise d''o munno]] -> <a href="">Lista d''e paise d''o munno</a>
+ # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']] -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
+ $text = $this->doQuotes($text);
+ }
# Link not escaped by : , create the various objects
if ( $noforce ) {
@@ -1729,8 +1799,8 @@ class Parser
wfProfileIn( __METHOD__."-interwiki" );
if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) {
$this->mOutput->addLanguageLink( $nt->getFullText() );
- $s = rtrim($s . $prefix);
- $s .= trim($trail, "\n") == '' ? '': $prefix . $trail;
+ $s = rtrim( $s . $prefix );
+ $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
wfProfileOut( __METHOD__."-interwiki" );
continue;
}
@@ -1749,7 +1819,7 @@ class Parser
# recursively parse links inside the image caption
# actually, this will parse them in any other parameters, too,
# but it might be hard to fix that, and it doesn't matter ATM
- $text = $this->replaceExternalLinks($text);
+ $text = $this->replaceExternalLinks( $text );
$holders->merge( $this->replaceInternalLinks2( $text ) );
}
# cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
@@ -1765,7 +1835,7 @@ class Parser
if ( $ns == NS_CATEGORY ) {
wfProfileIn( __METHOD__."-category" );
- $s = rtrim($s . "\n"); # bug 87
+ $s = rtrim( $s . "\n" ); # bug 87
if ( $wasblank ) {
$sortkey = $this->getDefaultSort();
@@ -1781,7 +1851,7 @@ class Parser
* Strip the whitespace Category links produce, see bug 87
* @todo We might want to use trim($tmp, "\n") here.
*/
- $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail;
+ $s .= trim( $prefix . $trail, "\n" ) == '' ? '': $prefix . $trail;
wfProfileOut( __METHOD__."-category" );
continue;
@@ -1789,8 +1859,8 @@ class Parser
}
# Self-link checking
- if( $nt->getFragment() === '' && $ns != NS_SPECIAL ) {
- if( in_array( $nt->getPrefixedText(), $selflink, true ) ) {
+ if ( $nt->getFragment() === '' && $ns != NS_SPECIAL ) {
+ if ( in_array( $nt->getPrefixedText(), $selflink, true ) ) {
$s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail );
continue;
}
@@ -1798,7 +1868,7 @@ class Parser
# NS_MEDIA is a pseudo-namespace for linking directly to a file
# FIXME: Should do batch file existence checks, see comment below
- if( $ns == NS_MEDIA ) {
+ if ( $ns == NS_MEDIA ) {
wfProfileIn( __METHOD__."-media" );
# Give extensions a chance to select the file revision for us
$skip = $time = false;
@@ -1821,7 +1891,7 @@ class Parser
#
# FIXME: isAlwaysKnown() can be expensive for file links; we should really do
# batch file existence checks for NS_FILE and NS_MEDIA
- if( $iw == '' && $nt->isAlwaysKnown() ) {
+ if ( $iw == '' && $nt->isAlwaysKnown() ) {
$this->mOutput->addLink( $nt );
$s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix );
} else {
@@ -1853,17 +1923,17 @@ class Parser
* breaking URLs in the following text without breaking trails on the
* wiki links, it's been made into a horrible function.
*
- * @param Title $nt
- * @param string $text
- * @param string $query
- * @param string $trail
- * @param string $prefix
- * @return string HTML-wikitext mix oh yuck
+ * @param $nt Title
+ * @param $text String
+ * @param $query String
+ * @param $trail String
+ * @param $prefix String
+ * @return String: HTML-wikitext mix oh yuck
*/
function makeKnownLinkHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
list( $inside, $trail ) = Linker::splitTrail( $trail );
- $sk = $this->mOptions->getSkin();
- // FIXME: use link() instead of deprecated makeKnownLinkObj()
+ $sk = $this->mOptions->getSkin( $this->mTitle );
+ # FIXME: use link() instead of deprecated makeKnownLinkObj()
$link = $sk->makeKnownLinkObj( $nt, $text, $query, $inside, $prefix );
return $this->armorLinks( $link ) . $trail;
}
@@ -1875,8 +1945,8 @@ class Parser
* Not needed quite as much as it used to be since free links are a bit
* more sensible these days. But bracketed links are still an issue.
*
- * @param string more-or-less HTML
- * @return string less-or-more HTML with NOPARSE bits
+ * @param $text String: more-or-less HTML
+ * @return String: less-or-more HTML with NOPARSE bits
*/
function armorLinks( $text ) {
return preg_replace( '/\b(' . wfUrlProtocols() . ')/',
@@ -1885,7 +1955,7 @@ class Parser
/**
* Return true if subpage links should be expanded on this page.
- * @return bool
+ * @return Boolean
*/
function areSubpagesAllowed() {
# Some namespaces don't allow subpages
@@ -1894,12 +1964,13 @@ class Parser
/**
* Handle link to subpage if necessary
- * @param string $target the source of the link
- * @param string &$text the link text, modified as necessary
+ *
+ * @param $target String: the source of the link
+ * @param &$text String: the link text, modified as necessary
* @return string the full name of the link
* @private
*/
- function maybeDoSubpageLink($target, &$text) {
+ function maybeDoSubpageLink( $target, &$text ) {
return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
}
@@ -1907,7 +1978,7 @@ class Parser
* Used by doBlockLevels()
* @private
*/
- /* private */ function closeParagraph() {
+ function closeParagraph() {
$result = '';
if ( $this->mLastSection != '' ) {
$result = '</' . $this->mLastSection . ">\n";
@@ -1916,42 +1987,64 @@ class Parser
$this->mLastSection = '';
return $result;
}
- # getCommon() returns the length of the longest common substring
- # of both arguments, starting at the beginning of both.
- #
- /* private */ function getCommon( $st1, $st2 ) {
+
+ /**
+ * getCommon() returns the length of the longest common substring
+ * of both arguments, starting at the beginning of both.
+ * @private
+ */
+ function getCommon( $st1, $st2 ) {
$fl = strlen( $st1 );
$shorter = strlen( $st2 );
- if ( $fl < $shorter ) { $shorter = $fl; }
+ if ( $fl < $shorter ) {
+ $shorter = $fl;
+ }
for ( $i = 0; $i < $shorter; ++$i ) {
- if ( $st1{$i} != $st2{$i} ) { break; }
+ if ( $st1{$i} != $st2{$i} ) {
+ break;
+ }
}
return $i;
}
- # These next three functions open, continue, and close the list
- # element appropriate to the prefix character passed into them.
- #
- /* private */ function openList( $char ) {
+
+ /**
+ * These next three functions open, continue, and close the list
+ * element appropriate to the prefix character passed into them.
+ * @private
+ */
+ function openList( $char ) {
$result = $this->closeParagraph();
- if ( '*' === $char ) { $result .= '<ul><li>'; }
- elseif ( '#' === $char ) { $result .= '<ol><li>'; }
- elseif ( ':' === $char ) { $result .= '<dl><dd>'; }
- elseif ( ';' === $char ) {
+ if ( '*' === $char ) {
+ $result .= '<ul><li>';
+ } elseif ( '#' === $char ) {
+ $result .= '<ol><li>';
+ } elseif ( ':' === $char ) {
+ $result .= '<dl><dd>';
+ } elseif ( ';' === $char ) {
$result .= '<dl><dt>';
$this->mDTopen = true;
+ } else {
+ $result = '<!-- ERR 1 -->';
}
- else { $result = '<!-- ERR 1 -->'; }
return $result;
}
- /* private */ function nextItem( $char ) {
- if ( '*' === $char || '#' === $char ) { return '</li><li>'; }
- elseif ( ':' === $char || ';' === $char ) {
+ /**
+ * TODO: document
+ * @param $char String
+ * @private
+ */
+ function nextItem( $char ) {
+ if ( '*' === $char || '#' === $char ) {
+ return '</li><li>';
+ } elseif ( ':' === $char || ';' === $char ) {
$close = '</dd>';
- if ( $this->mDTopen ) { $close = '</dt>'; }
+ if ( $this->mDTopen ) {
+ $close = '</dt>';
+ }
if ( ';' === $char ) {
$this->mDTopen = true;
return $close . '<dt>';
@@ -1963,18 +2056,26 @@ class Parser
return '<!-- ERR 2 -->';
}
- /* private */ function closeList( $char ) {
- if ( '*' === $char ) { $text = '</li></ul>'; }
- elseif ( '#' === $char ) { $text = '</li></ol>'; }
- elseif ( ':' === $char ) {
+ /**
+ * TODO: document
+ * @param $char String
+ * @private
+ */
+ function closeList( $char ) {
+ if ( '*' === $char ) {
+ $text = '</li></ul>';
+ } elseif ( '#' === $char ) {
+ $text = '</li></ol>';
+ } elseif ( ':' === $char ) {
if ( $this->mDTopen ) {
$this->mDTopen = false;
$text = '</dt></dl>';
} else {
$text = '</dd></dl>';
}
+ } else {
+ return '<!-- ERR 3 -->';
}
- else { return '<!-- ERR 3 -->'; }
return $text."\n";
}
/**#@-*/
@@ -1982,7 +2083,8 @@ class Parser
/**
* Make lists from lines starting with ':', '*', '#', etc. (DBL)
*
- * @param $linestart bool whether or not this is at the start of a line.
+ * @param $text String
+ * @param $linestart Boolean: whether or not this is at the start of a line.
* @private
* @return string the lists rendered as HTML
*/
@@ -2007,24 +2109,24 @@ class Parser
$linestart = true;
continue;
}
- // * = ul
- // # = ol
- // ; = dt
- // : = dd
+ # * = ul
+ # # = ol
+ # ; = dt
+ # : = dd
$lastPrefixLength = strlen( $lastPrefix );
- $preCloseMatch = preg_match('/<\\/pre/i', $oLine );
- $preOpenMatch = preg_match('/<pre/i', $oLine );
- // If not in a <pre> element, scan for and figure out what prefixes are there.
+ $preCloseMatch = preg_match( '/<\\/pre/i', $oLine );
+ $preOpenMatch = preg_match( '/<pre/i', $oLine );
+ # If not in a <pre> element, scan for and figure out what prefixes are there.
if ( !$this->mInPre ) {
# Multiple prefixes may abut each other for nested lists.
$prefixLength = strspn( $oLine, '*#:;' );
$prefix = substr( $oLine, 0, $prefixLength );
# eh?
- // ; and : are both from definition-lists, so they're equivalent
- // for the purposes of determining whether or not we need to open/close
- // elements.
+ # ; and : are both from definition-lists, so they're equivalent
+ # for the purposes of determining whether or not we need to open/close
+ # elements.
$prefix2 = str_replace( ';', ':', $prefix );
$t = substr( $oLine, $prefixLength );
$this->mInPre = (bool)$preOpenMatch;
@@ -2036,7 +2138,7 @@ class Parser
}
# List generation
- if( $prefixLength && $lastPrefix === $prefix2 ) {
+ if ( $prefixLength && $lastPrefix === $prefix2 ) {
# Same as the last item, so no need to deal with nesting or opening stuff
$output .= $this->nextItem( substr( $prefix, -1 ) );
$paragraphStack = false;
@@ -2047,37 +2149,37 @@ class Parser
# So we check for : in the remainder text to split up the
# title and definition, without b0rking links.
$term = $t2 = '';
- if ($this->findColonNoLinks($t, $term, $t2) !== false) {
+ if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
$t = $t2;
$output .= $term . $this->nextItem( ':' );
}
}
- } elseif( $prefixLength || $lastPrefixLength ) {
- // We need to open or close prefixes, or both.
+ } elseif ( $prefixLength || $lastPrefixLength ) {
+ # We need to open or close prefixes, or both.
# Either open or close a level...
$commonPrefixLength = $this->getCommon( $prefix, $lastPrefix );
$paragraphStack = false;
- // Close all the prefixes which aren't shared.
- while( $commonPrefixLength < $lastPrefixLength ) {
+ # Close all the prefixes which aren't shared.
+ while ( $commonPrefixLength < $lastPrefixLength ) {
$output .= $this->closeList( $lastPrefix[$lastPrefixLength-1] );
--$lastPrefixLength;
}
- // Continue the current prefix if appropriate.
+ # Continue the current prefix if appropriate.
if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
$output .= $this->nextItem( $prefix[$commonPrefixLength-1] );
}
- // Open prefixes where appropriate.
+ # Open prefixes where appropriate.
while ( $prefixLength > $commonPrefixLength ) {
$char = substr( $prefix, $commonPrefixLength, 1 );
$output .= $this->openList( $char );
if ( ';' === $char ) {
# FIXME: This is dupe of code above
- if ($this->findColonNoLinks($t, $term, $t2) !== false) {
+ if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
$t = $t2;
$output .= $term . $this->nextItem( ':' );
}
@@ -2087,11 +2189,11 @@ class Parser
$lastPrefix = $prefix2;
}
- // If we have no prefixes, go to paragraph mode.
- if( 0 == $prefixLength ) {
+ # 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
+ # XXX: use a stack for nestable elements like span, table and div
$openmatch = preg_match('/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
$closematch = preg_match(
'/(?:<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'.
@@ -2103,29 +2205,25 @@ class Parser
if ( $preOpenMatch and !$preCloseMatch ) {
$this->mInPre = true;
}
- if ( $closematch ) {
- $inBlockElem = false;
- } else {
- $inBlockElem = true;
- }
- } else if ( !$inBlockElem && !$this->mInPre ) {
- if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' or trim($t) != '' ) ) {
- // pre
- if ($this->mLastSection !== 'pre') {
+ $inBlockElem = !$closematch;
+ } elseif ( !$inBlockElem && !$this->mInPre ) {
+ if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' || trim( $t ) != '' ) ) {
+ # pre
+ if ( $this->mLastSection !== 'pre' ) {
$paragraphStack = false;
$output .= $this->closeParagraph().'<pre>';
$this->mLastSection = 'pre';
}
$t = substr( $t, 1 );
} else {
- // paragraph
- if ( trim($t) == '' ) {
+ # paragraph
+ if ( trim( $t ) === '' ) {
if ( $paragraphStack ) {
$output .= $paragraphStack.'<br />';
$paragraphStack = false;
$this->mLastSection = 'p';
} else {
- if ($this->mLastSection !== 'p' ) {
+ if ( $this->mLastSection !== 'p' ) {
$output .= $this->closeParagraph();
$this->mLastSection = '';
$paragraphStack = '<p>';
@@ -2138,7 +2236,7 @@ class Parser
$output .= $paragraphStack;
$paragraphStack = false;
$this->mLastSection = 'p';
- } else if ($this->mLastSection !== 'p') {
+ } elseif ( $this->mLastSection !== 'p' ) {
$output .= $this->closeParagraph().'<p>';
$this->mLastSection = 'p';
}
@@ -2147,11 +2245,11 @@ class Parser
}
wfProfileOut( __METHOD__."-paragraph" );
}
- // somewhere above we forget to get out of pre block (bug 785)
- if($preCloseMatch && $this->mInPre) {
+ # somewhere above we forget to get out of pre block (bug 785)
+ if ( $preCloseMatch && $this->mInPre ) {
$this->mInPre = false;
}
- if ($paragraphStack === false) {
+ if ( $paragraphStack === false ) {
$output .= $t."\n";
}
}
@@ -2171,31 +2269,32 @@ class Parser
/**
* Split up a string on ':', ignoring any occurences inside tags
* to prevent illegal overlapping.
- * @param string $str the string to split
- * @param string &$before set to everything before the ':'
- * @param string &$after set to everything after the ':'
- * return string the position of the ':', or false if none found
+ *
+ * @param $str String: the string to split
+ * @param &$before String: set to everything before the ':'
+ * @param &$after String: set to everything after the ':'
+ * return String: the position of the ':', or false if none found
*/
- function findColonNoLinks($str, &$before, &$after) {
+ function findColonNoLinks( $str, &$before, &$after ) {
wfProfileIn( __METHOD__ );
$pos = strpos( $str, ':' );
- if( $pos === false ) {
- // Nothing to find!
+ if ( $pos === false ) {
+ # Nothing to find!
wfProfileOut( __METHOD__ );
return false;
}
$lt = strpos( $str, '<' );
- if( $lt === false || $lt > $pos ) {
- // Easy; no tag nesting to worry about
+ if ( $lt === false || $lt > $pos ) {
+ # Easy; no tag nesting to worry about
$before = substr( $str, 0, $pos );
$after = substr( $str, $pos+1 );
wfProfileOut( __METHOD__ );
return $pos;
}
- // Ugly state machine to walk through avoiding tags.
+ # Ugly state machine to walk through avoiding tags.
$state = self::COLON_STATE_TEXT;
$stack = 0;
$len = strlen( $str );
@@ -2203,67 +2302,67 @@ class Parser
$c = $str{$i};
switch( $state ) {
- // (Using the number is a performance hack for common cases)
- case 0: // self::COLON_STATE_TEXT:
+ # (Using the number is a performance hack for common cases)
+ case 0: # self::COLON_STATE_TEXT:
switch( $c ) {
case "<":
- // Could be either a <start> tag or an </end> tag
+ # Could be either a <start> tag or an </end> tag
$state = self::COLON_STATE_TAGSTART;
break;
case ":":
- if( $stack == 0 ) {
- // We found it!
+ if ( $stack == 0 ) {
+ # 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.
+ # Embedded in a tag; don't break it.
break;
default:
- // Skip ahead looking for something interesting
+ # Skip ahead looking for something interesting
$colon = strpos( $str, ':', $i );
- if( $colon === false ) {
- // Nothing else interesting
+ if ( $colon === false ) {
+ # Nothing else interesting
wfProfileOut( __METHOD__ );
return false;
}
$lt = strpos( $str, '<', $i );
- if( $stack === 0 ) {
- if( $lt === false || $colon < $lt ) {
- // We found it!
+ if ( $stack === 0 ) {
+ if ( $lt === false || $colon < $lt ) {
+ # We found it!
$before = substr( $str, 0, $colon );
$after = substr( $str, $colon + 1 );
wfProfileOut( __METHOD__ );
return $i;
}
}
- if( $lt === false ) {
- // Nothing else interesting to find; abort!
- // We're nested, but there's no close tags left. Abort!
+ if ( $lt === false ) {
+ # Nothing else interesting to find; abort!
+ # We're nested, but there's no close tags left. Abort!
break 2;
}
- // Skip ahead to next tag start
+ # Skip ahead to next tag start
$i = $lt;
$state = self::COLON_STATE_TAGSTART;
}
break;
- case 1: // self::COLON_STATE_TAG:
- // In a <tag>
+ case 1: # self::COLON_STATE_TAG:
+ # In a <tag>
switch( $c ) {
case ">":
$stack++;
$state = self::COLON_STATE_TEXT;
break;
case "/":
- // Slash may be followed by >?
+ # Slash may be followed by >?
$state = self::COLON_STATE_TAGSLASH;
break;
default:
- // ignore
+ # ignore
}
break;
- case 2: // self::COLON_STATE_TAGSTART:
+ case 2: # self::COLON_STATE_TAGSTART:
switch( $c ) {
case "/":
$state = self::COLON_STATE_CLOSETAG;
@@ -2272,18 +2371,18 @@ class Parser
$state = self::COLON_STATE_COMMENT;
break;
case ">":
- // Illegal early close? This shouldn't happen D:
+ # Illegal early close? This shouldn't happen D:
$state = self::COLON_STATE_TEXT;
break;
default:
$state = self::COLON_STATE_TAG;
}
break;
- case 3: // self::COLON_STATE_CLOSETAG:
- // In a </tag>
- if( $c === ">" ) {
+ case 3: # self::COLON_STATE_CLOSETAG:
+ # In a </tag>
+ if ( $c === ">" ) {
$stack--;
- if( $stack < 0 ) {
+ if ( $stack < 0 ) {
wfDebug( __METHOD__.": Invalid input; too many close tags\n" );
wfProfileOut( __METHOD__ );
return false;
@@ -2292,28 +2391,28 @@ class Parser
}
break;
case self::COLON_STATE_TAGSLASH:
- if( $c === ">" ) {
- // Yes, a self-closed tag <blah/>
+ if ( $c === ">" ) {
+ # Yes, a self-closed tag <blah/>
$state = self::COLON_STATE_TEXT;
} else {
- // Probably we're jumping the gun, and this is an attribute
+ # Probably we're jumping the gun, and this is an attribute
$state = self::COLON_STATE_TAG;
}
break;
- case 5: // self::COLON_STATE_COMMENT:
- if( $c === "-" ) {
+ case 5: # self::COLON_STATE_COMMENT:
+ if ( $c === "-" ) {
$state = self::COLON_STATE_COMMENTDASH;
}
break;
case self::COLON_STATE_COMMENTDASH:
- if( $c === "-" ) {
+ if ( $c === "-" ) {
$state = self::COLON_STATE_COMMENTDASHDASH;
} else {
$state = self::COLON_STATE_COMMENT;
}
break;
case self::COLON_STATE_COMMENTDASHDASH:
- if( $c === ">" ) {
+ if ( $c === ">" ) {
$state = self::COLON_STATE_TEXT;
} else {
$state = self::COLON_STATE_COMMENT;
@@ -2323,8 +2422,9 @@ class Parser
throw new MWException( "State machine error in " . __METHOD__ );
}
}
- if( $stack > 0 ) {
+ if ( $stack > 0 ) {
wfDebug( __METHOD__.": Invalid input; not enough close tags (stack $stack, state $state)\n" );
+ wfProfileOut( __METHOD__ );
return false;
}
wfProfileOut( __METHOD__ );
@@ -2337,8 +2437,8 @@ class Parser
* @private
*/
function getVariableValue( $index, $frame=false ) {
- global $wgContLang, $wgSitename, $wgServer, $wgServerName;
- global $wgScriptPath, $wgStylePath;
+ global $wgContLang, $wgSitename, $wgServer;
+ global $wgArticlePath, $wgScriptPath, $wgStylePath;
/**
* Some of these require message or data lookups and can be
@@ -2442,7 +2542,7 @@ class Parser
$value = wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) );
break;
case 'talkpagename':
- if( $this->mTitle->canTalk() ) {
+ if ( $this->mTitle->canTalk() ) {
$talkPage = $this->mTitle->getTalkPage();
$value = wfEscapeWikiText( $talkPage->getPrefixedText() );
} else {
@@ -2450,7 +2550,7 @@ class Parser
}
break;
case 'talkpagenamee':
- if( $this->mTitle->canTalk() ) {
+ if ( $this->mTitle->canTalk() ) {
$talkPage = $this->mTitle->getTalkPage();
$value = $talkPage->getPrefixedUrl();
} else {
@@ -2466,62 +2566,69 @@ class Parser
$value = $subjPage->getPrefixedUrl();
break;
case 'revisionid':
- // Let the edit saving system know we should parse the page
- // *after* a revision ID has been assigned.
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned.
$this->mOutput->setFlag( 'vary-revision' );
wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision...\n" );
$value = $this->mRevisionId;
break;
case 'revisionday':
- // Let the edit saving system know we should parse the page
- // *after* a revision ID has been assigned. This is for null edits.
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
$this->mOutput->setFlag( 'vary-revision' );
wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
$value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
break;
case 'revisionday2':
- // Let the edit saving system know we should parse the page
- // *after* a revision ID has been assigned. This is for null edits.
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
$this->mOutput->setFlag( 'vary-revision' );
wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
$value = substr( $this->getRevisionTimestamp(), 6, 2 );
break;
case 'revisionmonth':
- // Let the edit saving system know we should parse the page
- // *after* a revision ID has been assigned. This is for null edits.
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
$this->mOutput->setFlag( 'vary-revision' );
wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
+ $value = substr( $this->getRevisionTimestamp(), 4, 2 );
+ break;
+ case 'revisionmonth1':
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
$value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
break;
case 'revisionyear':
- // Let the edit saving system know we should parse the page
- // *after* a revision ID has been assigned. This is for null edits.
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
$this->mOutput->setFlag( 'vary-revision' );
wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
$value = substr( $this->getRevisionTimestamp(), 0, 4 );
break;
case 'revisiontimestamp':
- // Let the edit saving system know we should parse the page
- // *after* a revision ID has been assigned. This is for null edits.
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
$this->mOutput->setFlag( 'vary-revision' );
wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
$value = $this->getRevisionTimestamp();
break;
case 'revisionuser':
- // Let the edit saving system know we should parse the page
- // *after* a revision ID has been assigned. This is for null edits.
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
$this->mOutput->setFlag( 'vary-revision' );
wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-revision...\n" );
$value = $this->getRevisionUser();
break;
case 'namespace':
- $value = str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
+ $value = str_replace( '_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
break;
case 'namespacee':
$value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
break;
case 'talkspace':
- $value = $this->mTitle->canTalk() ? str_replace('_',' ',$this->mTitle->getTalkNsText()) : '';
+ $value = $this->mTitle->canTalk() ? str_replace( '_',' ',$this->mTitle->getTalkNsText() ) : '';
break;
case 'talkspacee':
$value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
@@ -2545,8 +2652,8 @@ class Parser
$value = $wgContLang->formatNum( gmdate( 'H', $ts ), true );
break;
case 'currentweek':
- // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
- // int to remove the padding
+ # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
+ # int to remove the padding
$value = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) );
break;
case 'currentdow':
@@ -2565,8 +2672,8 @@ class Parser
$value = $wgContLang->formatNum( $localHour, true );
break;
case 'localweek':
- // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
- // int to remove the padding
+ # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
+ # int to remove the padding
$value = $wgContLang->formatNum( (int)$localWeek );
break;
case 'localdow':
@@ -2588,7 +2695,7 @@ class Parser
$value = $wgContLang->formatNum( SiteStats::pages() );
break;
case 'numberofadmins':
- $value = $wgContLang->formatNum( SiteStats::numberingroup('sysop') );
+ $value = $wgContLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
break;
case 'numberofedits':
$value = $wgContLang->formatNum( SiteStats::edits() );
@@ -2605,12 +2712,17 @@ class Parser
case 'currentversion':
$value = SpecialVersion::getVersion();
break;
+ case 'articlepath':
+ return $wgArticlePath;
case 'sitename':
return $wgSitename;
case 'server':
return $wgServer;
case 'servername':
- return $wgServerName;
+ wfSuppressWarnings(); # May give an E_WARNING in PHP < 5.3.3
+ $serverName = parse_url( $wgServer, PHP_URL_HOST );
+ wfRestoreWarnings();
+ return $serverName ? $serverName : $wgServer;
case 'scriptpath':
return $wgScriptPath;
case 'stylepath':
@@ -2618,14 +2730,15 @@ class Parser
case 'directionmark':
return $wgContLang->getDirMark();
case 'contentlanguage':
- global $wgContLanguageCode;
- return $wgContLanguageCode;
+ global $wgLanguageCode;
+ return $wgLanguageCode;
default:
$ret = null;
- if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret, &$frame ) ) )
+ if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret, &$frame ) ) ) {
return $ret;
- else
+ } else {
return null;
+ }
}
if ( $index )
@@ -2635,7 +2748,7 @@ class Parser
}
/**
- * initialise the magic variables (like CURRENTMONTHNAME) and substitution modifiers
+ * initialise the magic variables (like CURRENTMONTHNAME) and substitution modifiers
*
* @private
*/
@@ -2653,8 +2766,8 @@ class Parser
* Preprocess some wikitext and return the document tree.
* This is the ghost of replace_variables().
*
- * @param string $text The text to parse
- * @param integer flags Bitwise combination of:
+ * @param $text String: The text to parse
+ * @param $flags Integer: bitwise combination of:
* self::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being
* included. Default is to assume a direct page view.
*
@@ -2671,12 +2784,12 @@ class Parser
*
* @private
*/
- function preprocessToDom ( $text, $flags = 0 ) {
+ function preprocessToDom( $text, $flags = 0 ) {
$dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
return $dom;
}
- /*
+ /**
* Return a three-element array: leading whitespace, string contents, trailing whitespace
*/
public static function splitWhitespace( $s ) {
@@ -2702,11 +2815,11 @@ class Parser
* self::OT_PREPROCESS: templates but not extension tags
* self::OT_HTML: all templates and extension tags
*
- * @param string $tex The text to transform
- * @param PPFrame $frame Object describing the arguments passed to the template.
+ * @param $text String: the text to transform
+ * @param $frame PPFrame Object describing the arguments passed to the template.
* Arguments may also be provided as an associative array, as was the usual case before MW1.12.
* Providing arguments this way may be useful for extensions wishing to perform variable replacement explicitly.
- * @param bool $argsOnly Only do argument (triple-brace) expansion, not double-brace expansion
+ * @param $argsOnly Boolean: only do argument (triple-brace) expansion, not double-brace expansion
* @private
*/
function replaceVariables( $text, $frame = false, $argsOnly = false ) {
@@ -2720,7 +2833,7 @@ class Parser
$frame = $this->getPreprocessor()->newFrame();
} elseif ( !( $frame instanceof PPFrame ) ) {
wfDebug( __METHOD__." called using plain parameters instead of a PPFrame instance. Creating custom frame.\n" );
- $frame = $this->getPreprocessor()->newCustomFrame($frame);
+ $frame = $this->getPreprocessor()->newCustomFrame( $frame );
}
$dom = $this->preprocessToDom( $text );
@@ -2731,11 +2844,11 @@ class Parser
return $text;
}
- /// Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
+ # Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
static function createAssocArgs( $args ) {
$assocArgs = array();
$index = 1;
- foreach( $args as $arg ) {
+ foreach ( $args as $arg ) {
$eqpos = strpos( $arg, '=' );
if ( $eqpos === false ) {
$assocArgs[$index++] = $arg;
@@ -2758,15 +2871,22 @@ class Parser
* Warn the user when a parser limitation is reached
* Will warn at most once the user per limitation type
*
- * @param string $limitationType, should be one of:
- * 'expensive-parserfunction' (corresponding messages: 'expensive-parserfunction-warning', 'expensive-parserfunction-category')
- * 'post-expand-template-argument' (corresponding messages: 'post-expand-template-argument-warning', 'post-expand-template-argument-category')
- * 'post-expand-template-inclusion' (corresponding messages: 'post-expand-template-inclusion-warning', 'post-expand-template-inclusion-category')
- * @params int $current, $max When an explicit limit has been
+ * @param $limitationType String: should be one of:
+ * 'expensive-parserfunction' (corresponding messages:
+ * 'expensive-parserfunction-warning',
+ * 'expensive-parserfunction-category')
+ * 'post-expand-template-argument' (corresponding messages:
+ * 'post-expand-template-argument-warning',
+ * 'post-expand-template-argument-category')
+ * 'post-expand-template-inclusion' (corresponding messages:
+ * 'post-expand-template-inclusion-warning',
+ * 'post-expand-template-inclusion-category')
+ * @param $current Current value
+ * @param $max Maximum allowed, when an explicit limit has been
* exceeded, provide the values (optional)
*/
function limitationWarn( $limitationType, $current=null, $max=null) {
- //does no harm if $current and $max are present but are unnecessary for the message
+ # does no harm if $current and $max are present but are unnecessary for the message
$warning = wfMsgExt( "$limitationType-warning", array( 'parsemag', 'escape' ), $current, $max );
$this->mOutput->addWarning( $warning );
$this->addTrackingCategory( "$limitationType-category" );
@@ -2776,12 +2896,12 @@ class Parser
* Return the text of a template, after recursively
* replacing any variables or templates within the template.
*
- * @param array $piece The parts of the template
+ * @param $piece Array: the parts of the template
* $piece['title']: the title, i.e. the part before the |
* $piece['parts']: the parameter array
* $piece['lineStart']: whether the brace was at the start of a line
- * @param PPFrame The current frame, contains template arguments
- * @return string the text of the template
+ * @param $frame PPFrame The current frame, contains template arguments
+ * @return String: the text of the template
* @private
*/
function braceSubstitution( $piece, $frame ) {
@@ -2810,7 +2930,7 @@ class Parser
$originalTitle = $part1;
# $args is a list of argument nodes, starting from index 0, not including $part1
- $args = (null == $piece['parts']) ? array() : $piece['parts'];
+ $args = ( null == $piece['parts'] ) ? array() : $piece['parts'];
wfProfileOut( __METHOD__.'-setup' );
# SUBST
@@ -2846,8 +2966,9 @@ class Parser
$id = $this->mVariables->matchStartToEnd( $part1 );
if ( $id !== false ) {
$text = $this->getVariableValue( $id, $frame );
- if (MagicWord::getCacheTTL($id)>-1)
- $this->mOutput->mContainsOldMagic = true;
+ if ( MagicWord::getCacheTTL( $id ) > -1 ) {
+ $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
+ }
$found = true;
}
}
@@ -2928,8 +3049,8 @@ class Parser
unset( $result[0] );
}
- // Extract flags into the local scope
- // This allows callers to set flags such as nowiki, found, etc.
+ # Extract flags into the local scope
+ # This allows callers to set flags such as nowiki, found, etc.
extract( $result );
} else {
$text = $result;
@@ -2950,21 +3071,23 @@ class Parser
# Split the title into page and subpage
$subpage = '';
$part1 = $this->maybeDoSubpageLink( $part1, $subpage );
- if ($subpage !== '') {
+ if ( $subpage !== '' ) {
$ns = $this->mTitle->getNamespace();
}
$title = Title::newFromText( $part1, $ns );
if ( $title ) {
$titleText = $title->getPrefixedText();
# Check for language variants if the template is not found
- if($wgContLang->hasVariants() && $title->getArticleID() == 0){
+ if ( $wgContLang->hasVariants() && $title->getArticleID() == 0 ) {
$wgContLang->findVariantLink( $part1, $title, true );
}
# Do recursion depth check
$limit = $this->mOptions->getMaxTemplateDepth();
if ( $frame->depth >= $limit ) {
$found = true;
- $text = '<span class="error">' . wfMsgForContent( 'parser-template-recursion-depth-warning', $limit ) . '</span>';
+ $text = '<span class="error">'
+ . wfMsgForContent( 'parser-template-recursion-depth-warning', $limit )
+ . '</span>';
}
}
}
@@ -2973,15 +3096,18 @@ class Parser
if ( !$found && $title ) {
wfProfileIn( __METHOD__ . '-loadtpl' );
if ( !$title->isExternal() ) {
- if ( $title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html'] ) {
+ if ( $title->getNamespace() == NS_SPECIAL
+ && $this->mOptions->getAllowSpecialInclusion()
+ && $this->ot['html'] )
+ {
$text = SpecialPage::capturePath( $title );
if ( is_string( $text ) ) {
$found = true;
$isHTML = true;
$this->disableCache();
}
- } else if ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) {
- $found = false; //access denied
+ } elseif ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) {
+ $found = false; # access denied
wfDebug( __METHOD__.": template inclusion denied for " . $title->getPrefixedDBkey() );
} else {
list( $text, $title ) = $this->getTemplateDom( $title );
@@ -2997,13 +3123,13 @@ class Parser
$found = true;
}
} elseif ( $title->isTrans() ) {
- // Interwiki transclusion
+ # Interwiki transclusion
if ( $this->ot['html'] && !$forceRawInterwiki ) {
$text = $this->interwikiTransclude( $title, 'render' );
$isHTML = true;
} else {
$text = $this->interwikiTransclude( $title, 'raw' );
- // Preprocess it like a template
+ # Preprocess it like a template
$text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
$isChildObj = true;
}
@@ -3058,22 +3184,31 @@ class Parser
# immediately preceding headings
if ( $isHTML ) {
$text = "\n\n" . $this->insertStripItem( $text );
- }
- # Escape nowiki-style return values
- elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
+ } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
+ # Escape nowiki-style return values
$text = wfEscapeWikiText( $text );
- }
- # Bug 529: if the template begins with a table or block-level
- # element, it should be treated as beginning a new line.
- # This behaviour is somewhat controversial.
- elseif ( is_string( $text ) && !$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\*)/', $text)) /*}*/{
+ } elseif ( is_string( $text )
+ && !$piece['lineStart']
+ && preg_match( '/^(?:{\\||:|;|#|\*)/', $text ) )
+ {
+ # Bug 529: if the template begins with a table or block-level
+ # element, it should be treated as beginning a new line.
+ # This behaviour is somewhat controversial.
$text = "\n" . $text;
}
if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
# Error, oversize inclusion
- $text = "[[$originalTitle]]" .
- $this->insertStripItem( '<!-- WARNING: template omitted, post-expand include size too large -->' );
+ if ( $titleText !== false ) {
+ # Make a working, properly escaped link if possible (bug 23588)
+ $text = "[[:$titleText]]";
+ } else {
+ # This will probably not be a working link, but at least it may
+ # provide some hint of where the problem is
+ preg_replace( '/^:/', '', $originalTitle );
+ $text = "[[:$originalTitle]]";
+ }
+ $text .= $this->insertStripItem( '<!-- WARNING: template omitted, post-expand include size too large -->' );
$this->limitationWarn( 'post-expand-template-inclusion' );
}
@@ -3104,7 +3239,7 @@ class Parser
return array( $this->mTplDomCache[$titleText], $title );
}
- // Cache miss, go to the database
+ # Cache miss, go to the database
list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
if ( $text === false ) {
@@ -3115,9 +3250,9 @@ class Parser
$dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
$this->mTplDomCache[ $titleText ] = $dom;
- if (! $title->equals($cacheTitle)) {
+ if ( !$title->equals( $cacheTitle ) ) {
$this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
- array( $title->getNamespace(),$cdb = $title->getDBkey() );
+ array( $title->getNamespace(), $cdb = $title->getDBkey() );
}
return array( $dom, $title );
@@ -3127,7 +3262,7 @@ class Parser
* Fetch the unparsed text of a template and register a reference to it.
*/
function fetchTemplateAndTitle( $title ) {
- $templateCb = $this->mOptions->getTemplateCallback();
+ $templateCb = $this->mOptions->getTemplateCallback(); # Defaults to Parser::statelessFetchTemplate()
$stuff = call_user_func( $templateCb, $title, $this );
$text = $stuff['text'];
$finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
@@ -3136,11 +3271,11 @@ class Parser
$this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
}
}
- return array($text,$finalTitle);
+ return array( $text, $finalTitle );
}
function fetchTemplate( $title ) {
- $rv = $this->fetchTemplateAndTitle($title);
+ $rv = $this->fetchTemplateAndTitle( $title );
return $rv[0];
}
@@ -3153,13 +3288,13 @@ class Parser
$finalTitle = $title;
$deps = array();
- // Loop to fetch the article, with up to 1 redirect
+ # Loop to fetch the article, with up to 1 redirect
for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
# Give extensions a chance to select the revision instead
- $id = false; // Assume current
+ $id = false; # Assume current
wfRunHooks( 'BeforeParserFetchTemplateAndtitle', array( $parser, &$title, &$skip, &$id ) );
- if( $skip ) {
+ if ( $skip ) {
$text = false;
$deps[] = array(
'title' => $title,
@@ -3169,8 +3304,8 @@ class Parser
}
$rev = $id ? Revision::newFromId( $id ) : Revision::newFromTitle( $title );
$rev_id = $rev ? $rev->getId() : 0;
- // If there is no current revision, there is no page
- if( $id === false && !$rev ) {
+ # If there is no current revision, there is no page
+ if ( $id === false && !$rev ) {
$linkCache = LinkCache::singleton();
$linkCache->addBadLinkObj( $title );
}
@@ -3180,13 +3315,13 @@ class Parser
'page_id' => $title->getArticleID(),
'rev_id' => $rev_id );
- if( $rev ) {
+ if ( $rev ) {
$text = $rev->getText();
- } elseif( $title->getNamespace() == NS_MEDIAWIKI ) {
+ } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
global $wgContLang;
$message = $wgContLang->lcfirst( $title->getText() );
$text = wfMsgForContentNoTrans( $message );
- if( wfEmptyMsg( $message, $text ) ) {
+ if ( wfEmptyMsg( $message, $text ) ) {
$text = false;
break;
}
@@ -3196,7 +3331,7 @@ class Parser
if ( $text === false ) {
break;
}
- // Redirect?
+ # Redirect?
$finalTitle = $title;
$title = Title::newFromRedirect( $text );
}
@@ -3212,35 +3347,39 @@ class Parser
function interwikiTransclude( $title, $action ) {
global $wgEnableScaryTranscluding;
- if (!$wgEnableScaryTranscluding)
- return wfMsg('scarytranscludedisabled');
+ if ( !$wgEnableScaryTranscluding ) {
+ return wfMsgForContent('scarytranscludedisabled');
+ }
$url = $title->getFullUrl( "action=$action" );
- if (strlen($url) > 255)
- return wfMsg('scarytranscludetoolong');
- return $this->fetchScaryTemplateMaybeFromCache($url);
+ if ( strlen( $url ) > 255 ) {
+ return wfMsgForContent( 'scarytranscludetoolong' );
+ }
+ return $this->fetchScaryTemplateMaybeFromCache( $url );
}
- function fetchScaryTemplateMaybeFromCache($url) {
+ function fetchScaryTemplateMaybeFromCache( $url ) {
global $wgTranscludeCacheExpiry;
- $dbr = wfGetDB(DB_SLAVE);
+ $dbr = wfGetDB( DB_SLAVE );
$tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
- $obj = $dbr->selectRow('transcache', array('tc_time', 'tc_contents'),
- array('tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ) );
- if ($obj) {
+ $obj = $dbr->selectRow( 'transcache', array('tc_time', 'tc_contents' ),
+ array( 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ) );
+ if ( $obj ) {
return $obj->tc_contents;
}
- $text = Http::get($url);
- if (!$text)
- return wfMsg('scarytranscludefailed', $url);
+ $text = Http::get( $url );
+ if ( !$text ) {
+ return wfMsgForContent( 'scarytranscludefailed', $url );
+ }
- $dbw = wfGetDB(DB_MASTER);
- $dbw->replace('transcache', array('tc_url'), array(
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->replace( 'transcache', array('tc_url'), array(
'tc_url' => $url,
'tc_time' => $dbw->timestamp( time() ),
- 'tc_contents' => $text));
+ 'tc_contents' => $text)
+ );
return $text;
}
@@ -3294,21 +3433,19 @@ class Parser
* Return the text to be used for a given extension tag.
* This is the ghost of strip().
*
- * @param array $params Associative array of parameters:
+ * @param $params Associative array of parameters:
* name PPNode for the tag name
* attr PPNode for unparsed text where tag attributes are thought to be
* attributes Optional associative array of parsed attributes
* inner Contents of extension element
* noClose Original text did not have a close tag
- * @param PPFrame $frame
+ * @param $frame PPFrame
*/
function extensionSubstitution( $params, $frame ) {
- global $wgRawHtml, $wgContLang;
-
$name = $frame->expand( $params['name'] );
$attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
$content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
- $marker = "{$this->mUniqPrefix}-$name-" . sprintf('%08X', $this->mMarkerIndex++) . self::MARKER_SUFFIX;
+ $marker = "{$this->mUniqPrefix}-$name-" . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
$isFunctionTag = isset( $this->mFunctionTagHooks[strtolower($name)] ) &&
( $this->ot['html'] || $this->ot['pre'] );
@@ -3324,27 +3461,27 @@ class Parser
$attributes = $attributes + $params['attributes'];
}
- if( isset( $this->mTagHooks[$name] ) ) {
+ if ( isset( $this->mTagHooks[$name] ) ) {
# Workaround for PHP bug 35229 and similar
if ( !is_callable( $this->mTagHooks[$name] ) ) {
throw new MWException( "Tag hook for $name is not callable\n" );
}
$output = call_user_func_array( $this->mTagHooks[$name],
array( $content, $attributes, $this, $frame ) );
- } elseif( isset( $this->mFunctionTagHooks[$name] ) ) {
+ } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
list( $callback, $flags ) = $this->mFunctionTagHooks[$name];
- if( !is_callable( $callback ) )
+ if ( !is_callable( $callback ) ) {
throw new MWException( "Tag hook for $name is not callable\n" );
+ }
- $output = call_user_func_array( $callback,
- array( &$this, $frame, $content, $attributes ) );
+ $output = call_user_func_array( $callback, array( &$this, $frame, $content, $attributes ) );
} else {
$output = '<span class="error">Invalid tag extension name: ' .
htmlspecialchars( $name ) . '</span>';
}
if ( is_array( $output ) ) {
- // Extract flags to local scope (to override $markerType)
+ # Extract flags to local scope (to override $markerType)
$flags = $output;
$output = $flags[0];
unset( $flags[0] );
@@ -3368,7 +3505,7 @@ class Parser
}
}
- if( $markerType === 'none' ) {
+ if ( $markerType === 'none' ) {
return $output;
} elseif ( $markerType === 'nowiki' ) {
$this->mStripState->nowiki->setPair( $marker, $output );
@@ -3383,9 +3520,9 @@ class Parser
/**
* Increment an include size counter
*
- * @param string $type The type of expansion
- * @param integer $size The size of the text
- * @return boolean False if this inclusion would take it over the maximum, true otherwise
+ * @param $type String: the type of expansion
+ * @param $size Integer: the size of the text
+ * @return Boolean: false if this inclusion would take it over the maximum, true otherwise
*/
function incrementIncludeSize( $type, $size ) {
if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize( $type ) ) {
@@ -3399,12 +3536,12 @@ class Parser
/**
* Increment the expensive function count
*
- * @return boolean False if the limit has been exceeded
+ * @return Boolean: false if the limit has been exceeded
*/
function incrementExpensiveFunctionCount() {
global $wgExpensiveParserFunctionLimit;
$this->mExpensiveFunctionCount++;
- if($this->mExpensiveFunctionCount <= $wgExpensiveParserFunctionLimit) {
+ if ( $this->mExpensiveFunctionCount <= $wgExpensiveParserFunctionLimit ) {
return true;
}
return false;
@@ -3417,20 +3554,20 @@ class Parser
function doDoubleUnderscore( $text ) {
wfProfileIn( __METHOD__ );
- // The position of __TOC__ needs to be recorded
+ # The position of __TOC__ needs to be recorded
$mw = MagicWord::get( 'toc' );
- if( $mw->match( $text ) ) {
+ if ( $mw->match( $text ) ) {
$this->mShowToc = true;
$this->mForceTocPosition = true;
- // Set a placeholder. At the end we'll fill it in with the TOC.
+ # Set a placeholder. At the end we'll fill it in with the TOC.
$text = $mw->replace( '<!--MWTOC-->', $text, 1 );
- // Only keep the first one.
+ # Only keep the first one.
$text = $mw->replace( '', $text );
}
- // Now match and remove the rest of them
+ # Now match and remove the rest of them
$mwa = MagicWord::getDoubleUnderscoreArray();
$this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
@@ -3441,21 +3578,25 @@ class Parser
$this->mShowToc = false;
}
if ( isset( $this->mDoubleUnderscores['hiddencat'] ) && $this->mTitle->getNamespace() == NS_CATEGORY ) {
- $this->mOutput->setProperty( 'hiddencat', 'y' );
$this->addTrackingCategory( 'hidden-category-category' );
}
# (bug 8068) Allow control over whether robots index a page.
#
# FIXME (bug 14899): __INDEX__ always overrides __NOINDEX__ here! This
# is not desirable, the last one on the page should win.
- if( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
+ if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
$this->mOutput->setIndexPolicy( 'noindex' );
$this->addTrackingCategory( 'noindex-category' );
}
- if( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ){
+ if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
$this->mOutput->setIndexPolicy( 'index' );
$this->addTrackingCategory( 'index-category' );
}
+
+ # Cache all double underscores in the database
+ foreach ( $this->mDoubleUnderscores as $key => $val ) {
+ $this->mOutput->setProperty( $key, '' );
+ }
wfProfileOut( __METHOD__ );
return $text;
@@ -3464,14 +3605,17 @@ class Parser
/**
* Add a tracking category, getting the title from a system message,
* or print a debug message if the title is invalid.
- * @param $msg String message key
- * @return Bool whether the addition was successful
+ *
+ * @param $msg String: message key
+ * @return Boolean: whether the addition was successful
*/
- protected function addTrackingCategory( $msg ){
+ protected function addTrackingCategory( $msg ) {
$cat = wfMsgForContent( $msg );
# Allow tracking categories to be disabled by setting them to "-"
- if( $cat === '-' ) return false;
+ if ( $cat === '-' ) {
+ return false;
+ }
$containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
if ( $containerCategory ) {
@@ -3493,25 +3637,21 @@ class Parser
* It loops through all headlines, collects the necessary data, then splits up the
* string and re-inserts the newly formatted headlines.
*
- * @param string $text
- * @param string $origText Original, untouched wikitext
- * @param boolean $isMain
+ * @param $text String
+ * @param $origText String: original, untouched wikitext
+ * @param $isMain Boolean
* @private
*/
function formatHeadings( $text, $origText, $isMain=true ) {
global $wgMaxTocLevel, $wgContLang, $wgHtml5, $wgExperimentalHtmlIds;
$doNumberHeadings = $this->mOptions->getNumberHeadings();
- $showEditLink = $this->mOptions->getEditSection();
-
- // Do not call quickUserCan unless necessary
- if( $showEditLink && !$this->mTitle->quickUserCan( 'edit' ) ) {
- $showEditLink = 0;
- }
-
+
# Inhibit editsection links if requested in the page
- if ( isset( $this->mDoubleUnderscores['noeditsection'] ) || $this->mOptions->getIsPrintable() ) {
+ if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
$showEditLink = 0;
+ } else {
+ $showEditLink = $this->mOptions->getEditSection();
}
# Get all headlines for numbering them and adding funky stuff like [edit]
@@ -3522,7 +3662,7 @@ class Parser
# if there are fewer than 4 headlines in the article, do not show TOC
# unless it's been explicitly enabled.
$enoughToc = $this->mShowToc &&
- (($numMatches >= 4) || $this->mForceTocPosition);
+ ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
# Allow user to stipulate that a page should have a "new section"
# link added via __NEWSECTIONLINK__
@@ -3544,7 +3684,7 @@ class Parser
}
# We need this to perform operations on the HTML
- $sk = $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin( $this->mTitle );
# headline counter
$headlineCount = 0;
@@ -3557,7 +3697,6 @@ class Parser
$head = array();
$sublevelCount = array();
$levelCount = array();
- $toclevel = 0;
$level = 0;
$prevlevel = 0;
$toclevel = 0;
@@ -3571,23 +3710,23 @@ class Parser
$node = $root->getFirstChild();
$byteOffset = 0;
$tocraw = array();
+ $refers = array();
- foreach( $matches[3] as $headline ) {
+ foreach ( $matches[3] as $headline ) {
$isTemplate = false;
$titleText = false;
$sectionIndex = false;
$numbering = '';
$markerMatches = array();
- if (preg_match("/^$markerRegex/", $headline, $markerMatches)) {
+ if ( preg_match("/^$markerRegex/", $headline, $markerMatches ) ) {
$serial = $markerMatches[1];
list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
- $isTemplate = ($titleText != $baseTitleText);
- $headline = preg_replace("/^$markerRegex/", "", $headline);
+ $isTemplate = ( $titleText != $baseTitleText );
+ $headline = preg_replace( "/^$markerRegex/", "", $headline );
}
- if( $toclevel ) {
+ if ( $toclevel ) {
$prevlevel = $level;
- $prevtoclevel = $toclevel;
}
$level = $matches[1][$headlineCount];
@@ -3595,30 +3734,30 @@ class Parser
# Increase TOC level
$toclevel++;
$sublevelCount[$toclevel] = 0;
- if( $toclevel<$wgMaxTocLevel ) {
+ if ( $toclevel<$wgMaxTocLevel ) {
$prevtoclevel = $toclevel;
$toc .= $sk->tocIndent();
$numVisible++;
}
- }
- elseif ( $level < $prevlevel && $toclevel > 1 ) {
+ } elseif ( $level < $prevlevel && $toclevel > 1 ) {
# Decrease TOC level, find level to jump to
- for ($i = $toclevel; $i > 0; $i--) {
+ for ( $i = $toclevel; $i > 0; $i-- ) {
if ( $levelCount[$i] == $level ) {
# Found last matching level
$toclevel = $i;
break;
- }
- elseif ( $levelCount[$i] < $level ) {
+ } elseif ( $levelCount[$i] < $level ) {
# Found first matching level below current level
$toclevel = $i + 1;
break;
}
}
- if( $i == 0 ) $toclevel = 1;
- if( $toclevel<$wgMaxTocLevel ) {
- if($prevtoclevel < $wgMaxTocLevel) {
+ if ( $i == 0 ) {
+ $toclevel = 1;
+ }
+ if ( $toclevel<$wgMaxTocLevel ) {
+ if ( $prevtoclevel < $wgMaxTocLevel ) {
# Unindent only if the previous toc level was shown :p
$toc .= $sk->tocUnindent( $prevtoclevel - $toclevel );
$prevtoclevel = $toclevel;
@@ -3626,10 +3765,9 @@ class Parser
$toc .= $sk->tocLineEnd();
}
}
- }
- else {
+ } else {
# No change in level, end TOC line
- if( $toclevel<$wgMaxTocLevel ) {
+ if ( $toclevel<$wgMaxTocLevel ) {
$toc .= $sk->tocLineEnd();
}
}
@@ -3640,8 +3778,8 @@ class Parser
@$sublevelCount[$toclevel]++;
$dot = 0;
for( $i = 1; $i <= $toclevel; $i++ ) {
- if( !empty( $sublevelCount[$i] ) ) {
- if( $dot ) {
+ if ( !empty( $sublevelCount[$i] ) ) {
+ if ( $dot ) {
$numbering .= '.';
}
$numbering .= $wgContLang->formatNum( $sublevelCount[$i] );
@@ -3662,15 +3800,14 @@ class Parser
# Strip out HTML (other than plain <sup> and <sub>: bug 8393)
$tocline = preg_replace(
array( '#<(?!/?(sup|sub)).*?'.'>#', '#<(/?(sup|sub)).*?'.'>#' ),
- array( '', '<$1>'),
+ array( '', '<$1>' ),
$safeHeadline
);
$tocline = trim( $tocline );
# For the anchor, strip out HTML-y stuff period
$safeHeadline = preg_replace( '/<.*?'.'>/', '', $safeHeadline );
- $safeHeadline = preg_replace( '/[ _]+/', ' ', $safeHeadline );
- $safeHeadline = trim( $safeHeadline );
+ $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
# Save headline for section edit hint before it's escaped
$headlineHint = $safeHeadline;
@@ -3700,9 +3837,10 @@ class Parser
'noninitial' );
}
- # HTML names must be case-insensitively unique (bug 10721). FIXME:
- # Does this apply to Unicode characters? Because we aren't
- # handling those here.
+ # 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
+ # FIXME: We may be changing them depending on the current locale.
$arrayKey = strtolower( $safeHeadline );
if ( $legacyHeadline === false ) {
$legacyArrayKey = false;
@@ -3723,7 +3861,7 @@ class Parser
}
# Don't number the heading if it is the only one (looks silly)
- if( $doNumberHeadings && count( $matches[3] ) > 1) {
+ if ( $doNumberHeadings && count( $matches[3] ) > 1) {
# the two are different if the line contains a link
$headline = $numbering . ' ' . $headline;
}
@@ -3737,9 +3875,9 @@ class Parser
if ( $legacyHeadline !== false && $refers[$legacyArrayKey] > 1 ) {
$legacyAnchor .= '_' . $refers[$legacyArrayKey];
}
- if( $enoughToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) {
- $toc .= $sk->tocLine($anchor, $tocline,
- $numbering, $toclevel, ($isTemplate ? false : $sectionIndex));
+ if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
+ $toc .= $sk->tocLine( $anchor, $tocline,
+ $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
}
# Add the section to the section tree
@@ -3747,8 +3885,9 @@ class Parser
while ( $node && !$isTemplate ) {
if ( $node->getName() === 'h' ) {
$bits = $node->splitHeading();
- if ( $bits['i'] == $sectionIndex )
+ if ( $bits['i'] == $sectionIndex ) {
break;
+ }
}
$byteOffset += mb_strlen( $this->mStripState->unstripBoth(
$frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
@@ -3759,20 +3898,20 @@ class Parser
'level' => $level,
'line' => $tocline,
'number' => $numbering,
- 'index' => ($isTemplate ? 'T-' : '' ) . $sectionIndex,
+ 'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
'fromtitle' => $titleText,
'byteoffset' => ( $isTemplate ? null : $byteOffset ),
'anchor' => $anchor,
);
# give headline the correct <h#> tag
- if( $showEditLink && $sectionIndex !== false ) {
- if( $isTemplate ) {
+ if ( $showEditLink && $sectionIndex !== false ) {
+ if ( $isTemplate ) {
# Put a T flag in the section identifier, to indicate to extractSections()
# that sections inside <includeonly> should be counted.
- $editlink = $sk->doEditSectionLink(Title::newFromText( $titleText ), "T-$sectionIndex");
+ $editlink = $sk->doEditSectionLink( Title::newFromText( $titleText ), "T-$sectionIndex", null, $this->mOptions->getUserLang() );
} else {
- $editlink = $sk->doEditSectionLink($this->mTitle, $sectionIndex, $headlineHint);
+ $editlink = $sk->doEditSectionLink( $this->mTitle, $sectionIndex, $headlineHint, $this->mOptions->getUserLang() );
}
} else {
$editlink = '';
@@ -3787,15 +3926,15 @@ class Parser
$this->setOutputType( $oldType );
# Never ever show TOC if no headers
- if( $numVisible < 1 ) {
+ if ( $numVisible < 1 ) {
$enoughToc = false;
}
- if( $enoughToc ) {
- if( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
+ if ( $enoughToc ) {
+ if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
$toc .= $sk->tocUnindent( $prevtoclevel - 1 );
}
- $toc = $sk->tocList( $toc );
+ $toc = $sk->tocList( $toc, $this->mOptions->getUserLang() );
$this->mOutput->setTOCHTML( $toc );
}
@@ -3808,8 +3947,8 @@ class Parser
$blocks = preg_split( '/<H[1-6].*?' . '>.*?<\/H[1-6]>/i', $text );
$i = 0;
- foreach( $blocks as $block ) {
- if( $showEditLink && $headlineCount > 0 && $i == 0 && $block !== "\n" ) {
+ foreach ( $blocks as $block ) {
+ if ( $showEditLink && $headlineCount > 0 && $i == 0 && $block !== "\n" ) {
# This is the [edit] link that appears for the top block of text when
# section editing is enabled
@@ -3818,17 +3957,17 @@ class Parser
# $full .= $sk->editSectionLink(0);
}
$full .= $block;
- if( $enoughToc && !$i && $isMain && !$this->mForceTocPosition ) {
+ if ( $enoughToc && !$i && $isMain && !$this->mForceTocPosition ) {
# Top anchor now in skin
$full = $full.$toc;
}
- if( !empty( $head[$i] ) ) {
+ if ( !empty( $head[$i] ) ) {
$full .= $head[$i];
}
$i++;
}
- if( $this->mForceTocPosition ) {
+ if ( $this->mForceTocPosition ) {
return str_replace( '<!--MWTOC-->', $toc, $full );
} else {
return $full;
@@ -3836,108 +3975,17 @@ class Parser
}
/**
- * Merge $tree2 into $tree1 by replacing the section with index
- * $section in $tree1 and its descendants with the sections in $tree2.
- * Note that in the returned section tree, only the 'index' and
- * 'byteoffset' fields are guaranteed to be correct.
- * @param $tree1 array Section tree from ParserOutput::getSectons()
- * @param $tree2 array Section tree
- * @param $section int Section index
- * @param $title Title Title both section trees come from
- * @param $len2 int Length of the original wikitext for $tree2
- * @return array Merged section tree
- */
- public static function mergeSectionTrees( $tree1, $tree2, $section, $title, $len2 ) {
- global $wgContLang;
- $newTree = array();
- $targetLevel = false;
- $merged = false;
- $lastLevel = 1;
- $nextIndex = 1;
- $numbering = array( 0 );
- $titletext = $title->getPrefixedDBkey();
- foreach ( $tree1 as $s ) {
- if ( $targetLevel !== false ) {
- if ( $s['level'] <= $targetLevel )
- // We've skipped enough
- $targetLevel = false;
- else
- continue;
- }
- if ( $s['index'] != $section ||
- $s['fromtitle'] != $titletext ) {
- self::incrementNumbering( $numbering,
- $s['toclevel'], $lastLevel );
-
- // Rewrite index, byteoffset and number
- if ( $s['fromtitle'] == $titletext ) {
- $s['index'] = $nextIndex++;
- if ( $merged )
- $s['byteoffset'] += $len2;
- }
- $s['number'] = implode( '.', array_map(
- array( $wgContLang, 'formatnum' ),
- $numbering ) );
- $lastLevel = $s['toclevel'];
- $newTree[] = $s;
- } else {
- // We're at $section
- // Insert sections from $tree2 here
- foreach ( $tree2 as $s2 ) {
- // Rewrite the fields in $s2
- // before inserting it
- $s2['toclevel'] += $s['toclevel'] - 1;
- $s2['level'] += $s['level'] - 1;
- $s2['index'] = $nextIndex++;
- $s2['byteoffset'] += $s['byteoffset'];
-
- self::incrementNumbering( $numbering,
- $s2['toclevel'], $lastLevel );
- $s2['number'] = implode( '.', array_map(
- array( $wgContLang, 'formatnum' ),
- $numbering ) );
- $lastLevel = $s2['toclevel'];
- $newTree[] = $s2;
- }
- // Skip all descendants of $section in $tree1
- $targetLevel = $s['level'];
- $merged = true;
- }
- }
- return $newTree;
- }
-
- /**
- * Increment a section number. Helper function for mergeSectionTrees()
- * @param $number array Array representing a section number
- * @param $level int Current TOC level (depth)
- * @param $lastLevel int Level of previous TOC entry
- */
- private static function incrementNumbering( &$number, $level, $lastLevel ) {
- if ( $level > $lastLevel )
- $number[$level - 1] = 1;
- else if ( $level < $lastLevel ) {
- foreach ( $number as $key => $unused )
- if ( $key >= $level )
- unset( $number[$key] );
- $number[$level - 1]++;
- } else
- $number[$level - 1]++;
- }
-
- /**
* Transform wiki markup when saving a page by doing \r\n -> \n
* conversion, substitting signatures, {{subst:}} templates, etc.
*
- * @param string $text the text to transform
- * @param Title &$title the Title object for the current article
- * @param User $user the User object describing the current user
- * @param ParserOptions $options parsing options
- * @param bool $clearState whether to clear the parser state first
- * @return string the altered wiki markup
- * @public
+ * @param $text String: the text to transform
+ * @param $title Title: the Title object for the current article
+ * @param $user User: the User object describing the current user
+ * @param $options ParserOptions: parsing options
+ * @param $clearState Boolean: whether to clear the parser state first
+ * @return String: the altered wiki markup
*/
- function preSaveTransform( $text, Title $title, $user, $options, $clearState = true ) {
+ public function preSaveTransform( $text, Title $title, $user, $options, $clearState = true ) {
$this->mOptions = $options;
$this->setTitle( $title );
$this->setOutputType( self::OT_WIKI );
@@ -3962,13 +4010,11 @@ class Parser
function pstPass2( $text, $user ) {
global $wgContLang, $wgLocaltimezone;
- /* Note: This is the timestamp saved as hardcoded wikitext to
- * the database, we use $wgContLang here in order to give
- * everyone the same signature and use the default one rather
- * than the one selected in each user's preferences.
- *
- * (see also bug 12815)
- */
+ # Note: This is the timestamp saved as hardcoded wikitext to
+ # the database, we use $wgContLang here in order to give
+ # everyone the same signature and use the default one rather
+ # than the one selected in each user's preferences.
+ # (see also bug 12815)
$ts = $this->mOptions->getTimestamp();
if ( isset( $wgLocaltimezone ) ) {
$tz = $wgLocaltimezone;
@@ -3982,13 +4028,14 @@ class Parser
$ts = date( 'YmdHis', $unixts );
$tzMsg = date( 'T', $unixts ); # might vary on DST changeover!
- /* Allow translation of timezones trough wiki. date() can return
- * whatever crap the system uses, localised or not, so we cannot
- * ship premade translations.
- */
+ # Allow translation of timezones through wiki. date() can return
+ # whatever crap the system uses, localised or not, so we cannot
+ # ship premade translations.
$key = 'timezone-' . strtolower( trim( $tzMsg ) );
$value = wfMsgForContent( $key );
- if ( !wfEmptyMsg( $key, $value ) ) $tzMsg = $value;
+ if ( !wfEmptyMsg( $key, $value ) ) {
+ $tzMsg = $value;
+ }
date_default_timezone_set( $oldtz );
@@ -3998,6 +4045,9 @@ class Parser
# Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
$text = $this->replaceVariables( $text );
+ # This works almost by chance, as the replaceVariables are done before the getUserSig(),
+ # which may corrupt this parser instance via its wfMsgExt( parsemag ) call-
+
# Signatures
$sigText = $this->getUserSig( $user );
$text = strtr( $text, array(
@@ -4007,7 +4057,6 @@ class Parser
) );
# Context links: [[|name]] and [[name (context)|]]
- #
global $wgLegalTitleChars;
$tc = "[$wgLegalTitleChars]";
$nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
@@ -4045,7 +4094,10 @@ class Parser
* If you have pre-fetched the nickname or the fancySig option, you can
* specify them here to save a database query.
*
- * @param User $user
+ * @param $user User
+ * @param $nickname String: nickname to use or false to use user's default nickname
+ * @param $fancySig Boolean: whether the nicknname is the complete signature
+ * or null to use default value
* @return string
*/
function getUserSig( &$user, $nickname = false, $fancySig = null ) {
@@ -4053,21 +4105,22 @@ class Parser
$username = $user->getName();
- // If not given, retrieve from the user object.
+ # If not given, retrieve from the user object.
if ( $nickname === false )
$nickname = $user->getOption( 'nickname' );
- if ( is_null( $fancySig ) )
+ if ( is_null( $fancySig ) ) {
$fancySig = $user->getBoolOption( 'fancysig' );
+ }
$nickname = $nickname == null ? $username : $nickname;
- if( mb_strlen( $nickname ) > $wgMaxSigChars ) {
+ if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
$nickname = $username;
wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
- } elseif( $fancySig !== false ) {
+ } elseif ( $fancySig !== false ) {
# Sig. might contain markup; validate this
- if( $this->validateSig( $nickname ) !== false ) {
+ if ( $this->validateSig( $nickname ) !== false ) {
# Validated; clean up (if needed) and return it
return $this->cleanSig( $nickname, true );
} else {
@@ -4077,7 +4130,7 @@ class Parser
}
}
- // Make sure nickname doesnt get a sig in a sig
+ # Make sure nickname doesnt get a sig in a sig
$nickname = $this->cleanSigInSig( $nickname );
# If we're still here, make it a link to the user page
@@ -4093,7 +4146,7 @@ class Parser
/**
* Check that the user's signature contains no bad XML
*
- * @param string $text
+ * @param $text String
* @return mixed An expanded string, or false if invalid.
*/
function validateSig( $text ) {
@@ -4106,16 +4159,16 @@ class Parser
* 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures @see cleanSigInSig
* 2) Substitute all transclusions
*
- * @param string $text
+ * @param $text String
* @param $parsing Whether we're cleaning (preferences save) or parsing
- * @return string Signature text
+ * @return String: signature text
*/
function cleanSig( $text, $parsing = false ) {
if ( !$parsing ) {
global $wgTitle;
+ $this->mOptions = new ParserOptions;
$this->clearState();
$this->setTitle( $wgTitle );
- $this->mOptions = new ParserOptions;
$this->setOutputType = self::OT_PREPROCESS;
}
@@ -4145,8 +4198,9 @@ class Parser
/**
* Strip ~~~, ~~~~ and ~~~~~ out of signatures
- * @param string $text
- * @return string Signature text with /~{3,5}/ removed
+ *
+ * @param $text String
+ * @return String: signature text with /~{3,5}/ removed
*/
function cleanSigInSig( $text ) {
$text = preg_replace( '/~{3,5}/', '', $text );
@@ -4156,9 +4210,8 @@ class Parser
/**
* Set up some variables which are usually set up in parse()
* so that an external function can call some class members with confidence
- * @public
*/
- function startExternalParse( &$title, $options, $outputType, $clearState = true ) {
+ public function startExternalParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
$this->setTitle( $title );
$this->mOptions = $options;
$this->setOutputType( $outputType );
@@ -4170,12 +4223,11 @@ class Parser
/**
* Wrapper for preprocess()
*
- * @param string $text the text to preprocess
- * @param ParserOptions $options options
- * @return string
- * @public
+ * @param $text String: the text to preprocess
+ * @param $options ParserOptions: options
+ * @return String
*/
- function transformMsg( $text, $options ) {
+ public function transformMsg( $text, $options ) {
global $wgTitle;
static $executing = false;
@@ -4185,34 +4237,37 @@ class Parser
}
$executing = true;
- wfProfileIn(__METHOD__);
- $text = $this->preprocess( $text, $wgTitle, $options );
+ wfProfileIn( __METHOD__ );
+ $title = $wgTitle;
+ 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' );
+ }
+ $text = $this->preprocess( $text, $title, $options );
$executing = false;
- wfProfileOut(__METHOD__);
+ wfProfileOut( __METHOD__ );
return $text;
}
/**
* Create an HTML-style tag, e.g. <yourtag>special text</yourtag>
* The callback should have the following form:
- * function myParserHook( $text, $params, &$parser ) { ... }
+ * function myParserHook( $text, $params, $parser ) { ... }
*
* Transform and return $text. Use $parser for any required context, e.g. use
* $parser->getTitle() and $parser->getOptions() not $wgTitle or $wgOut->mParserOptions
*
- * @public
- *
- * @param mixed $tag The tag to use, e.g. 'hook' for <hook>
- * @param mixed $callback The callback function (and object) to use for the tag
- *
+ * @param $tag Mixed: the tag to use, e.g. 'hook' for <hook>
+ * @param $callback Mixed: the callback function (and object) to use for the tag
* @return The old value of the mTagHooks array associated with the hook
*/
- function setHook( $tag, $callback ) {
+ public function setHook( $tag, $callback ) {
$tag = strtolower( $tag );
$oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
$this->mTagHooks[$tag] = $callback;
- if( !in_array( $tag, $this->mStripList ) ) {
+ if ( !in_array( $tag, $this->mStripList ) ) {
$this->mStripList[] = $tag;
}
@@ -4251,11 +4306,9 @@ class Parser
* nowiki Wiki markup in the return value should be escaped
* isHTML The returned text is HTML, armour it against wikitext transformation
*
- * @public
- *
- * @param string $id The magic word ID
- * @param mixed $callback The callback function (and object) to use
- * @param integer $flags a combination of the following flags:
+ * @param $id String: The magic word ID
+ * @param $callback Mixed: the callback function (and object) to use
+ * @param $flags Integer: a combination of the following flags:
* SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}}
*
* SFH_OBJECT_ARGS Pass the template arguments as PPNode objects instead of text. This
@@ -4279,7 +4332,7 @@ class Parser
*
* @return The old callback function for this name, if any
*/
- function setFunctionHook( $id, $callback, $flags = 0 ) {
+ public function setFunctionHook( $id, $callback, $flags = 0 ) {
global $wgContLang;
$oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
@@ -4287,7 +4340,7 @@ class Parser
# Add to function cache
$mw = MagicWord::get( $id );
- if( !$mw )
+ if ( !$mw )
throw new MWException( __METHOD__.'() expecting a magic word identifier.' );
$synonyms = $mw->getSynonyms();
@@ -4314,7 +4367,7 @@ class Parser
/**
* Get all registered function hook identifiers
*
- * @return array
+ * @return Array
*/
function getFunctionHooks() {
return array_keys( $this->mFunctionHooks );
@@ -4331,7 +4384,7 @@ class Parser
$this->mFunctionTagHooks[$tag] : null;
$this->mFunctionTagHooks[$tag] = array( $callback, $flags );
- if( !in_array( $tag, $this->mStripList ) ) {
+ if ( !in_array( $tag, $this->mStripList ) ) {
$this->mStripList[] = $tag;
}
@@ -4351,8 +4404,9 @@ class Parser
/**
* Replace <!--LINK--> link placeholders with plain text of links
* (not HTML-formatted).
- * @param string $text
- * @return string
+ *
+ * @param $text String
+ * @return String
*/
function replaceLinkHoldersText( $text ) {
return $this->mLinkHolders->replaceText( $text );
@@ -4375,22 +4429,27 @@ class Parser
$ig->setParser( $this );
$ig->setHideBadImages();
$ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) );
- $ig->useSkin( $this->mOptions->getSkin() );
+ $ig->useSkin( $this->mOptions->getSkin( $this->mTitle ) );
$ig->mRevisionId = $this->mRevisionId;
- if( isset( $params['caption'] ) ) {
+ if ( isset( $params['showfilename'] ) ) {
+ $ig->setShowFilename( true );
+ } else {
+ $ig->setShowFilename( false );
+ }
+ if ( isset( $params['caption'] ) ) {
$caption = $params['caption'];
$caption = htmlspecialchars( $caption );
$caption = $this->replaceInternalLinks( $caption );
$ig->setCaptionHtml( $caption );
}
- if( isset( $params['perrow'] ) ) {
+ if ( isset( $params['perrow'] ) ) {
$ig->setPerRow( $params['perrow'] );
}
- if( isset( $params['widths'] ) ) {
+ if ( isset( $params['widths'] ) ) {
$ig->setWidths( $params['widths'] );
}
- if( isset( $params['heights'] ) ) {
+ if ( isset( $params['heights'] ) ) {
$ig->setHeights( $params['heights'] );
}
@@ -4407,11 +4466,12 @@ class Parser
continue;
}
- if ( strpos( $matches[0], '%' ) !== false )
+ if ( strpos( $matches[0], '%' ) !== false ) {
$matches[1] = urldecode( $matches[1] );
- $tp = Title::newFromText( $matches[1]/*, NS_FILE*/ );
+ }
+ $tp = Title::newFromText( $matches[1] );
$nt =& $tp;
- if( is_null( $nt ) ) {
+ if ( is_null( $nt ) ) {
# Bogus title. Ignore these so we don't bomb out later.
continue;
}
@@ -4440,7 +4500,7 @@ class Parser
$handlerClass = '';
}
if ( !isset( $this->mImageParams[$handlerClass] ) ) {
- // Initialise static lists
+ # Initialise static lists
static $internalParamNames = array(
'horizAlign' => array( 'left', 'right', 'center', 'none' ),
'vertAlign' => array( 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
@@ -4459,7 +4519,7 @@ class Parser
}
}
- // Add handler params
+ # Add handler params
$paramMap = $internalParamMap;
if ( $handler ) {
$handlerParamMap = $handler->getParamMap();
@@ -4475,9 +4535,10 @@ class Parser
/**
* Parse image options text and use it to make an image
- * @param Title $title
- * @param string $options
- * @param LinkHolderArray $holders
+ *
+ * @param $title Title
+ * @param $options String
+ * @param $holders LinkHolderArray
*/
function makeImage( $title, $options, $holders = false ) {
# Check if the options text is of the form "options|alt text"
@@ -4506,7 +4567,7 @@ class Parser
# * text-bottom
$parts = StringUtils::explode( "|", $options );
- $sk = $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin( $this->mTitle );
# Give extensions a chance to select the file revision for us
$skip = $time = $descQuery = false;
@@ -4517,7 +4578,6 @@ class Parser
}
# Get the file
- $imagename = $title->getDBkey();
$file = wfFindFile( $title, array( 'time' => $time ) );
# Get parameter map
$handler = $file ? $file->getHandler() : false;
@@ -4528,15 +4588,15 @@ class Parser
$caption = '';
$params = array( 'frame' => array(), 'handler' => array(),
'horizAlign' => array(), 'vertAlign' => array() );
- foreach( $parts as $part ) {
+ foreach ( $parts as $part ) {
$part = trim( $part );
list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
$validated = false;
- if( isset( $paramMap[$magicName] ) ) {
+ if ( isset( $paramMap[$magicName] ) ) {
list( $type, $paramName ) = $paramMap[$magicName];
- // Special case; width and height come in one variable together
- if( $type === 'handler' && $paramName === 'width' ) {
+ # Special case; width and height come in one variable together
+ if ( $type === 'handler' && $paramName === 'width' ) {
$m = array();
# (bug 13500) In both cases (width/height and width only),
# permit trailing "px" for backward compatibility.
@@ -4557,7 +4617,7 @@ class Parser
$params[$type]['width'] = $width;
$validated = true;
}
- } // else no validation -- bug 13436
+ } # else no validation -- bug 13436
} else {
if ( $type === 'handler' ) {
# Validate handler parameter
@@ -4567,9 +4627,9 @@ class Parser
switch( $paramName ) {
case 'manualthumb':
case 'alt':
- // @todo Fixme: possibly check validity here for
- // manualthumb? downstream behavior seems odd with
- // missing manual thumbs.
+ # @todo Fixme: possibly check validity here for
+ # manualthumb? downstream behavior seems odd with
+ # missing manual thumbs.
$validated = true;
$value = $this->stripAltText( $value, $holders );
break;
@@ -4584,6 +4644,9 @@ class Parser
if ( preg_match( "/^($prots)$chars+$/", $value, $m ) ) {
$paramName = 'link-url';
$this->mOutput->addExternalLink( $value );
+ if ( $this->mOptions->getExternalLinkTarget() ) {
+ $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
+ }
$validated = true;
}
} else {
@@ -4597,7 +4660,7 @@ class Parser
}
break;
default:
- // Most other things appear to be empty or numeric...
+ # Most other things appear to be empty or numeric...
$validated = ( $value === false || is_numeric( trim( $value ) ) );
}
}
@@ -4670,7 +4733,7 @@ class Parser
wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params ) );
# Linker does the rest
- $ret = $sk->makeImageLink2( $title, $file, $params['frame'], $params['handler'], $time, $descQuery );
+ $ret = $sk->makeImageLink2( $title, $file, $params['frame'], $params['handler'], $time, $descQuery, $this->mOptions->getThumbSize() );
# Give the handler a chance to modify the parser object
if ( $handler ) {
@@ -4705,15 +4768,17 @@ class Parser
*/
function disableCache() {
wfDebug( "Parser output marked as uncacheable.\n" );
- $this->mOutput->mCacheTime = -1;
+ $this->mOutput->setCacheTime( -1 ); // old style, for compatibility
+ $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
}
- /**#@+
+ /**
* Callback from the Sanitizer for expanding items found in HTML attribute
* values, so they can be safely tested and escaped.
- * @param string $text
- * @param PPFrame $frame
- * @return string
+ *
+ * @param $text String
+ * @param $frame PPFrame
+ * @return String
* @private
*/
function attributeStripCallback( &$text, $frame = false ) {
@@ -4722,22 +4787,12 @@ class Parser
return $text;
}
- /**#@-*/
-
- /**#@+
- * Accessor/mutator
- */
- function Title( $x = null ) { return wfSetVar( $this->mTitle, $x ); }
- function Options( $x = null ) { return wfSetVar( $this->mOptions, $x ); }
- function OutputType( $x = null ) { return wfSetVar( $this->mOutputType, $x ); }
- /**#@-*/
-
- /**#@+
+ /**
* Accessor
*/
- function getTags() { return array_merge( array_keys($this->mTransparentTagHooks), array_keys( $this->mTagHooks ) ); }
- /**#@-*/
-
+ function getTags() {
+ return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ) );
+ }
/**
* Break wikitext input into sections, and either pull or replace
@@ -4745,8 +4800,8 @@ class Parser
*
* External callers should use the getSection and replaceSection methods.
*
- * @param string $text Page wikitext
- * @param string $section A section identifier string of the form:
+ * @param $text String: Page wikitext
+ * @param $section String: a section identifier string of the form:
* <flag1> - <flag2> - ... - <section number>
*
* Currently the only recognised flag is "T", which means the target section number
@@ -4759,21 +4814,21 @@ class Parser
* pull the given section along with its lower-level subsections. If the section is
* not found, $mode=get will return $newtext, and $mode=replace will return $text.
*
- * @param string $mode One of "get" or "replace"
- * @param string $newText Replacement text for section data.
- * @return string for "get", the extracted section text.
- * for "replace", the whole page with the section replaced.
+ * @param $mode String: one of "get" or "replace"
+ * @param $newText String: replacement text for section data.
+ * @return String: for "get", the extracted section text.
+ * for "replace", the whole page with the section replaced.
*/
private function extractSections( $text, $section, $mode, $newText='' ) {
global $wgTitle;
- $this->clearState();
- $this->setTitle( $wgTitle ); // not generally used but removes an ugly failure mode
$this->mOptions = new ParserOptions;
- $this->setOutputType( self::OT_WIKI );
+ $this->clearState();
+ $this->setTitle( $wgTitle ); # not generally used but removes an ugly failure mode
+ $this->setOutputType( self::OT_PLAIN );
$outText = '';
$frame = $this->getPreprocessor()->newFrame();
- // Process section extraction flags
+ # Process section extraction flags
$flags = 0;
$sectionParts = explode( '-', $section );
$sectionIndex = array_pop( $sectionParts );
@@ -4782,23 +4837,23 @@ class Parser
$flags |= self::PTD_FOR_INCLUSION;
}
}
- // Preprocess the text
+ # Preprocess the text
$root = $this->preprocessToDom( $text, $flags );
- // <h> nodes indicate section breaks
- // They can only occur at the top level, so we can find them by iterating the root's children
+ # <h> nodes indicate section breaks
+ # They can only occur at the top level, so we can find them by iterating the root's children
$node = $root->getFirstChild();
- // Find the target section
+ # Find the target section
if ( $sectionIndex == 0 ) {
- // Section zero doesn't nest, level=big
+ # Section zero doesn't nest, level=big
$targetLevel = 1000;
} else {
- while ( $node ) {
- if ( $node->getName() === 'h' ) {
- $bits = $node->splitHeading();
+ while ( $node ) {
+ if ( $node->getName() === 'h' ) {
+ $bits = $node->splitHeading();
if ( $bits['i'] == $sectionIndex ) {
- $targetLevel = $bits['level'];
+ $targetLevel = $bits['level'];
break;
}
}
@@ -4810,7 +4865,7 @@ class Parser
}
if ( !$node ) {
- // Not found
+ # Not found
if ( $mode === 'get' ) {
return $newText;
} else {
@@ -4818,7 +4873,7 @@ class Parser
}
}
- // Find the end of the section, including nested sections
+ # Find the end of the section, including nested sections
do {
if ( $node->getName() === 'h' ) {
$bits = $node->splitHeading();
@@ -4833,13 +4888,13 @@ class Parser
$node = $node->getNextSibling();
} while ( $node );
- // Write out the remainder (in replace mode only)
+ # Write out the remainder (in replace mode only)
if ( $mode === 'replace' ) {
- // Output the replacement text
- // Add two newlines on -- trailing whitespace in $newText is conventionally
- // stripped by the editor, so we need both newlines to restore the paragraph gap
- // Only add trailing whitespace if there is newText
- if($newText != "") {
+ # Output the replacement text
+ # Add two newlines on -- trailing whitespace in $newText is conventionally
+ # stripped by the editor, so we need both newlines to restore the paragraph gap
+ # Only add trailing whitespace if there is newText
+ if ( $newText != "" ) {
$outText .= $newText . "\n\n";
}
@@ -4850,7 +4905,7 @@ class Parser
}
if ( is_string( $outText ) ) {
- // Re-insert stripped tags
+ # Re-insert stripped tags
$outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
}
@@ -4864,20 +4919,38 @@ class Parser
*
* If a section contains subsections, these are also returned.
*
- * @param string $text text to look in
- * @param string $section section identifier
- * @param string $deftext default to return if section is not found
+ * @param $text String: text to look in
+ * @param $section String: section identifier
+ * @param $deftext String: default to return if section is not found
* @return string text of the requested section
*/
public function getSection( $text, $section, $deftext='' ) {
return $this->extractSections( $text, $section, "get", $deftext );
}
+ /**
+ * This function returns $oldtext after the content of the section
+ * specified by $section has been replaced with $text.
+ *
+ * @param $text String: former text of the article
+ * @param $section Numeric: section identifier
+ * @param $text String: replacing text
+ * #return String: modified text
+ */
public function replaceSection( $oldtext, $section, $text ) {
return $this->extractSections( $oldtext, $section, "replace", $text );
}
/**
+ * Get the ID of the revision we are parsing
+ *
+ * @return Mixed: integer or null
+ */
+ function getRevisionId() {
+ return $this->mRevisionId;
+ }
+
+ /**
* Get the timestamp associated with the current revision, adjusted for
* the default server-local timestamp
*/
@@ -4889,18 +4962,18 @@ class Parser
$timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
array( 'rev_id' => $this->mRevisionId ), __METHOD__ );
- // Normalize timestamp to internal MW format for timezone processing.
- // This has the added side-effect of replacing a null value with
- // the current time, which gives us more sensible behavior for
- // previews.
+ # Normalize timestamp to internal MW format for timezone processing.
+ # This has the added side-effect of replacing a null value with
+ # the current time, which gives us more sensible behavior for
+ # previews.
$timestamp = wfTimestamp( TS_MW, $timestamp );
- // The cryptic '' timezone parameter tells to use the site-default
- // timezone offset instead of the user settings.
- //
- // Since this value will be saved into the parser cache, served
- // to other users, and potentially even used inside links and such,
- // it needs to be consistent for all visitors.
+ # The cryptic '' timezone parameter tells to use the site-default
+ # timezone offset instead of the user settings.
+ #
+ # Since this value will be saved into the parser cache, served
+ # to other users, and potentially even used inside links and such,
+ # it needs to be consistent for all visitors.
$this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
wfProfileOut( __METHOD__ );
@@ -4910,11 +4983,13 @@ class Parser
/**
* Get the name of the user that edited the last revision
+ *
+ * @return String: user name
*/
function getRevisionUser() {
- // if this template is subst: the revision id will be blank,
- // so just use the current user's name
- if( $this->mRevisionId ) {
+ # if this template is subst: the revision id will be blank,
+ # so just use the current user's name
+ if ( $this->mRevisionId ) {
$revision = Revision::newFromId( $this->mRevisionId );
$revuser = $revision->getUserText();
} else {
@@ -4931,23 +5006,24 @@ class Parser
*/
public function setDefaultSort( $sort ) {
$this->mDefaultSort = $sort;
+ $this->mOutput->setProperty( 'defaultsort', $sort );
}
/**
* Accessor for $mDefaultSort
- * Will use the title/prefixed title if none is set
+ * Will use the empty string if none is set.
+ *
+ * This value is treated as a prefix, so the
+ * empty string is equivalent to sorting by
+ * page name.
*
* @return string
*/
public function getDefaultSort() {
- global $wgCategoryPrefixedDefaultSortkey;
- if( $this->mDefaultSort !== false ) {
+ if ( $this->mDefaultSort !== false ) {
return $this->mDefaultSort;
- } elseif ($this->mTitle->getNamespace() == NS_CATEGORY ||
- !$wgCategoryPrefixedDefaultSortkey) {
- return $this->mTitle->getText();
} else {
- return $this->mTitle->getPrefixedText();
+ return '';
}
}
@@ -4969,19 +5045,23 @@ class Parser
public function guessSectionNameFromWikiText( $text ) {
# Strip out wikitext links(they break the anchor)
$text = $this->stripSectionName( $text );
- $headline = Sanitizer::decodeCharReferences( $text );
- # strip out HTML
- $headline = StringUtils::delimiterReplace( '<', '>', '', $headline );
- $headline = trim( $headline );
- $sectionanchor = '#' . urlencode( str_replace( ' ', '_', $headline ) );
- $replacearray = array(
- '%3A' => ':',
- '%' => '.'
- );
- return str_replace(
- array_keys( $replacearray ),
- array_values( $replacearray ),
- $sectionanchor );
+ $text = Sanitizer::normalizeSectionNameWhitespace( $text );
+ return '#' . Sanitizer::escapeId( $text, 'noninitial' );
+ }
+
+ /**
+ * Same as guessSectionNameFromWikiText(), but produces legacy anchors
+ * instead. For use in redirects, since IE6 interprets Redirect: headers
+ * as something other than UTF-8 (apparently?), resulting in breakage.
+ *
+ * @param $text String: The section name
+ * @return string An anchor
+ */
+ public function guessLegacySectionNameFromWikiText( $text ) {
+ # Strip out wikitext links(they break the anchor)
+ $text = $this->stripSectionName( $text );
+ $text = Sanitizer::normalizeSectionNameWhitespace( $text );
+ return '#' . Sanitizer::escapeId( $text, array( 'noninitial', 'legacy' ) );
}
/**
@@ -4994,42 +5074,38 @@ class Parser
* to create valid section anchors by mimicing the output of the
* parser when headings are parsed.
*
- * @param $text string Text string to be stripped of wikitext
+ * @param $text String: text string to be stripped of wikitext
* for use in a Section anchor
* @return Filtered text string
*/
public function stripSectionName( $text ) {
# Strip internal link markup
- $text = preg_replace('/\[\[:?([^[|]+)\|([^[]+)\]\]/','$2',$text);
- $text = preg_replace('/\[\[:?([^[]+)\|?\]\]/','$1',$text);
+ $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
+ $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
# Strip external link markup (FIXME: Not Tolerant to blank link text
# I.E. [http://www.mediawiki.org] will render as [1] or something depending
# on how many empty links there are on the page - need to figure that out.
- $text = preg_replace('/\[(?:' . wfUrlProtocols() . ')([^ ]+?) ([^[]+)\]/','$2',$text);
+ $text = preg_replace( '/\[(?:' . wfUrlProtocols() . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
# Parse wikitext quotes (italics & bold)
- $text = $this->doQuotes($text);
+ $text = $this->doQuotes( $text );
# Strip HTML tags
$text = StringUtils::delimiterReplace( '<', '>', '', $text );
return $text;
}
- function srvus( $text ) {
- return $this->testSrvus( $text, $this->mOutputType );
- }
-
/**
* strip/replaceVariables/unstrip for preprocessor regression testing
*/
- function testSrvus( $text, $title, $options, $outputType = self::OT_HTML ) {
+ function testSrvus( $text, $title, ParserOptions $options, $outputType = self::OT_HTML ) {
+ $this->mOptions = $options;
$this->clearState();
- if ( ! ( $title instanceof Title ) ) {
+ if ( !$title instanceof Title ) {
$title = Title::newFromText( $title );
}
$this->mTitle = $title;
- $this->mOptions = $options;
$this->setOutputType( $outputType );
$text = $this->replaceVariables( $text );
$text = $this->mStripState->unstripBoth( $text );
@@ -5039,14 +5115,14 @@ class Parser
function testPst( $text, $title, $options ) {
global $wgUser;
- if ( ! ( $title instanceof Title ) ) {
+ if ( !$title instanceof Title ) {
$title = Title::newFromText( $title );
}
return $this->preSaveTransform( $text, $title, $wgUser, $options );
}
function testPreprocess( $text, $title, $options ) {
- if ( ! ( $title instanceof Title ) ) {
+ if ( !$title instanceof Title ) {
$title = Title::newFromText( $title );
}
return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
@@ -5080,11 +5156,13 @@ class Parser
$data = array();
$data['text'] = $text;
- // First, find all strip markers, and store their
- // data in an array.
+ # First, find all strip markers, and store their
+ # data in an array.
$stripState = new StripState;
$pos = 0;
- while( ( $start_pos = strpos( $text, $this->mUniqPrefix, $pos ) ) && ( $end_pos = strpos( $text, self::MARKER_SUFFIX, $pos ) ) ) {
+ while ( ( $start_pos = strpos( $text, $this->mUniqPrefix, $pos ) )
+ && ( $end_pos = strpos( $text, self::MARKER_SUFFIX, $pos ) ) )
+ {
$end_pos += strlen( self::MARKER_SUFFIX );
$marker = substr( $text, $start_pos, $end_pos-$start_pos );
@@ -5103,17 +5181,17 @@ class Parser
}
$data['stripstate'] = $stripState;
- // Now, find all of our links, and store THEIR
- // data in an array! :)
+ # Now, find all of our links, and store THEIR
+ # data in an array! :)
$links = array( 'internal' => array(), 'interwiki' => array() );
$pos = 0;
- // Internal links
- while( ( $start_pos = strpos( $text, '<!--LINK ', $pos ) ) ) {
+ # Internal links
+ while ( ( $start_pos = strpos( $text, '<!--LINK ', $pos ) ) ) {
list( $ns, $trail ) = explode( ':', substr( $text, $start_pos + strlen( '<!--LINK ' ) ), 2 );
- $ns = trim($ns);
- if (empty( $links['internal'][$ns] )) {
+ $ns = trim( $ns );
+ if ( empty( $links['internal'][$ns] ) ) {
$links['internal'][$ns] = array();
}
@@ -5124,8 +5202,8 @@ class Parser
$pos = 0;
- // Interwiki links
- while( ( $start_pos = strpos( $text, '<!--IWLINK ', $pos ) ) ) {
+ # Interwiki links
+ while ( ( $start_pos = strpos( $text, '<!--IWLINK ', $pos ) ) ) {
$data = substr( $text, $start_pos );
$key = trim( substr( $data, 0, strpos( $data, '-->' ) ) );
$links['interwiki'][] = $this->mLinkHolders->interwiki[$key];
@@ -5137,22 +5215,29 @@ class Parser
return $data;
}
- function unserialiseHalfParsedText( $data, $intPrefix = null /* Unique identifying prefix */ ) {
- if (!$intPrefix)
- $intPrefix = $this->getRandomString();
+ /**
+ * TODO: document
+ * @param $data Array
+ * @param $intPrefix String unique identifying prefix
+ * @return String
+ */
+ function unserialiseHalfParsedText( $data, $intPrefix = null ) {
+ if ( !$intPrefix ) {
+ $intPrefix = self::getRandomString();
+ }
- // First, extract the strip state.
+ # First, extract the strip state.
$stripState = $data['stripstate'];
$this->mStripState->general->merge( $stripState->general );
$this->mStripState->nowiki->merge( $stripState->nowiki );
- // Now, extract the text, and renumber links
+ # Now, extract the text, and renumber links
$text = $data['text'];
$links = $data['linkholder'];
- // Internal...
- foreach( $links['internal'] as $ns => $nsLinks ) {
- foreach( $nsLinks as $key => $entry ) {
+ # Internal...
+ foreach ( $links['internal'] as $ns => $nsLinks ) {
+ foreach ( $nsLinks as $key => $entry ) {
$newKey = $intPrefix . '-' . $key;
$this->mLinkHolders->internals[$ns][$newKey] = $entry;
@@ -5160,15 +5245,15 @@ class Parser
}
}
- // Interwiki...
- foreach( $links['interwiki'] as $key => $entry ) {
+ # Interwiki...
+ foreach ( $links['interwiki'] as $key => $entry ) {
$newKey = "$intPrefix-$key";
$this->mLinkHolders->interwikis[$newKey] = $entry;
$text = str_replace( "<!--IWLINK $key-->", "<!--IWLINK $newKey-->", $text );
}
- // Should be good to go.
+ # Should be good to go.
return $text;
}
}
diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php
index 524d6be5..1e028ae5 100644
--- a/includes/parser/ParserCache.php
+++ b/includes/parser/ParserCache.php
@@ -1,9 +1,18 @@
<?php
/**
+ * Cache for outputs of the PHP parser
+ *
+ * @file
+ */
+
+/**
* @ingroup Cache Parser
* @todo document
*/
class ParserCache {
+ private $mMemc;
+ const try116cache = false; /* Only useful $wgParserCacheExpireTime after updating to 1.17 */
+
/**
* Get an instance of this object
*/
@@ -20,49 +29,119 @@ class ParserCache {
* Setup a cache pathway with a given back-end storage mechanism.
* May be a memcached client or a BagOStuff derivative.
*
- * @param object $memCached
+ * @param $memCached Object
*/
function __construct( $memCached ) {
+ if ( !$memCached ) {
+ throw new MWException( "Tried to create a ParserCache with an invalid memcached" );
+ }
$this->mMemc = $memCached;
}
- function getKey( $article, $popts ) {
+ protected function getParserOutputKey( $article, $hash ) {
global $wgRequest;
- if( $popts instanceof User ) // It used to be getKey( &$article, &$user )
- $popts = ParserOptions::newFromUser( $popts );
-
- $user = $popts->mUser;
- $printable = ( $popts->getIsPrintable() ) ? '!printable=1' : '';
- $hash = $user->getPageRenderingHash();
- if( !$article->mTitle->quickUserCan( 'edit' ) ) {
- // section edit links are suppressed even if the user has them on
- $edit = '!edit=0';
- } else {
- $edit = '';
- }
+ // idhash seem to mean 'page id' + 'rendering hash' (r3710)
$pageid = $article->getID();
$renderkey = (int)($wgRequest->getVal('action') == 'render');
- $key = wfMemcKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}{$edit}{$printable}" );
+
+ $key = wfMemcKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}" );
return $key;
}
+ protected function getOptionsKey( $article ) {
+ $pageid = $article->getID();
+ return wfMemcKey( 'pcache', 'idoptions', "{$pageid}" );
+ }
+
+ /**
+ * Provides an E-Tag suitable for the whole page. Note that $article
+ * is just the main wikitext. The E-Tag has to be unique to the whole
+ * page, even if the article itself is the same, so it uses the
+ * complete set of user options. We don't want to use the preference
+ * of a different user on a message just because it wasn't used in
+ * $article. For example give a Chinese interface to a user with
+ * English preferences. That's why we take into account *all* user
+ * options. (r70809 CR)
+ */
function getETag( $article, $popts ) {
- return 'W/"' . $this->getKey($article, $popts) . "--" . $article->mTouched. '"';
+ return 'W/"' . $this->getParserOutputKey( $article,
+ $popts->optionsHash( ParserOptions::legacyOptions() ) ) .
+ "--" . $article->getTouched() . '"';
}
- function getDirty( $article, $popts ) {
- $key = $this->getKey( $article, $popts );
- wfDebug( "Trying parser cache $key\n" );
- $value = $this->mMemc->get( $key );
+ /**
+ * Retrieve the ParserOutput from ParserCache, even if it's outdated.
+ */
+ public function getDirty( $article, $popts ) {
+ $value = $this->get( $article, $popts, true );
return is_object( $value ) ? $value : false;
}
- function get( $article, $popts ) {
+ /**
+ * Used to provide a unique id for the PoolCounter.
+ * It would be preferable to have this code in get()
+ * instead of having Article looking in our internals.
+ */
+ public function getKey( $article, $popts, $useOutdated = true ) {
+ global $wgCacheEpoch;
+
+ if( $popts instanceof User ) {
+ wfWarn( "Use of outdated prototype ParserCache::getKey( &\$article, &\$user )\n" );
+ $popts = ParserOptions::newFromUser( $popts );
+ }
+
+ // Determine the options which affect this article
+ $optionsKey = $this->mMemc->get( $this->getOptionsKey( $article ) );
+ if ( $optionsKey != false ) {
+ if ( !$useOutdated && $optionsKey->expired( $article->getTouched() ) ) {
+ wfIncrStats( "pcache_miss_expired" );
+ $cacheTime = $optionsKey->getCacheTime();
+ wfDebug( "Parser options key expired, touched " . $article->getTouched() . ", epoch $wgCacheEpoch, cached $cacheTime\n" );
+ return false;
+ }
+
+ $usedOptions = $optionsKey->mUsedOptions;
+ wfDebug( "Parser cache options found.\n" );
+ } else {
+ if ( !$useOutdated && !self::try116cache ) {
+ return false;
+ }
+ $usedOptions = ParserOptions::legacyOptions();
+ }
+
+ return $this->getParserOutputKey( $article, $popts->optionsHash( $usedOptions ) );
+ }
+
+ /**
+ * Retrieve the ParserOutput from ParserCache.
+ * false if not found or outdated.
+ */
+ public function get( $article, $popts, $useOutdated = false ) {
global $wgCacheEpoch;
wfProfileIn( __METHOD__ );
- $value = $this->getDirty( $article, $popts );
+ $canCache = $article->checkTouched();
+ if ( !$canCache ) {
+ // It's a redirect now
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $touched = $article->getTouched();
+
+ $parserOutputKey = $this->getKey( $article, $popts, $useOutdated );
+ if ( $parserOutputKey === false ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $value = $this->mMemc->get( $parserOutputKey );
+ if ( self::try116cache && !$value && strpos( $value, '*' ) !== -1 ) {
+ wfDebug( "New format parser cache miss.\n" );
+ $parserOutputKey = $this->getParserOutputKey( $article, $popts->optionsHash( ParserOptions::legacyOptions() ) );
+ $value = $this->mMemc->get( $parserOutputKey );
+ }
if ( !$value ) {
wfDebug( "Parser cache miss.\n" );
wfIncrStats( "pcache_miss_absent" );
@@ -71,18 +150,11 @@ class ParserCache {
}
wfDebug( "Found.\n" );
- # Invalid if article has changed since the cache was made
- $canCache = $article->checkTouched();
- $cacheTime = $value->getCacheTime();
- $touched = $article->mTouched;
- if ( !$canCache || $value->expired( $touched ) ) {
- if ( !$canCache ) {
- wfIncrStats( "pcache_miss_invalid" );
- wfDebug( "Invalid cached redirect, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
- } else {
- wfIncrStats( "pcache_miss_expired" );
- wfDebug( "Key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
- }
+
+ if ( !$useOutdated && $value->expired( $touched ) ) {
+ wfIncrStats( "pcache_miss_expired" );
+ $cacheTime = $value->getCacheTime();
+ wfDebug( "ParserOutput key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
$value = false;
} else {
if ( isset( $value->mTimestamp ) ) {
@@ -95,31 +167,37 @@ class ParserCache {
return $value;
}
- function save( $parserOutput, $article, $popts ){
- global $wgParserCacheExpireTime;
- $key = $this->getKey( $article, $popts );
- if( $parserOutput->getCacheTime() != -1 ) {
+ public function save( $parserOutput, $article, $popts ) {
+ $expire = $parserOutput->getCacheExpiry();
+ if( $expire > 0 ) {
$now = wfTimestampNow();
+
+ $optionsKey = new CacheTime;
+ $optionsKey->mUsedOptions = $parserOutput->getUsedOptions();
+ $optionsKey->updateCacheExpiry( $expire );
+
+ $optionsKey->setCacheTime( $now );
$parserOutput->setCacheTime( $now );
+ $optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
+
+ $parserOutputKey = $this->getParserOutputKey( $article, $popts->optionsHash( $optionsKey->mUsedOptions ) );
+
// Save the timestamp so that we don't have to load the revision row on view
$parserOutput->mTimestamp = $article->getTimestamp();
- $parserOutput->mText .= "\n<!-- Saved in parser cache with key $key and timestamp $now -->\n";
- wfDebug( "Saved in parser cache with key $key and timestamp $now\n" );
+ $parserOutput->mText .= "\n<!-- Saved in parser cache with key $parserOutputKey and timestamp $now -->\n";
+ wfDebug( "Saved in parser cache with key $parserOutputKey and timestamp $now\n" );
- if( $parserOutput->containsOldMagic() ){
- $expire = 3600; # 1 hour
- } else {
- $expire = $wgParserCacheExpireTime;
- }
- $this->mMemc->set( $key, $parserOutput, $expire );
+ // Save the parser output
+ $this->mMemc->set( $parserOutputKey, $parserOutput, $expire );
+ // ...and its pointer
+ $this->mMemc->set( $this->getOptionsKey( $article ), $optionsKey, $expire );
} else {
wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" );
}
}
-
}
diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php
index 985bba28..9d8b3e4f 100644
--- a/includes/parser/ParserOptions.php
+++ b/includes/parser/ParserOptions.php
@@ -1,5 +1,11 @@
<?php
-
+/**
+ * Options for the PHP parser
+ *
+ * @file
+ * @ingroup Parser
+ */
+
/**
* Set options of the Parser
* @todo document
@@ -29,43 +35,61 @@ class ParserOptions {
var $mEnableLimitReport; # Enable limit report in an HTML comment on output
var $mTimestamp; # Timestamp used for {{CURRENTDAY}} etc.
var $mExternalLinkTarget; # Target attribute for external links
+ var $mMath; # User math preference (as integer)
+ var $mUserLang; # Language code of the User language.
+ var $mThumbSize; # Thumb size preferred by the user.
+ var $mCleanSignatures; #
var $mUser; # Stored user object, just used to initialise the skin
var $mIsPreview; # Parsing the page for a "preview" operation
var $mIsSectionPreview; # Parsing the page for a "preview" operation on a single section
var $mIsPrintable; # Parsing the printable version of the page
+ var $mExtraKey = ''; # Extra key that should be present in the caching key.
+
+ protected $onAccessCallback = null;
+
function getUseDynamicDates() { return $this->mUseDynamicDates; }
function getInterwikiMagic() { return $this->mInterwikiMagic; }
function getAllowExternalImages() { return $this->mAllowExternalImages; }
function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; }
function getEnableImageWhitelist() { return $this->mEnableImageWhitelist; }
- function getEditSection() { return $this->mEditSection; }
- function getNumberHeadings() { return $this->mNumberHeadings; }
+ function getEditSection() { $this->optionUsed('editsection');
+ return $this->mEditSection; }
+ function getNumberHeadings() { $this->optionUsed('numberheadings');
+ return $this->mNumberHeadings; }
function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; }
function getTidy() { return $this->mTidy; }
function getInterfaceMessage() { return $this->mInterfaceMessage; }
function getTargetLanguage() { return $this->mTargetLanguage; }
function getMaxIncludeSize() { return $this->mMaxIncludeSize; }
function getMaxPPNodeCount() { return $this->mMaxPPNodeCount; }
+ function getMaxPPExpandDepth() { return $this->mMaxPPExpandDepth; }
function getMaxTemplateDepth() { return $this->mMaxTemplateDepth; }
function getRemoveComments() { return $this->mRemoveComments; }
function getTemplateCallback() { return $this->mTemplateCallback; }
function getEnableLimitReport() { return $this->mEnableLimitReport; }
function getCleanSignatures() { return $this->mCleanSignatures; }
function getExternalLinkTarget() { return $this->mExternalLinkTarget; }
+ function getMath() { $this->optionUsed('math');
+ return $this->mMath; }
+ function getThumbSize() { $this->optionUsed('thumbsize');
+ return $this->mThumbSize; }
+
function getIsPreview() { return $this->mIsPreview; }
function getIsSectionPreview() { return $this->mIsSectionPreview; }
- function getIsPrintable() { return $this->mIsPrintable; }
+ function getIsPrintable() { $this->optionUsed('printable');
+ return $this->mIsPrintable; }
- function getSkin() {
+ function getSkin( $title = null ) {
if ( !isset( $this->mSkin ) ) {
- $this->mSkin = $this->mUser->getSkin();
+ $this->mSkin = $this->mUser->getSkin( $title );
}
return $this->mSkin;
}
function getDateFormat() {
+ $this->optionUsed('dateformat');
if ( !isset( $this->mDateFormat ) ) {
$this->mDateFormat = $this->mUser->getDatePreference();
}
@@ -79,6 +103,16 @@ class ParserOptions {
return $this->mTimestamp;
}
+ /**
+ * You shouldn't use this. Really. $parser->getFunctionLang() is all you need.
+ * Using this fragments the cache and is discouraged. Yes, {{int: }} uses this,
+ * producing inconsistent tables (Bug 14404).
+ */
+ function getUserLang() {
+ $this->optionUsed('userlang');
+ return $this->mUserLang;
+ }
+
function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); }
@@ -101,17 +135,30 @@ class ParserOptions {
function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); }
function setCleanSignatures( $x ) { return wfSetVar( $this->mCleanSignatures, $x ); }
function setExternalLinkTarget( $x ) { return wfSetVar( $this->mExternalLinkTarget, $x ); }
+ function setMath( $x ) { return wfSetVar( $this->mMath, $x ); }
+ function setUserLang( $x ) { return wfSetVar( $this->mUserLang, $x ); }
+ function setThumbSize( $x ) { return wfSetVar( $this->mThumbSize, $x ); }
+
function setIsPreview( $x ) { return wfSetVar( $this->mIsPreview, $x ); }
function setIsSectionPreview( $x ) { return wfSetVar( $this->mIsSectionPreview, $x ); }
function setIsPrintable( $x ) { return wfSetVar( $this->mIsPrintable, $x ); }
+ /**
+ * Extra key that should be present in the parser cache key.
+ */
+ function addExtraKey( $key ) {
+ $this->mExtraKey .= '!' . $key;
+ }
+
function __construct( $user = null ) {
$this->initialiseFromUser( $user );
}
/**
* Get parser options
- * @static
+ *
+ * @param $user User object
+ * @return ParserOptions object
*/
static function newFromUser( $user ) {
return new ParserOptions( $user );
@@ -122,7 +169,7 @@ class ParserOptions {
global $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
global $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion, $wgMaxArticleSize;
global $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth, $wgCleanSignatures;
- global $wgExternalLinkTarget;
+ global $wgExternalLinkTarget, $wgLang;
wfProfileIn( __METHOD__ );
@@ -161,9 +208,131 @@ class ParserOptions {
$this->mEnableLimitReport = false;
$this->mCleanSignatures = $wgCleanSignatures;
$this->mExternalLinkTarget = $wgExternalLinkTarget;
+ $this->mMath = $user->getOption( 'math' );
+ $this->mUserLang = $wgLang->getCode();
+ $this->mThumbSize = $user->getOption( 'thumbsize' );
+
$this->mIsPreview = false;
$this->mIsSectionPreview = false;
+ $this->mIsPrintable = false;
wfProfileOut( __METHOD__ );
}
+
+ /**
+ * Registers a callback for tracking which ParserOptions which are used.
+ * This is a private API with the parser.
+ */
+ function registerWatcher( $callback ) {
+ $this->onAccessCallback = $callback;
+ }
+
+ /**
+ * Called when an option is accessed.
+ */
+ protected function optionUsed( $optionName ) {
+ if ( $this->onAccessCallback ) {
+ call_user_func( $this->onAccessCallback, $optionName );
+ }
+ }
+
+ /**
+ * Returns the full array of options that would have been used by
+ * in 1.16.
+ * Used to get the old parser cache entries when available.
+ */
+ public static function legacyOptions() {
+ global $wgUseDynamicDates;
+ $legacyOpts = array( 'math', 'stubthreshold', 'numberheadings', 'userlang', 'thumbsize', 'editsection', 'printable' );
+ if ( $wgUseDynamicDates ) {
+ $legacyOpts[] = 'dateformat';
+ }
+ return $legacyOpts;
+ }
+
+ /**
+ * Generate a hash string with the values set on these ParserOptions
+ * for the keys given in the array.
+ * This will be used as part of the hash key for the parser cache,
+ * so users sharign the options with vary for the same page share
+ * the same cached data safely.
+ *
+ * Replaces User::getPageRenderingHash()
+ *
+ * Extensions which require it should install 'PageRenderingHash' hook,
+ * which will give them a chance to modify this key based on their own
+ * settings.
+ *
+ * @since 1.17
+ * @return \string Page rendering hash
+ */
+ public function optionsHash( $forOptions ) {
+ global $wgContLang, $wgRenderHashAppend;
+
+ $confstr = '';
+
+ if ( in_array( 'math', $forOptions ) )
+ $confstr .= $this->mMath;
+ else
+ $confstr .= '*';
+
+
+ // Space assigned for the stubthreshold but unused
+ // since it disables the parser cache, its value will always
+ // be 0 when this function is called by parsercache.
+ // The conditional is here to avoid a confusing 0
+ if ( true || in_array( 'stubthreshold', $forOptions ) )
+ $confstr .= '!0' ;
+ else
+ $confstr .= '!*' ;
+
+ if ( in_array( 'dateformat', $forOptions ) )
+ $confstr .= '!' . $this->getDateFormat();
+
+ if ( in_array( 'numberheadings', $forOptions ) )
+ $confstr .= '!' . ( $this->mNumberHeadings ? '1' : '' );
+ else
+ $confstr .= '!*';
+
+ if ( in_array( 'userlang', $forOptions ) )
+ $confstr .= '!' . $this->mUserLang;
+ else
+ $confstr .= '!*';
+
+ if ( in_array( 'thumbsize', $forOptions ) )
+ $confstr .= '!' . $this->mThumbSize;
+ else
+ $confstr .= '!*';
+
+ // add in language specific options, if any
+ // FIXME: This is just a way of retrieving the url/user preferred variant
+ $confstr .= $wgContLang->getExtraHashOptions();
+
+ // Since the skin could be overloading link(), it should be
+ // included here but in practice, none of our skins do that.
+ // $confstr .= "!" . $this->mSkin->getSkinName();
+
+ $confstr .= $wgRenderHashAppend;
+
+ if ( !in_array( 'editsection', $forOptions ) ) {
+ $confstr .= '!*';
+ } elseif ( !$this->mEditSection ) {
+ $confstr .= '!edit=0';
+ }
+
+ if ( $this->mIsPrintable && in_array( 'printable', $forOptions ) )
+ $confstr .= '!printable=1';
+
+ if ( $this->mExtraKey != '' )
+ $confstr .= $this->mExtraKey;
+
+ // Give a chance for extensions to modify the hash, if they have
+ // extra options or other effects on the parser cache.
+ wfRunHooks( 'PageRenderingHash', array( &$confstr ) );
+
+ // Make it a valid memcached key fragment
+ $confstr = str_replace( ' ', '_', $confstr );
+
+ return $confstr;
+ }
}
diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php
index ea5840e6..1e4765db 100644
--- a/includes/parser/ParserOutput.php
+++ b/includes/parser/ParserOutput.php
@@ -1,34 +1,131 @@
<?php
/**
+ * Output of the PHP parser
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
* @todo document
* @ingroup Parser
*/
-class ParserOutput
+
+class CacheTime {
+ var $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}}
+
+ function getCacheTime() { return $this->mCacheTime; }
+
+ function containsOldMagic() { return $this->mContainsOldMagic; }
+ function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
+
+ /**
+ * setCacheTime() sets the timestamp expressing when the page has been rendered.
+ * This doesn not control expiry, see updateCacheExpiry() for that!
+ */
+ function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
+
+
+ /**
+ * Sets the number of seconds after which this object should expire.
+ * This value is used with the ParserCache.
+ * If called with a value greater than the value provided at any previous call,
+ * the new call has no effect. The value returned by getCacheExpiry is smaller
+ * or equal to the smallest number that was provided as an argument to
+ * updateCacheExpiry().
+ */
+ function updateCacheExpiry( $seconds ) {
+ $seconds = (int)$seconds;
+
+ if ( $this->mCacheExpiry === null || $this->mCacheExpiry > $seconds )
+ $this->mCacheExpiry = $seconds;
+
+ // hack: set old-style marker for uncacheable entries.
+ if ( $this->mCacheExpiry !== null && $this->mCacheExpiry <= 0 )
+ $this->mCacheTime = -1;
+ }
+
+ /**
+ * Returns the number of seconds after which this object should expire.
+ * This method is used by ParserCache to determine how long the ParserOutput can be cached.
+ * The timestamp of expiry can be calculated by adding getCacheExpiry() to getCacheTime().
+ * The value returned by getCacheExpiry is smaller or equal to the smallest number
+ * that was provided to a call of updateCacheExpiry(), and smaller or equal to the
+ * value of $wgParserCacheExpireTime.
+ */
+ function getCacheExpiry() {
+ global $wgParserCacheExpireTime;
+
+ if ( $this->mCacheTime < 0 ) return 0; // old-style marker for "not cachable"
+
+ $expire = $this->mCacheExpiry;
+
+ if ( $expire === null )
+ $expire = $wgParserCacheExpireTime;
+ else
+ $expire = min( $expire, $wgParserCacheExpireTime );
+
+ if( $this->containsOldMagic() ) { //compatibility hack
+ $expire = min( $expire, 3600 ); # 1 hour
+ }
+
+ if ( $expire <= 0 ) return 0; // not cachable
+ else return $expire;
+ }
+
+
+ function isCacheable() {
+ return $this->getCacheExpiry() > 0;
+ }
+
+ /**
+ * Return true if this cached output object predates the global or
+ * per-article cache invalidation timestamps, or if it comes from
+ * an incompatible older version.
+ *
+ * @param $touched String: the affected article's last touched timestamp
+ * @return Boolean
+ */
+ public function expired( $touched ) {
+ global $wgCacheEpoch;
+ return !$this->isCacheable() || // parser says it's uncacheable
+ $this->getCacheTime() < $touched ||
+ $this->getCacheTime() <= $wgCacheEpoch ||
+ $this->getCacheTime() < wfTimestamp( TS_MW, time() - $this->getCacheExpiry() ) || // expiry period has passed
+ !isset( $this->mVersion ) ||
+ version_compare( $this->mVersion, Parser::VERSION, "lt" );
+ }
+}
+
+class ParserOutput extends CacheTime
{
var $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
- $mContainsOldMagic, # Boolean variable indicating if the input contained variables like {{CURRENTDAY}}
$mTitleText, # title text of the chosen language variant
- $mCacheTime = '', # Time when this object was generated, or -1 for uncacheable. Used in ParserCache.
- $mVersion = Parser::VERSION, # Compatibility check
$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.
$mTemplateIds = array(), # 2-D map of NS/DBK to rev ID for the template references. ID=zero for broken.
$mImages = array(), # DB keys of the images used, in the array key only
$mExternalLinks = array(), # External link URLs, in the key only
+ $mInterwikiLinks = array(), # 2-D map of prefix/DBK (in keys only) for the inline interwiki links in the document.
$mNewSection = false, # Show a new section link?
$mHideNewSection = false, # Hide the new section link?
$mNoGallery = false, # No gallery on category page? (__NOGALLERY__)
$mHeadItems = array(), # Items to put in the <head> section
+ $mModules = array(), # Modules to be loaded by the resource loader
$mOutputHooks = array(), # Hook tags as per $wgParserOutputHooks
$mWarnings = array(), # Warning text to be returned to the user. Wikitext formatted, in the key only
$mSections = array(), # Table of contents
$mProperties = array(), # Name/value pairs to be cached in the DB
$mTOCHTML = ''; # HTML of the TOC
private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change.
+ private $mAccessedOptions = null; # List of ParserOptions (stored in the keys)
- function ParserOutput( $text = '', $languageLinks = array(), $categoryLinks = array(),
+ function __construct( $text = '', $languageLinks = array(), $categoryLinks = array(),
$containsOldMagic = false, $titletext = '' )
{
$this->mText = $text;
@@ -40,9 +137,9 @@ class ParserOutput
function getText() { return $this->mText; }
function &getLanguageLinks() { return $this->mLanguageLinks; }
+ function getInterwikiLinks() { return $this->mInterwikiLinks; }
function getCategoryLinks() { return array_keys( $this->mCategories ); }
function &getCategories() { return $this->mCategories; }
- function getCacheTime() { return $this->mCacheTime; }
function getTitleText() { return $this->mTitleText; }
function getSections() { return $this->mSections; }
function &getLinks() { return $this->mLinks; }
@@ -51,18 +148,17 @@ class ParserOutput
function &getExternalLinks() { return $this->mExternalLinks; }
function getNoGallery() { return $this->mNoGallery; }
function getHeadItems() { return $this->mHeadItems; }
+ function getModules() { return $this->mModules; }
function getSubtitle() { return $this->mSubtitle; }
function getOutputHooks() { return (array)$this->mOutputHooks; }
function getWarnings() { return array_keys( $this->mWarnings ); }
function getIndexPolicy() { return $this->mIndexPolicy; }
function getTOCHTML() { return $this->mTOCHTML; }
- function containsOldMagic() { return $this->mContainsOldMagic; }
function setText( $text ) { return wfSetVar( $this->mText, $text ); }
function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); }
function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); }
- function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
- function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
+
function setTitleText( $t ) { return wfSetVar( $this->mTitleText, $t ); }
function setSections( $toc ) { return wfSetVar( $this->mSections, $toc ); }
function setIndexPolicy( $policy ) { return wfSetVar( $this->mIndexPolicy, $policy ); }
@@ -96,9 +192,16 @@ class ParserOutput
$this->mExternalLinks[$url] = 1;
}
+ /**
+ * Record a local or interwiki inline link for saving in future link tables.
+ *
+ * @param $title Title object
+ * @param $id Mixed: optional known page_id so we can skip the lookup
+ */
function addLink( $title, $id = null ) {
if ( $title->isExternal() ) {
// Don't record interwikis in pagelinks
+ $this->addInterwikiLink( $title );
return;
}
$ns = $title->getNamespace();
@@ -139,23 +242,20 @@ class ParserOutput
}
$this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
}
-
+
/**
- * Return true if this cached output object predates the global or
- * per-article cache invalidation timestamps, or if it comes from
- * an incompatible older version.
- *
- * @param string $touched the affected article's last touched timestamp
- * @return bool
- * @public
+ * @param $title Title object, must be an interwiki link
+ * @throws MWException if given invalid input
*/
- function expired( $touched ) {
- global $wgCacheEpoch;
- return $this->getCacheTime() == -1 || // parser says it's uncacheable
- $this->getCacheTime() < $touched ||
- $this->getCacheTime() <= $wgCacheEpoch ||
- !isset( $this->mVersion ) ||
- version_compare( $this->mVersion, Parser::VERSION, "lt" );
+ function addInterwikiLink( $title ) {
+ $prefix = $title->getInterwiki();
+ if( $prefix == '' ) {
+ throw new MWException( 'Non-interwiki link passed, internal parser error.' );
+ }
+ if (!isset($this->mInterwikiLinks[$prefix])) {
+ $this->mInterwikiLinks[$prefix] = array();
+ }
+ $this->mInterwikiLinks[$prefix][$title->getDBkey()] = 1;
}
/**
@@ -170,22 +270,27 @@ class ParserOutput
$this->mHeadItems[] = $section;
}
}
+
+ function addModules( $modules ) {
+ $this->mModules = array_merge( $this->mModules, (array) $modules );
+ }
/**
* Override the title to be used for display
* -- this is assumed to have been validated
* (check equal normalisation, etc.)
*
- * @param string $text Desired title text
+ * @param $text String: desired title text
*/
public function setDisplayTitle( $text ) {
$this->setTitleText( $text );
+ $this->setProperty( 'displaytitle', $text );
}
/**
* Get the title to be used for display
*
- * @return string
+ * @return String
*/
public function getDisplayTitle() {
$t = $this->getTitleText( );
@@ -223,4 +328,25 @@ class ParserOutput
}
return $this->mProperties;
}
+
+
+ /**
+ * Returns the options from its ParserOptions which have been taken
+ * into account to produce this output or false if not available.
+ * @return mixed Array/false
+ */
+ public function getUsedOptions() {
+ if ( !isset( $this->mAccessedOptions ) ) {
+ return false;
+ }
+ return array_keys( $this->mAccessedOptions );
+ }
+
+ /**
+ * Callback passed by the Parser to the ParserOptions to keep track of which options are used.
+ * @access private
+ */
+ function recordOption( $option ) {
+ $this->mAccessedOptions[$option] = true;
+ }
}
diff --git a/includes/parser/Parser_DiffTest.php b/includes/parser/Parser_DiffTest.php
index 608c883a..c6dd76e5 100644
--- a/includes/parser/Parser_DiffTest.php
+++ b/includes/parser/Parser_DiffTest.php
@@ -1,4 +1,9 @@
<?php
+/**
+ * Fake parser that output the difference of two different parsers
+ *
+ * @file
+ */
/**
* @ingroup Parser
@@ -8,14 +13,13 @@ class Parser_DiffTest
var $parsers, $conf;
var $shortOutput = false;
- var $dfUniqPrefix;
+ var $dtUniqPrefix;
function __construct( $conf ) {
if ( !isset( $conf['parsers'] ) ) {
throw new MWException( __METHOD__ . ': no parsers specified' );
}
$this->conf = $conf;
- $this->dtUniqPrefix = "\x7fUNIQ" . Parser::getRandomString();
}
function init() {
@@ -102,14 +106,18 @@ class Parser_DiffTest
function setFunctionHook( $id, $callback, $flags = 0 ) {
$this->init();
- foreach ( $this->parsers as $i => $parser ) {
+ foreach ( $this->parsers as $parser ) {
$parser->setFunctionHook( $id, $callback, $flags );
}
}
function onClearState( &$parser ) {
// hack marker prefixes to get identical output
- $parser->mUniqPrefix = $this->dtUniqPrefix;
+ if ( !isset( $this->dtUniqPrefix ) ) {
+ $this->dtUniqPrefix = $parser->uniqPrefix();
+ } else {
+ $parser->mUniqPrefix = $this->dtUniqPrefix;
+ }
return true;
}
}
diff --git a/includes/parser/Parser_LinkHooks.php b/includes/parser/Parser_LinkHooks.php
index 2b306933..7c17ce4e 100644
--- a/includes/parser/Parser_LinkHooks.php
+++ b/includes/parser/Parser_LinkHooks.php
@@ -1,5 +1,11 @@
<?php
/**
+ * Modified version of the PHP parser with hooks for wiki links; experimental
+ *
+ * @file
+ */
+
+/**
* Parser with LinkHooks experiment
* @ingroup Parser
*/
@@ -78,9 +84,9 @@ class Parser_LinkHooks extends Parser
*
* @public
*
- * @param integer|string $ns The Namespace ID or regex pattern if SLH_PATTERN is set
- * @param mixed $callback The callback function (and object) to use
- * @param integer $flags a combination of the following flags:
+ * @param $ns Integer or String: the Namespace ID or regex pattern if SLH_PATTERN is set
+ * @param $callback Mixed: the callback function (and object) to use
+ * @param $flags Integer: a combination of the following flags:
* SLH_PATTERN Use a regex link pattern rather than a namespace
*
* @return The old callback function for this name, if any
@@ -111,8 +117,6 @@ class Parser_LinkHooks extends Parser
* @private
*/
function replaceInternalLinks2( &$s ) {
- global $wgContLang;
-
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__.'-setup' );
@@ -128,7 +132,6 @@ class Parser_LinkHooks extends Parser
$titleRegex = "/^([{$tc}]+)$/sD";
}
- $sk = $this->mOptions->getSkin();
$holders = new LinkHolderArray( $this );
if( is_null( $this->mTitle ) ) {
@@ -136,13 +139,7 @@ class Parser_LinkHooks extends Parser
wfProfileOut( __METHOD__.'-setup' );
throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
}
- $nottalk = !$this->mTitle->isTalkPage();
-
- if($wgContLang->hasVariants()) {
- $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText());
- } else {
- $selflink = array($this->mTitle->getPrefixedText());
- }
+
wfProfileOut( __METHOD__.'-setup' );
$offset = 0;
@@ -268,9 +265,10 @@ class Parser_LinkHooks extends Parser
if( $return === false ) {
# False (no link) was returned, output plain wikitext
# Build it again as the hook is allowed to modify $paramText
- return isset($paramText) ? "[[$titleText|$paramText]]" : "[[$titleText]]";
+ $return = isset($paramText) ? "[[$titleText|$paramText]]" : "[[$titleText]]";
}
# Content was returned, return it
+ wfProfileOut( __METHOD__ );
return $return;
}
diff --git a/includes/parser/Preprocessor.php b/includes/parser/Preprocessor.php
index 9c417d23..c31f37bf 100644
--- a/includes/parser/Preprocessor.php
+++ b/includes/parser/Preprocessor.php
@@ -1,4 +1,9 @@
<?php
+/**
+ * Interfaces for preprocessors
+ *
+ * @file
+ */
/**
* @ingroup Parser
@@ -13,6 +18,9 @@ interface Preprocessor {
/** Create a new custom frame for programmatic use of parameter replacement as used in some extensions */
function newCustomFrame( $args );
+ /** Create a new custom node for programmatic use of parameter replacement as used in some extensions */
+ function newPartNodeArray( $values );
+
/** Preprocess text to a PPNode */
function preprocessToObj( $text, $flags = 0 );
}
diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php
index 673ac241..2b635f7c 100644
--- a/includes/parser/Preprocessor_DOM.php
+++ b/includes/parser/Preprocessor_DOM.php
@@ -1,5 +1,11 @@
<?php
-
+/**
+ * Preprocessor using PHP's dom extension
+ *
+ * @file
+ * @ingroup Parser
+ */
+
/**
* @ingroup Parser
*/
@@ -29,6 +35,30 @@ class Preprocessor_DOM implements Preprocessor {
return new PPCustomFrame_DOM( $this, $args );
}
+ function newPartNodeArray( $values ) {
+ //NOTE: DOM manipulation is slower than building & parsing XML! (or so Tim sais)
+ $xml = "";
+ $xml .= "<list>";
+
+ foreach ( $values as $k => $val ) {
+
+ if ( is_int( $k ) ) {
+ $xml .= "<part><name index=\"$k\"/><value>" . htmlspecialchars( $val ) ."</value></part>";
+ } else {
+ $xml .= "<part><name>" . htmlspecialchars( $k ) . "</name>=<value>" . htmlspecialchars( $val ) . "</value></part>";
+ }
+ }
+
+ $xml .= "</list>";
+
+ $dom = new DOMDocument();
+ $dom->loadXML( $xml );
+ $root = $dom->documentElement;
+
+ $node = new PPNode_DOM( $root->childNodes );
+ return $node;
+ }
+
function memCheck() {
if ( $this->memoryLimit === false ) {
return;
@@ -45,8 +75,8 @@ class Preprocessor_DOM implements Preprocessor {
* Preprocess some wikitext and return the document tree.
* This is the ghost of Parser::replace_variables().
*
- * @param string $text The text to parse
- * @param integer flags Bitwise combination of:
+ * @param $text String: the text to parse
+ * @param $flags Integer: bitwise combination of:
* Parser::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being
* included. Default is to assume a direct page view.
*
@@ -443,7 +473,7 @@ class Preprocessor_DOM implements Preprocessor {
$count = $piece->count;
$equalsLength = strspn( $revText, '=', strlen( $text ) - $searchStart );
if ( $equalsLength > 0 ) {
- if ( $i - $equalsLength == $piece->startPos ) {
+ if ( $searchStart - $equalsLength == $piece->startPos ) {
// This is just a single string of equals signs on its own line
// Replicate the doHeadings behaviour /={count}(.+)={count}/
// First find out how many equals signs there really are (don't stop at 6)
@@ -481,9 +511,7 @@ class Preprocessor_DOM implements Preprocessor {
// another heading. Infinite loops are avoided because the next iteration MUST
// hit the heading open case above, which unconditionally increments the
// input pointer.
- }
-
- elseif ( $found == 'open' ) {
+ } elseif ( $found == 'open' ) {
# count opening brace characters
$count = strspn( $text, $curChar, $i );
@@ -506,9 +534,7 @@ class Preprocessor_DOM implements Preprocessor {
$accum .= htmlspecialchars( str_repeat( $curChar, $count ) );
}
$i += $count;
- }
-
- elseif ( $found == 'close' ) {
+ } elseif ( $found == 'close' ) {
$piece = $stack->top;
# lets check if there are enough characters for closing brace
$maxCount = $piece->count;
@@ -516,7 +542,6 @@ class Preprocessor_DOM implements Preprocessor {
# check for maximum matching characters (if there are 5 closing
# characters, we will probably need only 3 - depending on the rules)
- $matchingCount = 0;
$rule = $rules[$piece->open];
if ( $count > $rule['max'] ) {
# The specified maximum exists in the callback array, unless the caller
@@ -561,7 +586,7 @@ class Preprocessor_DOM implements Preprocessor {
$element = "<$name$attr>";
$element .= "<title>$title</title>";
$argIndex = 1;
- foreach ( $parts as $partIndex => $part ) {
+ foreach ( $parts as $part ) {
if ( isset( $part->eqpos ) ) {
$argName = substr( $part->out, 0, $part->eqpos );
$argValue = substr( $part->out, $part->eqpos + 1 );
@@ -822,7 +847,7 @@ class PPFrame_DOM implements PPFrame {
/**
* Construct a new preprocessor frame.
- * @param Preprocessor $preprocessor The parent preprocessor
+ * @param $preprocessor Preprocessor: The parent preprocessor
*/
function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
@@ -877,12 +902,12 @@ class PPFrame_DOM implements PPFrame {
return $root;
}
- if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->mMaxPPNodeCount )
+ if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() )
{
return '<span class="error">Node-count limit exceeded</span>';
}
- if ( $expansionDepth > $this->parser->mOptions->mMaxPPExpandDepth ) {
+ if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
return '<span class="error">Expansion depth limit exceeded</span>';
}
wfProfileIn( __METHOD__ );
@@ -932,7 +957,9 @@ class PPFrame_DOM implements PPFrame {
$iteratorStack[$level] = false;
}
- if ( $contextNode instanceof PPNode_DOM ) $contextNode = $contextNode->node;
+ if ( $contextNode instanceof PPNode_DOM ) {
+ $contextNode = $contextNode->node;
+ }
$newIterator = false;
@@ -951,7 +978,7 @@ class PPFrame_DOM implements PPFrame {
$titles = $xpath->query( 'title', $contextNode );
$title = $titles->item( 0 );
$parts = $xpath->query( 'part', $contextNode );
- if ( $flags & self::NO_TEMPLATES ) {
+ if ( $flags & PPFrame::NO_TEMPLATES ) {
$newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $title, $parts );
} else {
$lineStart = $contextNode->getAttribute( 'lineStart' );
@@ -972,7 +999,7 @@ class PPFrame_DOM implements PPFrame {
$titles = $xpath->query( 'title', $contextNode );
$title = $titles->item( 0 );
$parts = $xpath->query( 'part', $contextNode );
- if ( $flags & self::NO_ARGS ) {
+ if ( $flags & PPFrame::NO_ARGS ) {
$newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $title, $parts );
} else {
$params = array(
@@ -990,13 +1017,13 @@ class PPFrame_DOM implements PPFrame {
# Remove it in HTML, pre+remove and STRIP_COMMENTS modes
if ( $this->parser->ot['html']
|| ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
- || ( $flags & self::STRIP_COMMENTS ) )
+ || ( $flags & PPFrame::STRIP_COMMENTS ) )
{
$out .= '';
}
# Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
# Not in RECOVER_COMMENTS mode (extractSections) though
- elseif ( $this->parser->ot['wiki'] && ! ( $flags & self::RECOVER_COMMENTS ) ) {
+ elseif ( $this->parser->ot['wiki'] && ! ( $flags & PPFrame::RECOVER_COMMENTS ) ) {
$out .= $this->parser->insertStripItem( $contextNode->textContent );
}
# Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
@@ -1008,7 +1035,7 @@ class PPFrame_DOM implements PPFrame {
# OT_WIKI will only respect <ignore> in substed templates.
# The other output types respect it unless NO_IGNORE is set.
# extractSections() sets NO_IGNORE and so never respects it.
- if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & self::NO_IGNORE ) ) {
+ if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) {
$out .= $contextNode->textContent;
} else {
$out .= '';
@@ -1112,7 +1139,9 @@ class PPFrame_DOM implements PPFrame {
$first = true;
$s = '';
foreach ( $args as $root ) {
- if ( $root instanceof PPNode_DOM ) $root = $root->node;
+ if ( $root instanceof PPNode_DOM ) {
+ $root = $root->node;
+ }
if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
$root = array( $root );
}
@@ -1136,9 +1165,11 @@ class PPFrame_DOM implements PPFrame {
$args = array_slice( func_get_args(), 1 );
$out = array();
$first = true;
- if ( $root instanceof PPNode_DOM ) $root = $root->node;
foreach ( $args as $root ) {
+ if ( $root instanceof PPNode_DOM ) {
+ $root = $root->node;
+ }
if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
$root = array( $root );
}
@@ -1163,7 +1194,9 @@ class PPFrame_DOM implements PPFrame {
$first = true;
foreach ( $args as $root ) {
- if ( $root instanceof PPNode_DOM ) $root = $root->node;
+ if ( $root instanceof PPNode_DOM ) {
+ $root = $root->node;
+ }
if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
$root = array( $root );
}
@@ -1239,7 +1272,8 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
var $numberedExpansionCache, $namedExpansionCache;
function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
- PPFrame_DOM::__construct( $preprocessor );
+ parent::__construct( $preprocessor );
+
$this->parent = $parent;
$this->numberedArgs = $numberedArgs;
$this->namedArgs = $namedArgs;
@@ -1310,7 +1344,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
}
if ( !isset( $this->numberedExpansionCache[$index] ) ) {
# No trimming for unnamed arguments
- $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], self::STRIP_COMMENTS );
+ $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS );
}
return $this->numberedExpansionCache[$index];
}
@@ -1322,7 +1356,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
if ( !isset( $this->namedExpansionCache[$name] ) ) {
# Trim named arguments post-expand, for backwards compatibility
$this->namedExpansionCache[$name] = trim(
- $this->parent->expand( $this->namedArgs[$name], self::STRIP_COMMENTS ) );
+ $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
}
return $this->namedExpansionCache[$name];
}
@@ -1351,7 +1385,7 @@ class PPCustomFrame_DOM extends PPFrame_DOM {
var $args;
function __construct( $preprocessor, $args ) {
- PPFrame_DOM::__construct( $preprocessor );
+ parent::__construct( $preprocessor );
$this->args = $args;
}
diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php
index c5d69685..6cb2febc 100644
--- a/includes/parser/Preprocessor_Hash.php
+++ b/includes/parser/Preprocessor_Hash.php
@@ -1,5 +1,11 @@
<?php
-
+/**
+ * Preprocessor using PHP arrays
+ *
+ * @file
+ * @ingroup Parser
+ */
+
/**
* Differences from DOM schema:
* * attribute nodes are children
@@ -23,12 +29,39 @@ class Preprocessor_Hash implements Preprocessor {
return new PPCustomFrame_Hash( $this, $args );
}
+ function newPartNodeArray( $values ) {
+ $list = array();
+
+ foreach ( $values as $k => $val ) {
+ $partNode = new PPNode_Hash_Tree( 'part' );
+ $nameNode = new PPNode_Hash_Tree( 'name' );
+
+ if ( is_int( $k ) ) {
+ $nameNode->addChild( new PPNode_Hash_Attr( 'index', $k ) );
+ $partNode->addChild( $nameNode );
+ } else {
+ $nameNode->addChild( new PPNode_Hash_Text( $k ) );
+ $partNode->addChild( $nameNode );
+ $partNode->addChild( new PPNode_Hash_Text( '=' ) );
+ }
+
+ $valueNode = new PPNode_Hash_Tree( 'value' );
+ $valueNode->addChild( new PPNode_Hash_Text( $val ) );
+ $partNode->addChild( $valueNode );
+
+ $list[] = $partNode;
+ }
+
+ $node = new PPNode_Hash_Array( $list );
+ return $node;
+ }
+
/**
* Preprocess some wikitext and return the document tree.
* This is the ghost of Parser::replace_variables().
*
- * @param string $text The text to parse
- * @param integer flags Bitwise combination of:
+ * @param $text String: the text to parse
+ * @param $flags Integer: bitwise combination of:
* Parser::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being
* included. Default is to assume a direct page view.
*
@@ -401,7 +434,7 @@ class Preprocessor_Hash implements Preprocessor {
$count = $piece->count;
$equalsLength = strspn( $revText, '=', strlen( $text ) - $searchStart );
if ( $equalsLength > 0 ) {
- if ( $i - $equalsLength == $piece->startPos ) {
+ if ( $searchStart - $equalsLength == $piece->startPos ) {
// This is just a single string of equals signs on its own line
// Replicate the doHeadings behaviour /={count}(.+)={count}/
// First find out how many equals signs there really are (don't stop at 6)
@@ -479,7 +512,6 @@ class Preprocessor_Hash implements Preprocessor {
# check for maximum matching characters (if there are 5 closing
# characters, we will probably need only 3 - depending on the rules)
- $matchingCount = 0;
$rule = $rules[$piece->open];
if ( $count > $rule['max'] ) {
# The specified maximum exists in the callback array, unless the caller
@@ -526,7 +558,7 @@ class Preprocessor_Hash implements Preprocessor {
$titleNode->lastChild = $titleAccum->lastNode;
$element->addChild( $titleNode );
$argIndex = 1;
- foreach ( $parts as $partIndex => $part ) {
+ foreach ( $parts as $part ) {
if ( isset( $part->eqpos ) ) {
// Find equals
$lastNode = false;
@@ -647,7 +679,7 @@ class Preprocessor_Hash implements Preprocessor {
// Cache
if ($cacheable) {
- $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );;
+ $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );
$wgMemc->set( $cacheKey, $cacheValue, 86400 );
wfProfileOut( __METHOD__.'-cache-miss' );
wfProfileOut( __METHOD__.'-cacheable' );
@@ -804,7 +836,7 @@ class PPFrame_Hash implements PPFrame {
/**
* Construct a new preprocessor frame.
- * @param Preprocessor $preprocessor The parent preprocessor
+ * @param $preprocessor Preprocessor: the parent preprocessor
*/
function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
@@ -826,7 +858,6 @@ class PPFrame_Hash implements PPFrame {
$title = $this->title;
}
if ( $args !== false ) {
- $xpath = false;
if ( $args instanceof PPNode_Hash_Array ) {
$args = $args->value;
} elseif ( !is_array( $args ) ) {
@@ -855,11 +886,11 @@ class PPFrame_Hash implements PPFrame {
return $root;
}
- if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->mMaxPPNodeCount )
+ if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() )
{
return '<span class="error">Node-count limit exceeded</span>';
}
- if ( $expansionDepth > $this->parser->mOptions->mMaxPPExpandDepth ) {
+ if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
return '<span class="error">Expansion depth limit exceeded</span>';
}
++$expansionDepth;
@@ -915,7 +946,7 @@ class PPFrame_Hash implements PPFrame {
if ( $contextNode->name == 'template' ) {
# Double-brace expansion
$bits = $contextNode->splitTemplate();
- if ( $flags & self::NO_TEMPLATES ) {
+ if ( $flags & PPFrame::NO_TEMPLATES ) {
$newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] );
} else {
$ret = $this->parser->braceSubstitution( $bits, $this );
@@ -928,7 +959,7 @@ class PPFrame_Hash implements PPFrame {
} elseif ( $contextNode->name == 'tplarg' ) {
# Triple-brace expansion
$bits = $contextNode->splitTemplate();
- if ( $flags & self::NO_ARGS ) {
+ if ( $flags & PPFrame::NO_ARGS ) {
$newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] );
} else {
$ret = $this->parser->argSubstitution( $bits, $this );
@@ -943,13 +974,13 @@ class PPFrame_Hash implements PPFrame {
# Remove it in HTML, pre+remove and STRIP_COMMENTS modes
if ( $this->parser->ot['html']
|| ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
- || ( $flags & self::STRIP_COMMENTS ) )
+ || ( $flags & PPFrame::STRIP_COMMENTS ) )
{
$out .= '';
}
# Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
# Not in RECOVER_COMMENTS mode (extractSections) though
- elseif ( $this->parser->ot['wiki'] && ! ( $flags & self::RECOVER_COMMENTS ) ) {
+ elseif ( $this->parser->ot['wiki'] && ! ( $flags & PPFrame::RECOVER_COMMENTS ) ) {
$out .= $this->parser->insertStripItem( $contextNode->firstChild->value );
}
# Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
@@ -961,7 +992,7 @@ class PPFrame_Hash implements PPFrame {
# OT_WIKI will only respect <ignore> in substed templates.
# The other output types respect it unless NO_IGNORE is set.
# extractSections() sets NO_IGNORE and so never respects it.
- if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & self::NO_IGNORE ) ) {
+ if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) {
$out .= $contextNode->firstChild->value;
} else {
//$out .= '';
@@ -1186,7 +1217,8 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
var $numberedExpansionCache, $namedExpansionCache;
function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
- PPFrame_Hash::__construct( $preprocessor );
+ parent::__construct( $preprocessor );
+
$this->parent = $parent;
$this->numberedArgs = $numberedArgs;
$this->namedArgs = $namedArgs;
@@ -1257,7 +1289,7 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
}
if ( !isset( $this->numberedExpansionCache[$index] ) ) {
# No trimming for unnamed arguments
- $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], self::STRIP_COMMENTS );
+ $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS );
}
return $this->numberedExpansionCache[$index];
}
@@ -1269,7 +1301,7 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
if ( !isset( $this->namedExpansionCache[$name] ) ) {
# Trim named arguments post-expand, for backwards compatibility
$this->namedExpansionCache[$name] = trim(
- $this->parent->expand( $this->namedArgs[$name], self::STRIP_COMMENTS ) );
+ $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
}
return $this->namedExpansionCache[$name];
}
@@ -1298,7 +1330,7 @@ class PPCustomFrame_Hash extends PPFrame_Hash {
var $args;
function __construct( $preprocessor, $args ) {
- PPFrame_Hash::__construct( $preprocessor );
+ parent::__construct( $preprocessor );
$this->args = $args;
}
diff --git a/includes/parser/Tidy.php b/includes/parser/Tidy.php
index 95f83621..38f22fd8 100644
--- a/includes/parser/Tidy.php
+++ b/includes/parser/Tidy.php
@@ -1,4 +1,9 @@
<?php
+/**
+ * HTML validation and correction
+ *
+ * @file
+ */
/**
* Class to interact with HTML tidy
@@ -16,8 +21,8 @@ class MWTidy {
* If tidy isn't able to correct the markup, the original will be
* returned in all its glory with a warning comment appended.
*
- * @param string $text Hideous HTML input
- * @return string Corrected HTML output
+ * @param $text String: hideous HTML input
+ * @return String: corrected HTML output
*/
public static function tidy( $text ) {
global $wgTidyInternal;
@@ -26,9 +31,6 @@ class MWTidy {
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'.
'<head><title>test</title></head><body>'.$text.'</body></html>';
- # Tidy is known to clobber tabs; convert them to entities
- $wrappedtext = str_replace( "\t", '&#9;', $wrappedtext );
-
if( $wgTidyInternal ) {
$correctedtext = self::execInternalTidy( $wrappedtext );
} else {
@@ -39,9 +41,6 @@ class MWTidy {
return $text . "\n<!-- Tidy found serious XHTML errors -->\n";
}
- # Convert the tabs back from entities
- $correctedtext = str_replace( '&#9;', "\t", $correctedtext );
-
return $correctedtext;
}
@@ -71,7 +70,7 @@ class MWTidy {
* @param $text String: HTML to check
* @param $stderr Boolean: Whether to read from STDERR rather than STDOUT
* @param &$retval Exit code (-1 on internal error)
- * @retrun mixed String or null
+ * @return mixed String or null
*/
private static function execExternalTidy( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgTidyBin, $wgTidyOpts;
@@ -119,15 +118,13 @@ class MWTidy {
$retval = -1;
}
- wfProfileOut( __METHOD__ );
-
if( !$stderr && $cleansource == '' && $text != '' ) {
// Some kind of error happened, so we couldn't get the corrected text.
// Just give up; we'll use the source text and append a warning.
- return null;
- } else {
- return $cleansource;
+ $cleansource = null;
}
+ wfProfileOut( __METHOD__ );
+ return $cleansource;
}
/**
@@ -137,7 +134,7 @@ class MWTidy {
* 'pear install tidy' should be able to compile the extension module.
*/
private static function execInternalTidy( $text, $stderr = false, &$retval = null ) {
- global $wgTidyConf, $IP, $wgDebugTidy;
+ global $wgTidyConf, $wgDebugTidy;
wfProfileIn( __METHOD__ );
$tidy = new tidy;
@@ -145,6 +142,7 @@ class MWTidy {
if( $stderr ) {
$retval = $tidy->getStatus();
+ wfProfileOut( __METHOD__ );
return $tidy->errorBuffer;
} else {
$tidy->cleanRepair();
diff --git a/includes/proxy_check.php b/includes/proxy_check.php
index 61995fea..2bc46c0d 100644
--- a/includes/proxy_check.php
+++ b/includes/proxy_check.php
@@ -1,6 +1,8 @@
<?php
/**
* Command line script to check for an open proxy at a specified location
+ *
+ * @file
*/
if( php_sapi_name() != 'cli' ) {
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
new file mode 100644
index 00000000..c18022a4
--- /dev/null
+++ b/includes/resourceloader/ResourceLoader.php
@@ -0,0 +1,740 @@
+<?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 Roan Kattouw
+ * @author Trevor Parscal
+ */
+
+/**
+ * Dynamic JavaScript and CSS resource loading system.
+ *
+ * Most of the documention is on the MediaWiki documentation wiki starting at:
+ * http://www.mediawiki.org/wiki/ResourceLoader
+ */
+class ResourceLoader {
+
+ /* Protected Static Members */
+ protected static $filterCacheVersion = 2;
+
+ /** Array: List of module name/ResourceLoaderModule object pairs */
+ protected $modules = array();
+ /** Associative array mapping module name to info associative array */
+ protected $moduleInfos = array();
+
+ /* Protected Methods */
+
+ /**
+ * Loads information stored in the database about modules.
+ *
+ * This method grabs modules dependencies from the database and updates modules
+ * objects.
+ *
+ * This is not inside the module code because it is much faster to
+ * request all of the information at once than it is to have each module
+ * requests its own information. This sacrifice of modularity yields a substantial
+ * performance improvement.
+ *
+ * @param $modules Array: List of module names to preload information for
+ * @param $context ResourceLoaderContext: Context to load the information within
+ */
+ public function preloadModuleInfo( array $modules, ResourceLoaderContext $context ) {
+ if ( !count( $modules ) ) {
+ return; // or else Database*::select() will explode, plus it's cheaper!
+ }
+ $dbr = wfGetDB( DB_SLAVE );
+ $skin = $context->getSkin();
+ $lang = $context->getLanguage();
+
+ // Get file dependency information
+ $res = $dbr->select( 'module_deps', array( 'md_module', 'md_deps' ), array(
+ 'md_module' => $modules,
+ 'md_skin' => $context->getSkin()
+ ), __METHOD__
+ );
+
+ // Set modules' dependencies
+ $modulesWithDeps = array();
+ foreach ( $res as $row ) {
+ $this->getModule( $row->md_module )->setFileDependencies( $skin,
+ FormatJson::decode( $row->md_deps, true )
+ );
+ $modulesWithDeps[] = $row->md_module;
+ }
+
+ // Register the absence of a dependency row too
+ foreach ( array_diff( $modules, $modulesWithDeps ) as $name ) {
+ $this->getModule( $name )->setFileDependencies( $skin, array() );
+ }
+
+ // Get message blob mtimes. Only do this for modules with messages
+ $modulesWithMessages = array();
+ foreach ( $modules as $name ) {
+ if ( count( $this->getModule( $name )->getMessages() ) ) {
+ $modulesWithMessages[] = $name;
+ }
+ }
+ $modulesWithoutMessages = array_flip( $modules ); // Will be trimmed down by the loop below
+ if ( count( $modulesWithMessages ) ) {
+ $res = $dbr->select( 'msg_resource', array( 'mr_resource', 'mr_timestamp' ), array(
+ 'mr_resource' => $modulesWithMessages,
+ 'mr_lang' => $lang
+ ), __METHOD__
+ );
+ foreach ( $res as $row ) {
+ $this->getModule( $row->mr_resource )->setMsgBlobMtime( $lang, $row->mr_timestamp );
+ unset( $modulesWithoutMessages[$row->mr_resource] );
+ }
+ }
+ foreach ( array_keys( $modulesWithoutMessages ) as $name ) {
+ $this->getModule( $name )->setMsgBlobMtime( $lang, 0 );
+ }
+ }
+
+ /**
+ * Runs JavaScript or CSS data through a filter, caching the filtered result for future calls.
+ *
+ * Available filters are:
+ * - minify-js \see JavaScriptMinifier::minify
+ * - minify-css \see CSSMin::minify
+ *
+ * If $data is empty, only contains whitespace or the filter was unknown,
+ * $data is returned unmodified.
+ *
+ * @param $filter String: Name of filter to run
+ * @param $data String: Text to filter, such as JavaScript or CSS text
+ * @return String: Filtered data, or a comment containing an error message
+ */
+ protected function filter( $filter, $data ) {
+ global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
+ 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;
+ }
+
+ // Try for cache hit
+ // Use CACHE_ANYTHING since filtering is very slow compared to DB queries
+ $key = wfMemcKey( 'resourceloader', 'filter', $filter, md5( $data ) );
+ $cache = wfGetCache( CACHE_ANYTHING );
+ $cacheEntry = $cache->get( $key );
+ if ( is_string( $cacheEntry ) ) {
+ wfProfileOut( __METHOD__ );
+ return $cacheEntry;
+ }
+
+ // Run the filter - we've already verified one of these will work
+ try {
+ switch ( $filter ) {
+ case 'minify-js':
+ $result = JavaScriptMinifier::minify( $data,
+ $wgResourceLoaderMinifierStatementsOnOwnLine,
+ $wgResourceLoaderMinifierMaxLineLength
+ );
+ break;
+ case 'minify-css':
+ $result = CSSMin::minify( $data );
+ break;
+ }
+
+ // Save filtered text to Memcached
+ $cache->set( $key, $result );
+ } catch ( Exception $exception ) {
+ // Return exception as a comment
+ $result = "/*\n{$exception->__toString()}\n*/\n";
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ return $result;
+ }
+
+ /* Methods */
+
+ /**
+ * Registers core modules and runs registration hooks.
+ */
+ public function __construct() {
+ global $IP, $wgResourceModules;
+
+ wfProfileIn( __METHOD__ );
+
+ // Register core modules
+ $this->register( include( "$IP/resources/Resources.php" ) );
+ // Register extension modules
+ wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
+ $this->register( $wgResourceModules );
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Registers a module with the ResourceLoader system.
+ *
+ * @param $name Mixed: Name of module as a string or List of name/object pairs as an array
+ * @param $info Module info array. For backwards compatibility with 1.17alpha,
+ * this may also be a ResourceLoaderModule object. Optional when using
+ * multiple-registration calling style.
+ * @throws MWException: If a duplicate module registration is attempted
+ * @throws MWException: If a module name contains illegal characters (pipes or commas)
+ * @throws MWException: If something other than a ResourceLoaderModule is being registered
+ * @return Boolean: False if there were any errors, in which case one or more modules were not
+ * registered
+ */
+ public function register( $name, $info = null ) {
+ wfProfileIn( __METHOD__ );
+
+ // Allow multiple modules to be registered in one call
+ if ( is_array( $name ) ) {
+ foreach ( $name as $key => $value ) {
+ $this->register( $key, $value );
+ }
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ // Disallow duplicate registrations
+ if ( isset( $this->moduleInfos[$name] ) ) {
+ // A module has already been registered by this name
+ throw new MWException(
+ 'ResourceLoader duplicate registration error. ' .
+ 'Another module has already been registered as ' . $name
+ );
+ }
+
+ // Check $name for illegal characters
+ if ( preg_match( '/[|,]/', $name ) ) {
+ throw new MWException( "ResourceLoader module name '$name' is invalid. Names may not contain pipes (|) or commas (,)" );
+ }
+
+ // Attach module
+ if ( is_object( $info ) ) {
+ // Old calling convention
+ // Validate the input
+ if ( !( $info instanceof ResourceLoaderModule ) ) {
+ throw new MWException( 'ResourceLoader invalid module error. ' .
+ 'Instances of ResourceLoaderModule expected.' );
+ }
+
+ $this->moduleInfos[$name] = array( 'object' => $info );
+ $info->setName( $name );
+ $this->modules[$name] = $info;
+ } else {
+ // New calling convention
+ $this->moduleInfos[$name] = $info;
+ }
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Get a list of module names
+ *
+ * @return Array: List of module names
+ */
+ public function getModuleNames() {
+ return array_keys( $this->moduleInfos );
+ }
+
+ /**
+ * Get the ResourceLoaderModule object for a given module name.
+ *
+ * @param $name String: Module name
+ * @return Mixed: ResourceLoaderModule if module has been registered, null otherwise
+ */
+ public function getModule( $name ) {
+ if ( !isset( $this->modules[$name] ) ) {
+ if ( !isset( $this->moduleInfos[$name] ) ) {
+ // No such module
+ return null;
+ }
+ // Construct the requested object
+ $info = $this->moduleInfos[$name];
+ if ( isset( $info['object'] ) ) {
+ // Object given in info array
+ $object = $info['object'];
+ } else {
+ if ( !isset( $info['class'] ) ) {
+ $class = 'ResourceLoaderFileModule';
+ } else {
+ $class = $info['class'];
+ }
+ $object = new $class( $info );
+ }
+ $object->setName( $name );
+ $this->modules[$name] = $object;
+ }
+
+ return $this->modules[$name];
+ }
+
+ /**
+ * Outputs a response to a resource load-request, including a content-type header.
+ *
+ * @param $context ResourceLoaderContext: Context in which a response should be formed
+ */
+ public function respond( ResourceLoaderContext $context ) {
+ global $wgResourceLoaderMaxage, $wgCacheEpoch;
+
+ // Buffer output to catch warnings. Normally we'd use ob_clean() on the
+ // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
+ // is used: ob_clean() will clear the GZIP header in that case and it won't come
+ // back for subsequent output, resulting in invalid GZIP. So we have to wrap
+ // the whole thing in our own output buffer to be sure the active buffer
+ // doesn't use ob_gzhandler.
+ // See http://bugs.php.net/bug.php?id=36514
+ ob_start();
+
+ wfProfileIn( __METHOD__ );
+ $exceptions = '';
+
+ // Split requested modules into two groups, modules and missing
+ $modules = array();
+ $missing = array();
+ foreach ( $context->getModules() as $name ) {
+ if ( isset( $this->moduleInfos[$name] ) ) {
+ $modules[$name] = $this->getModule( $name );
+ } else {
+ $missing[] = $name;
+ }
+ }
+
+ // If a version wasn't specified we need a shorter expiry time for updates
+ // to propagate to clients quickly
+ if ( is_null( $context->getVersion() ) ) {
+ $maxage = $wgResourceLoaderMaxage['unversioned']['client'];
+ $smaxage = $wgResourceLoaderMaxage['unversioned']['server'];
+ }
+ // If a version was specified we can use a longer expiry time since changing
+ // version numbers causes cache misses
+ else {
+ $maxage = $wgResourceLoaderMaxage['versioned']['client'];
+ $smaxage = $wgResourceLoaderMaxage['versioned']['server'];
+ }
+
+ // Preload information needed to the mtime calculation below
+ try {
+ $this->preloadModuleInfo( array_keys( $modules ), $context );
+ } catch( Exception $e ) {
+ // Add exception to the output as a comment
+ $exceptions .= "/*\n{$e->__toString()}\n*/\n";
+ }
+
+ wfProfileIn( __METHOD__.'-getModifiedTime' );
+
+ $private = false;
+ // To send Last-Modified and support If-Modified-Since, we need to detect
+ // the last modified time
+ $mtime = wfTimestamp( TS_UNIX, $wgCacheEpoch );
+ foreach ( $modules as $module ) {
+ try {
+ // Bypass Squid and other shared caches if the request includes any private modules
+ if ( $module->getGroup() === 'private' ) {
+ $private = true;
+ }
+ // Calculate maximum modified time
+ $mtime = max( $mtime, $module->getModifiedTime( $context ) );
+ } catch ( Exception $e ) {
+ // Add exception to the output as a comment
+ $exceptions .= "/*\n{$e->__toString()}\n*/\n";
+ }
+ }
+
+ wfProfileOut( __METHOD__.'-getModifiedTime' );
+
+ if ( $context->getOnly() === 'styles' ) {
+ header( 'Content-Type: text/css; charset=utf-8' );
+ } else {
+ header( 'Content-Type: text/javascript; charset=utf-8' );
+ }
+ header( 'Last-Modified: ' . wfTimestamp( TS_RFC2822, $mtime ) );
+ if ( $context->getDebug() ) {
+ // Do not cache debug responses
+ header( 'Cache-Control: private, no-cache, must-revalidate' );
+ header( 'Pragma: no-cache' );
+ } else {
+ if ( $private ) {
+ header( "Cache-Control: private, max-age=$maxage" );
+ $exp = $maxage;
+ } else {
+ header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" );
+ $exp = min( $maxage, $smaxage );
+ }
+ header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) );
+ }
+
+ // If there's an If-Modified-Since header, respond with a 304 appropriately
+ // Some clients send "timestamp;length=123". Strip the part after the first ';'
+ // so we get a valid timestamp.
+ $ims = $context->getRequest()->getHeader( 'If-Modified-Since' );
+ if ( $ims !== false ) {
+ $imsTS = strtok( $ims, ';' );
+ if ( $mtime <= wfTimestamp( TS_UNIX, $imsTS ) ) {
+ // There's another bug in ob_gzhandler (see also the comment at
+ // the top of this function) that causes it to gzip even empty
+ // responses, meaning it's impossible to produce a truly empty
+ // response (because the gzip header is always there). This is
+ // a problem because 304 responses have to be completely empty
+ // per the HTTP spec, and Firefox behaves buggily when they're not.
+ // See also http://bugs.php.net/bug.php?id=51579
+ // To work around this, we tear down all output buffering before
+ // sending the 304.
+ // On some setups, ob_get_level() doesn't seem to go down to zero
+ // no matter how often we call ob_get_clean(), so instead of doing
+ // the more intuitive while ( ob_get_level() > 0 ) ob_get_clean();
+ // we have to be safe here and avoid an infinite loop.
+ for ( $i = 0; $i < ob_get_level(); $i++ ) {
+ ob_end_clean();
+ }
+
+ header( 'HTTP/1.0 304 Not Modified' );
+ header( 'Status: 304 Not Modified' );
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+ }
+
+ // Generate a response
+ $response = $this->makeModuleResponse( $context, $modules, $missing );
+
+ // Prepend comments indicating exceptions
+ $response = $exceptions . $response;
+
+ // Capture any PHP warnings from the output buffer and append them to the
+ // response in a comment if we're in debug mode.
+ if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) {
+ $response = "/*\n$warnings\n*/\n" . $response;
+ }
+
+ // Remove the output buffer and output the response
+ ob_end_clean();
+ echo $response;
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Generates code for a response
+ *
+ * @param $context ResourceLoaderContext: Context in which to generate a response
+ * @param $modules Array: List of module objects keyed by module name
+ * @param $missing Array: List of unavailable modules (optional)
+ * @return String: Response data
+ */
+ public function makeModuleResponse( ResourceLoaderContext $context,
+ array $modules, $missing = array() )
+ {
+ $out = '';
+ $exceptions = '';
+ if ( $modules === array() && $missing === array() ) {
+ return '/* No modules requested. Max made me put this here */';
+ }
+
+ wfProfileIn( __METHOD__ );
+ // Pre-fetch blobs
+ if ( $context->shouldIncludeMessages() ) {
+ try {
+ $blobs = MessageBlobStore::get( $this, $modules, $context->getLanguage() );
+ } catch ( Exception $e ) {
+ // Add exception to the output as a comment
+ $exceptions .= "/*\n{$e->__toString()}\n*/\n";
+ }
+ } else {
+ $blobs = array();
+ }
+
+ // Generate output
+ foreach ( $modules as $name => $module ) {
+ wfProfileIn( __METHOD__ . '-' . $name );
+ try {
+ // Scripts
+ $scripts = '';
+ if ( $context->shouldIncludeScripts() ) {
+ // bug 27054: Append semicolon to prevent weird bugs
+ // caused by files not terminating their statements right
+ $scripts .= $module->getScript( $context ) . ";\n";
+ }
+
+ // Styles
+ $styles = array();
+ if ( $context->shouldIncludeStyles() ) {
+ $styles = $module->getStyles( $context );
+ }
+
+ // Messages
+ $messagesBlob = isset( $blobs[$name] ) ? $blobs[$name] : '{}';
+
+ // Append output
+ switch ( $context->getOnly() ) {
+ case 'scripts':
+ $out .= $scripts;
+ break;
+ case 'styles':
+ $out .= self::makeCombinedStyles( $styles );
+ break;
+ case 'messages':
+ $out .= self::makeMessageSetScript( new XmlJsCode( $messagesBlob ) );
+ break;
+ default:
+ // Minify CSS before embedding in mediaWiki.loader.implement call
+ // (unless in debug mode)
+ if ( !$context->getDebug() ) {
+ foreach ( $styles as $media => $style ) {
+ $styles[$media] = $this->filter( 'minify-css', $style );
+ }
+ }
+ $out .= self::makeLoaderImplementScript( $name, $scripts, $styles,
+ new XmlJsCode( $messagesBlob ) );
+ break;
+ }
+ } catch ( Exception $e ) {
+ // Add exception to the output as a comment
+ $exceptions .= "/*\n{$e->__toString()}\n*/\n";
+
+ // Register module as missing
+ $missing[] = $name;
+ unset( $modules[$name] );
+ }
+ wfProfileOut( __METHOD__ . '-' . $name );
+ }
+
+ // Update module states
+ if ( $context->shouldIncludeScripts() ) {
+ // Set the state of modules loaded as only scripts to ready
+ if ( count( $modules ) && $context->getOnly() === 'scripts'
+ && !isset( $modules['startup'] ) )
+ {
+ $out .= self::makeLoaderStateScript(
+ array_fill_keys( array_keys( $modules ), 'ready' ) );
+ }
+ // Set the state of modules which were requested but unavailable as missing
+ if ( is_array( $missing ) && count( $missing ) ) {
+ $out .= self::makeLoaderStateScript( array_fill_keys( $missing, 'missing' ) );
+ }
+ }
+
+ if ( !$context->getDebug() ) {
+ if ( $context->getOnly() === 'styles' ) {
+ $out = $this->filter( 'minify-css', $out );
+ } else {
+ $out = $this->filter( 'minify-js', $out );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $exceptions . $out;
+ }
+
+ /* Static Methods */
+
+ /**
+ * Returns JS code to call to mediaWiki.loader.implement for a module with
+ * given properties.
+ *
+ * @param $name Module name
+ * @param $scripts Array: List of JavaScript code snippets to be executed after the
+ * module is loaded
+ * @param $styles Array: List of CSS strings keyed by media type
+ * @param $messages Mixed: List of messages associated with this module. May either be an
+ * associative array mapping message key to value, or a JSON-encoded message blob containing
+ * the same data, wrapped in an XmlJsCode object.
+ */
+ public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
+ if ( is_array( $scripts ) ) {
+ $scripts = implode( $scripts, "\n" );
+ }
+ return Xml::encodeJsCall(
+ 'mediaWiki.loader.implement',
+ array(
+ $name,
+ new XmlJsCode( "function( $, mw ) {{$scripts}}" ),
+ (object)$styles,
+ (object)$messages
+ ) );
+ }
+
+ /**
+ * Returns JS code which, when called, will register a given list of messages.
+ *
+ * @param $messages Mixed: Either an associative array mapping message key to value, or a
+ * JSON-encoded message blob containing the same data, wrapped in an XmlJsCode object.
+ */
+ public static function makeMessageSetScript( $messages ) {
+ return Xml::encodeJsCall( 'mediaWiki.messages.set', array( (object)$messages ) );
+ }
+
+ /**
+ * Combines an associative array mapping media type to CSS into a
+ * single stylesheet with @media blocks.
+ *
+ * @param $styles Array: List of CSS strings keyed by media type
+ */
+ public static function makeCombinedStyles( array $styles ) {
+ $out = '';
+ foreach ( $styles as $media => $style ) {
+ // Transform the media type based on request params and config
+ // The way that this relies on $wgRequest to propagate request params is slightly evil
+ $media = OutputPage::transformCssMedia( $media );
+
+ if ( $media === null ) {
+ // Skip
+ } else if ( $media === '' || $media == 'all' ) {
+ // Don't output invalid or frivolous @media statements
+ $out .= "$style\n";
+ } else {
+ $out .= "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "\n}\n";
+ }
+ }
+ return $out;
+ }
+
+ /**
+ * Returns a JS call to mediaWiki.loader.state, which sets the state of a
+ * module or modules to a given value. Has two calling conventions:
+ *
+ * - ResourceLoader::makeLoaderStateScript( $name, $state ):
+ * Set the state of a single module called $name to $state
+ *
+ * - ResourceLoader::makeLoaderStateScript( array( $name => $state, ... ) ):
+ * Set the state of modules with the given names to the given states
+ */
+ public static function makeLoaderStateScript( $name, $state = null ) {
+ if ( is_array( $name ) ) {
+ return Xml::encodeJsCall( 'mediaWiki.loader.state', array( $name ) );
+ } else {
+ return Xml::encodeJsCall( 'mediaWiki.loader.state', array( $name, $state ) );
+ }
+ }
+
+ /**
+ * Returns JS code which calls the script given by $script. The script will
+ * be called with local variables name, version, dependencies and group,
+ * which will have values corresponding to $name, $version, $dependencies
+ * and $group as supplied.
+ *
+ * @param $name String: Module name
+ * @param $version Integer: Module version number as a timestamp
+ * @param $dependencies Array: List of module names on which this module depends
+ * @param $group String: Group which the module is in.
+ * @param $script String: JavaScript code
+ */
+ public static function makeCustomLoaderScript( $name, $version, $dependencies, $group, $script ) {
+ $script = str_replace( "\n", "\n\t", trim( $script ) );
+ return Xml::encodeJsCall(
+ "( function( name, version, dependencies, group ) {\n\t$script\n} )",
+ array( $name, $version, $dependencies, $group ) );
+ }
+
+ /**
+ * Returns JS code which calls mediaWiki.loader.register with the given
+ * parameters. Has three calling conventions:
+ *
+ * - ResourceLoader::makeLoaderRegisterScript( $name, $version, $dependencies, $group ):
+ * Register a single module.
+ *
+ * - ResourceLoader::makeLoaderRegisterScript( array( $name1, $name2 ) ):
+ * Register modules with the given names.
+ *
+ * - ResourceLoader::makeLoaderRegisterScript( array(
+ * array( $name1, $version1, $dependencies1, $group1 ),
+ * array( $name2, $version2, $dependencies1, $group2 ),
+ * ...
+ * ) ):
+ * Registers modules with the given names and parameters.
+ *
+ * @param $name String: Module name
+ * @param $version Integer: Module version number as a timestamp
+ * @param $dependencies Array: List of module names on which this module depends
+ * @param $group String: group which the module is in.
+ */
+ public static function makeLoaderRegisterScript( $name, $version = null,
+ $dependencies = null, $group = null )
+ {
+ if ( is_array( $name ) ) {
+ return Xml::encodeJsCall( 'mediaWiki.loader.register', array( $name ) );
+ } else {
+ $version = (int) $version > 1 ? (int) $version : 1;
+ return Xml::encodeJsCall( 'mediaWiki.loader.register',
+ array( $name, $version, $dependencies, $group ) );
+ }
+ }
+
+ /**
+ * Returns JS code which runs given JS code if the client-side framework is
+ * present.
+ *
+ * @param $script String: JavaScript code
+ */
+ public static function makeLoaderConditionalScript( $script ) {
+ $script = str_replace( "\n", "\n\t", trim( $script ) );
+ return "if ( window.mediaWiki ) {\n\t$script\n}\n";
+ }
+
+ /**
+ * Returns JS code which will set the MediaWiki configuration array to
+ * the given value.
+ *
+ * @param $configuration Array: List of configuration values keyed by variable name
+ */
+ public static function makeConfigSetScript( array $configuration ) {
+ return Xml::encodeJsCall( 'mediaWiki.config.set', array( $configuration ) );
+ }
+
+ /**
+ * Convert an array of module names to a packed query string.
+ *
+ * For example, array( 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' )
+ * becomes 'foo.bar,baz|bar.baz,quux'
+ * @param $modules array of module names (strings)
+ * @return string Packed query string
+ */
+ public static function makePackedModulesString( $modules ) {
+ $groups = array(); // array( prefix => array( suffixes ) )
+ foreach ( $modules as $module ) {
+ $pos = strrpos( $module, '.' );
+ $prefix = $pos === false ? '' : substr( $module, 0, $pos );
+ $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
+ $groups[$prefix][] = $suffix;
+ }
+
+ $arr = array();
+ foreach ( $groups as $prefix => $suffixes ) {
+ $p = $prefix === '' ? '' : $prefix . '.';
+ $arr[] = $p . implode( ',', $suffixes );
+ }
+ return implode( '|', $arr );
+ }
+
+ /**
+ * Determine whether debug mode was requested
+ * Order of priority is 1) request param, 2) cookie, 3) $wg setting
+ * @return bool
+ */
+ public static function inDebugMode() {
+ global $wgRequest, $wgResourceLoaderDebug;
+ static $retval = null;
+ if ( !is_null( $retval ) )
+ return $retval;
+ return $retval = $wgRequest->getFuzzyBool( 'debug',
+ $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug ) );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php
new file mode 100644
index 00000000..bf059b46
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -0,0 +1,176 @@
+<?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 Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+/**
+ * Object passed around to modules which contains information about the state
+ * of a specific loader request
+ */
+class ResourceLoaderContext {
+
+ /* Protected Members */
+
+ protected $resourceLoader;
+ protected $request;
+ protected $modules;
+ protected $language;
+ protected $direction;
+ protected $skin;
+ protected $user;
+ protected $debug;
+ protected $only;
+ protected $version;
+ protected $hash;
+
+ /* Methods */
+
+ public function __construct( ResourceLoader $resourceLoader, WebRequest $request ) {
+ global $wgDefaultSkin, $wgResourceLoaderDebug;
+
+ $this->resourceLoader = $resourceLoader;
+ $this->request = $request;
+
+ // Interpret request
+ // List of modules
+ $modules = $request->getVal( 'modules' );
+ $this->modules = $modules ? self::expandModuleNames( $modules ) : array();
+ // Various parameters
+ $this->skin = $request->getVal( 'skin' );
+ $this->user = $request->getVal( 'user' );
+ $this->debug = $request->getFuzzyBool( 'debug', $wgResourceLoaderDebug );
+ $this->only = $request->getVal( 'only' );
+ $this->version = $request->getVal( 'version' );
+
+ if ( !$this->skin ) {
+ $this->skin = $wgDefaultSkin;
+ }
+ }
+
+ /**
+ * Expand a string of the form jquery.foo,bar|jquery.ui.baz,quux to
+ * an array of module names like array( 'jquery.foo', 'jquery.bar',
+ * 'jquery.ui.baz', 'jquery.ui.quux' )
+ * @param $modules String Packed module name list
+ * @return array of module names
+ */
+ public static function expandModuleNames( $modules ) {
+ $retval = array();
+ $exploded = explode( '|', $modules );
+ foreach ( $exploded as $group ) {
+ if ( strpos( $group, ',' ) === false ) {
+ // This is not a set of modules in foo.bar,baz notation
+ // but a single module
+ $retval[] = $group;
+ } else {
+ // This is a set of modules in foo.bar,baz notation
+ $pos = strrpos( $group, '.' );
+ if ( $pos === false ) {
+ // Prefixless modules, i.e. without dots
+ $retval = explode( ',', $group );
+ } else {
+ // We have a prefix and a bunch of suffixes
+ $prefix = substr( $group, 0, $pos ); // 'foo'
+ $suffixes = explode( ',', substr( $group, $pos + 1 ) ); // array( 'bar', 'baz' )
+ foreach ( $suffixes as $suffix ) {
+ $retval[] = "$prefix.$suffix";
+ }
+ }
+ }
+ }
+ return $retval;
+ }
+
+ public function getResourceLoader() {
+ return $this->resourceLoader;
+ }
+
+ public function getRequest() {
+ return $this->request;
+ }
+
+ public function getModules() {
+ return $this->modules;
+ }
+
+ public function getLanguage() {
+ if ( $this->language === null ) {
+ global $wgLang;
+ $this->language = $this->request->getVal( 'lang' );
+ if ( !$this->language ) {
+ $this->language = $wgLang->getCode();
+ }
+ }
+ return $this->language;
+ }
+
+ public function getDirection() {
+ if ( $this->direction === null ) {
+ $this->direction = $this->request->getVal( 'dir' );
+ if ( !$this->direction ) {
+ global $wgContLang;
+ $this->direction = $wgContLang->getDir();
+ }
+ }
+ return $this->direction;
+ }
+
+ public function getSkin() {
+ return $this->skin;
+ }
+
+ public function getUser() {
+ return $this->user;
+ }
+
+ public function getDebug() {
+ return $this->debug;
+ }
+
+ public function getOnly() {
+ return $this->only;
+ }
+
+ public function getVersion() {
+ return $this->version;
+ }
+
+ public function shouldIncludeScripts() {
+ return is_null( $this->only ) || $this->only === 'scripts';
+ }
+
+ public function shouldIncludeStyles() {
+ return is_null( $this->only ) || $this->only === 'styles';
+ }
+
+ public function shouldIncludeMessages() {
+ return is_null( $this->only ) || $this->only === 'messages';
+ }
+
+ public function getHash() {
+ if ( !isset( $this->hash ) ) {
+ $this->hash = implode( '|', array(
+ $this->getLanguage(), $this->getDirection(), $this->skin, $this->user,
+ $this->debug, $this->only, $this->version
+ ) );
+ }
+ return $this->hash;
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
new file mode 100644
index 00000000..44967a2e
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -0,0 +1,509 @@
+<?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 Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+/**
+ * ResourceLoader module based on local JavaScript/CSS files.
+ */
+class ResourceLoaderFileModule extends ResourceLoaderModule {
+
+ /* Protected Members */
+
+ /** String: Local base path, see __construct() */
+ protected $localBasePath = '';
+ /** String: Remote base path, see __construct() */
+ protected $remoteBasePath = '';
+ /**
+ * Array: List of paths to JavaScript files to always include
+ * @example array( [file-path], [file-path], ... )
+ */
+ protected $scripts = array();
+ /**
+ * Array: List of JavaScript files to include when using a specific language
+ * @example array( [language-code] => array( [file-path], [file-path], ... ), ... )
+ */
+ protected $languageScripts = array();
+ /**
+ * Array: List of JavaScript files to include when using a specific skin
+ * @example array( [skin-name] => array( [file-path], [file-path], ... ), ... )
+ */
+ protected $skinScripts = array();
+ /**
+ * Array: List of paths to JavaScript files to include in debug mode
+ * @example array( [skin-name] => array( [file-path], [file-path], ... ), ... )
+ */
+ protected $debugScripts = array();
+ /**
+ * Array: List of paths to JavaScript files to include in the startup module
+ * @example array( [file-path], [file-path], ... )
+ */
+ protected $loaderScripts = array();
+ /**
+ * Array: List of paths to CSS files to always include
+ * @example array( [file-path], [file-path], ... )
+ */
+ protected $styles = array();
+ /**
+ * Array: List of paths to CSS files to include when using specific skins
+ * @example array( [file-path], [file-path], ... )
+ */
+ protected $skinStyles = array();
+ /**
+ * Array: List of modules this module depends on
+ * @example array( [file-path], [file-path], ... )
+ */
+ protected $dependencies = array();
+ /**
+ * Array: List of message keys used by this module
+ * @example array( [message-key], [message-key], ... )
+ */
+ protected $messages = array();
+ /** String: Name of group to load this module in */
+ protected $group;
+ /** Boolean: Link to raw files in debug mode */
+ protected $debugRaw = true;
+ /**
+ * Array: Cache for mtime
+ * @example array( [hash] => [mtime], [hash] => [mtime], ... )
+ */
+ protected $modifiedTime = array();
+ /**
+ * Array: Place where readStyleFile() tracks file dependencies
+ * @example array( [file-path], [file-path], ... )
+ */
+ protected $localFileRefs = array();
+
+ /* Methods */
+
+ /**
+ * Constructs a new module from an options array.
+ *
+ * @param $options Array: List of options; if not given or empty, an empty module will be
+ * constructed
+ * @param $localBasePath String: Base path to prepend to all local paths in $options. Defaults
+ * to $IP
+ * @param $remoteBasePath String: Base path to prepend to all remote paths in $options. Defaults
+ * to $wgScriptPath
+ *
+ * @example $options
+ * array(
+ * // Base path to prepend to all local paths in $options. Defaults to $IP
+ * 'localBasePath' => [base path],
+ * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath
+ * 'remoteBasePath' => [base path],
+ * // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath
+ * 'remoteExtPath' => [base path],
+ * // Scripts to always include
+ * 'scripts' => [file path string or array of file path strings],
+ * // Scripts to include in specific language contexts
+ * 'languageScripts' => array(
+ * [language code] => [file path string or array of file path strings],
+ * ),
+ * // Scripts to include in specific skin contexts
+ * 'skinScripts' => array(
+ * [skin name] => [file path string or array of file path strings],
+ * ),
+ * // Scripts to include in debug contexts
+ * 'debugScripts' => [file path string or array of file path strings],
+ * // Scripts to include in the startup module
+ * 'loaderScripts' => [file path string or array of file path strings],
+ * // Modules which must be loaded before this module
+ * 'dependencies' => [modile name string or array of module name strings],
+ * // Styles to always load
+ * 'styles' => [file path string or array of file path strings],
+ * // Styles to include in specific skin contexts
+ * 'skinStyles' => array(
+ * [skin name] => [file path string or array of file path strings],
+ * ),
+ * // Messages to always load
+ * 'messages' => [array of message key strings],
+ * // Group which this module should be loaded together with
+ * 'group' => [group name string],
+ * )
+ */
+ public function __construct( $options = array(), $localBasePath = null,
+ $remoteBasePath = null )
+ {
+ global $IP, $wgScriptPath;
+ $this->localBasePath = $localBasePath === null ? $IP : $localBasePath;
+ $this->remoteBasePath = $remoteBasePath === null ? $wgScriptPath : $remoteBasePath;
+
+ if ( isset( $options['remoteExtPath'] ) ) {
+ global $wgExtensionAssetsPath;
+ $this->remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath'];
+ }
+
+ foreach ( $options as $member => $option ) {
+ switch ( $member ) {
+ // Lists of file paths
+ case 'scripts':
+ case 'debugScripts':
+ case 'loaderScripts':
+ case 'styles':
+ $this->{$member} = (array) $option;
+ break;
+ // Collated lists of file paths
+ case 'languageScripts':
+ case 'skinScripts':
+ case 'skinStyles':
+ if ( !is_array( $option ) ) {
+ throw new MWException(
+ "Invalid collated file path list error. " .
+ "'$option' given, array expected."
+ );
+ }
+ foreach ( $option as $key => $value ) {
+ if ( !is_string( $key ) ) {
+ throw new MWException(
+ "Invalid collated file path list key error. " .
+ "'$key' given, string expected."
+ );
+ }
+ $this->{$member}[$key] = (array) $value;
+ }
+ break;
+ // Lists of strings
+ case 'dependencies':
+ case 'messages':
+ $this->{$member} = (array) $option;
+ break;
+ // Single strings
+ case 'group':
+ case 'localBasePath':
+ case 'remoteBasePath':
+ $this->{$member} = (string) $option;
+ break;
+ // Single booleans
+ case 'debugRaw':
+ $this->{$member} = (bool) $option;
+ break;
+ }
+ }
+ // Make sure the remote base path is a complete valid url
+ $this->remoteBasePath = wfExpandUrl( $this->remoteBasePath );
+ }
+
+ /**
+ * Gets all scripts for a given context concatenated together.
+ *
+ * @param $context ResourceLoaderContext: Context in which to generate script
+ * @return String: JavaScript code for $context
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ $files = array_merge(
+ $this->scripts,
+ self::tryForKey( $this->languageScripts, $context->getLanguage() ),
+ self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
+ );
+ if ( $context->getDebug() ) {
+ $files = array_merge( $files, $this->debugScripts );
+ if ( $this->debugRaw ) {
+ $script = '';
+ foreach ( $files as $file ) {
+ $path = $this->getRemotePath( $file );
+ $script .= "\n\t" . Xml::encodeJsCall( 'mediaWiki.loader.load', array( $path ) );
+ }
+ return $script;
+ }
+ }
+ return $this->readScriptFiles( $files );
+ }
+
+ /**
+ * Gets loader script.
+ *
+ * @return String: JavaScript code to be added to startup module
+ */
+ public function getLoaderScript() {
+ if ( count( $this->loaderScripts ) == 0 ) {
+ return false;
+ }
+ return $this->readScriptFiles( $this->loaderScripts );
+ }
+
+ /**
+ * Gets all styles for a given context concatenated together.
+ *
+ * @param $context ResourceLoaderContext: Context in which to generate styles
+ * @return String: CSS code for $context
+ */
+ public function getStyles( ResourceLoaderContext $context ) {
+ // Merge general styles and skin specific styles, retaining media type collation
+ $styles = $this->readStyleFiles( $this->styles, $this->getFlip( $context ) );
+ $skinStyles = $this->readStyleFiles(
+ self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
+ $this->getFlip( $context )
+ );
+
+ foreach ( $skinStyles as $media => $style ) {
+ if ( isset( $styles[$media] ) ) {
+ $styles[$media] .= $style;
+ } else {
+ $styles[$media] = $style;
+ }
+ }
+ // Collect referenced files
+ $this->localFileRefs = array_unique( $this->localFileRefs );
+ // If the list has been modified since last time we cached it, update the cache
+ if ( $this->localFileRefs !== $this->getFileDependencies( $context->getSkin() ) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->replace( 'module_deps',
+ array( array( 'md_module', 'md_skin' ) ), array(
+ 'md_module' => $this->getName(),
+ 'md_skin' => $context->getSkin(),
+ 'md_deps' => FormatJson::encode( $this->localFileRefs ),
+ )
+ );
+ }
+ return $styles;
+ }
+
+ /**
+ * Gets list of message keys used by this module.
+ *
+ * @return Array: List of message keys
+ */
+ public function getMessages() {
+ return $this->messages;
+ }
+
+ /**
+ * Gets the name of the group this module should be loaded in.
+ *
+ * @return String: Group name
+ */
+ public function getGroup() {
+ return $this->group;
+ }
+
+ /**
+ * Gets list of names of modules this module depends on.
+ *
+ * @return Array: List of module names
+ */
+ public function getDependencies() {
+ return $this->dependencies;
+ }
+
+ /**
+ * Get the last modified timestamp of this module.
+ *
+ * Last modified timestamps are calculated from the highest last modified
+ * timestamp of this module's constituent files as well as the files it
+ * depends on. This function is context-sensitive, only performing
+ * calculations on files relevant to the given language, skin and debug
+ * mode.
+ *
+ * @param $context ResourceLoaderContext: Context in which to calculate
+ * the modified time
+ * @return Integer: UNIX timestamp
+ * @see ResourceLoaderModule::getFileDependencies
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
+ return $this->modifiedTime[$context->getHash()];
+ }
+ wfProfileIn( __METHOD__ );
+
+ $files = array();
+
+ // Flatten style files into $files
+ $styles = self::collateFilePathListByOption( $this->styles, 'media', 'all' );
+ foreach ( $styles as $styleFiles ) {
+ $files = array_merge( $files, $styleFiles );
+ }
+ $skinFiles = self::tryForKey(
+ self::collateFilePathListByOption( $this->skinStyles, 'media', 'all' ),
+ $context->getSkin(),
+ 'default'
+ );
+ foreach ( $skinFiles as $styleFiles ) {
+ $files = array_merge( $files, $styleFiles );
+ }
+
+ // Final merge, this should result in a master list of dependent files
+ $files = array_merge(
+ $files,
+ $this->scripts,
+ $context->getDebug() ? $this->debugScripts : array(),
+ self::tryForKey( $this->languageScripts, $context->getLanguage() ),
+ self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ),
+ $this->loaderScripts
+ );
+ $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() ) );
+
+ // If a module is nothing but a list of dependencies, we need to avoid
+ // giving max() an empty array
+ if ( count( $files ) === 0 ) {
+ wfProfileOut( __METHOD__ );
+ return $this->modifiedTime[$context->getHash()] = 1;
+ }
+
+ wfProfileIn( __METHOD__.'-filemtime' );
+ $filesMtime = max( array_map( 'filemtime', $files ) );
+ wfProfileOut( __METHOD__.'-filemtime' );
+ $this->modifiedTime[$context->getHash()] = max(
+ $filesMtime,
+ $this->getMsgBlobMtime( $context->getLanguage() ) );
+
+ wfProfileOut( __METHOD__ );
+ return $this->modifiedTime[$context->getHash()];
+ }
+
+ /* Protected Members */
+
+ protected function getLocalPath( $path ) {
+ return "{$this->localBasePath}/$path";
+ }
+
+ protected function getRemotePath( $path ) {
+ return "{$this->remoteBasePath}/$path";
+ }
+
+ /**
+ * Collates file paths by option (where provided).
+ *
+ * @param $list Array: List of file paths in any combination of index/path
+ * or path/options pairs
+ * @param $option String: option name
+ * @param $default Mixed: default value if the option isn't set
+ * @return Array: List of file paths, collated by $option
+ */
+ protected static function collateFilePathListByOption( array $list, $option, $default ) {
+ $collatedFiles = array();
+ foreach ( (array) $list as $key => $value ) {
+ if ( is_int( $key ) ) {
+ // File name as the value
+ if ( !isset( $collatedFiles[$default] ) ) {
+ $collatedFiles[$default] = array();
+ }
+ $collatedFiles[$default][] = $value;
+ } else if ( is_array( $value ) ) {
+ // File name as the key, options array as the value
+ $optionValue = isset( $value[$option] ) ? $value[$option] : $default;
+ if ( !isset( $collatedFiles[$optionValue] ) ) {
+ $collatedFiles[$optionValue] = array();
+ }
+ $collatedFiles[$optionValue][] = $key;
+ }
+ }
+ return $collatedFiles;
+ }
+
+ /**
+ * Gets a list of element that match a key, optionally using a fallback key.
+ *
+ * @param $list Array: List of lists to select from
+ * @param $key String: Key to look for in $map
+ * @param $fallback String: Key to look for in $list if $key doesn't exist
+ * @return Array: List of elements from $map which matched $key or $fallback,
+ * or an empty list in case of no match
+ */
+ protected static function tryForKey( array $list, $key, $fallback = null ) {
+ if ( isset( $list[$key] ) && is_array( $list[$key] ) ) {
+ return $list[$key];
+ } else if ( is_string( $fallback )
+ && isset( $list[$fallback] )
+ && is_array( $list[$fallback] ) )
+ {
+ return $list[$fallback];
+ }
+ return array();
+ }
+
+ /**
+ * Gets the contents of a list of JavaScript files.
+ *
+ * @param $scripts Array: List of file paths to scripts to read, remap and concetenate
+ * @return String: Concatenated and remapped JavaScript data from $scripts
+ */
+ protected function readScriptFiles( array $scripts ) {
+ if ( empty( $scripts ) ) {
+ return '';
+ }
+ $js = '';
+ foreach ( array_unique( $scripts ) as $fileName ) {
+ $localPath = $this->getLocalPath( $fileName );
+ $contents = file_get_contents( $localPath );
+ if ( $contents === false ) {
+ throw new MWException( __METHOD__.": script file not found: \"$localPath\"" );
+ }
+ $js .= $contents . "\n";
+ }
+ return $js;
+ }
+
+ /**
+ * Gets the contents of a list of CSS files.
+ *
+ * @param $styles Array: List of file paths to styles to read, remap and concetenate
+ * @return Array: List of concatenated and remapped CSS data from $styles,
+ * keyed by media type
+ */
+ protected function readStyleFiles( array $styles, $flip ) {
+ if ( empty( $styles ) ) {
+ return array();
+ }
+ $styles = self::collateFilePathListByOption( $styles, 'media', 'all' );
+ foreach ( $styles as $media => $files ) {
+ $uniqueFiles = array_unique( $files );
+ $styles[$media] = implode(
+ "\n",
+ array_map(
+ array( $this, 'readStyleFile' ),
+ $uniqueFiles,
+ array_fill( 0, count( $uniqueFiles ), $flip )
+ )
+ );
+ }
+ return $styles;
+ }
+
+ /**
+ * Reads a style file.
+ *
+ * This method can be used as a callback for array_map()
+ *
+ * @param $path String: File path of script file to read
+ * @return String: CSS data in script file
+ */
+ protected function readStyleFile( $path, $flip ) {
+ $localPath = $this->getLocalPath( $path );
+ $style = file_get_contents( $localPath );
+ if ( $style === false ) {
+ throw new MWException( __METHOD__.": style file not found: \"$localPath\"" );
+ }
+ if ( $flip ) {
+ $style = CSSJanus::transform( $style, true, false );
+ }
+ $dir = $this->getLocalPath( dirname( $path ) );
+ $remoteDir = $this->getRemotePath( dirname( $path ) );
+ // Get and register local file references
+ $this->localFileRefs = array_merge(
+ $this->localFileRefs,
+ CSSMin::getLocalFileReferences( $style, $dir ) );
+ return CSSMin::remap(
+ $style, $dir, $remoteDir, true
+ );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php
new file mode 100644
index 00000000..77d230c9
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -0,0 +1,239 @@
+<?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 Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+/**
+ * Abstraction for resource loader modules, with name registration and maxage functionality.
+ */
+abstract class ResourceLoaderModule {
+
+ /* Protected Members */
+
+ protected $name = null;
+
+ // In-object cache for file dependencies
+ protected $fileDeps = array();
+ // In-object cache for message blob mtime
+ protected $msgBlobMtime = array();
+
+ /* Methods */
+
+ /**
+ * Get this module's name. This is set when the module is registered
+ * with ResourceLoader::register()
+ *
+ * @return Mixed: Name (string) or null if no name was set
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * Set this module's name. This is called by ResourceLodaer::register()
+ * when registering the module. Other code should not call this.
+ *
+ * @param $name String: Name
+ */
+ public function setName( $name ) {
+ $this->name = $name;
+ }
+
+ /**
+ * Get whether CSS for this module should be flipped
+ */
+ public function getFlip( $context ) {
+ return $context->getDirection() === 'rtl';
+ }
+
+ /**
+ * Get all JS for this module for a given language and skin.
+ * Includes all relevant JS except loader scripts.
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return String: JavaScript code
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ // Stub, override expected
+ return '';
+ }
+
+ /**
+ * Get all CSS for this module for a given skin.
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return Array: List of CSS strings keyed by media type
+ */
+ public function getStyles( ResourceLoaderContext $context ) {
+ // Stub, override expected
+ return '';
+ }
+
+ /**
+ * Get the messages needed for this module.
+ *
+ * To get a JSON blob with messages, use MessageBlobStore::get()
+ *
+ * @return Array: List of message keys. Keys may occur more than once
+ */
+ public function getMessages() {
+ // Stub, override expected
+ return array();
+ }
+
+ /**
+ * Get the group this module is in.
+ *
+ * @return String: Group name
+ */
+ public function getGroup() {
+ // Stub, override expected
+ return null;
+ }
+
+ /**
+ * Get the loader JS for this module, if set.
+ *
+ * @return Mixed: JavaScript loader code as a string or boolean false if no custom loader set
+ */
+ public function getLoaderScript() {
+ // Stub, override expected
+ return false;
+ }
+
+ /**
+ * Get a list of modules this module depends on.
+ *
+ * Dependency information is taken into account when loading a module
+ * on the client side. When adding a module on the server side,
+ * dependency information is NOT taken into account and YOU are
+ * responsible for adding dependent modules as well. If you don't do
+ * this, the client side loader will send a second request back to the
+ * server to fetch the missing modules, which kind of defeats the
+ * purpose of the resource loader.
+ *
+ * To add dependencies dynamically on the client side, use a custom
+ * loader script, see getLoaderScript()
+ * @return Array: List of module names as strings
+ */
+ public function getDependencies() {
+ // Stub, override expected
+ return array();
+ }
+
+ /**
+ * Get the files this module depends on indirectly for a given skin.
+ * Currently these are only image files referenced by the module's CSS.
+ *
+ * @param $skin String: Skin name
+ * @return Array: List of files
+ */
+ public function getFileDependencies( $skin ) {
+ // Try in-object cache first
+ if ( isset( $this->fileDeps[$skin] ) ) {
+ return $this->fileDeps[$skin];
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $deps = $dbr->selectField( 'module_deps', 'md_deps', array(
+ 'md_module' => $this->getName(),
+ 'md_skin' => $skin,
+ ), __METHOD__
+ );
+ if ( !is_null( $deps ) ) {
+ $this->fileDeps[$skin] = (array) FormatJson::decode( $deps, true );
+ } else {
+ $this->fileDeps[$skin] = array();
+ }
+ return $this->fileDeps[$skin];
+ }
+
+ /**
+ * Set preloaded file dependency information. Used so we can load this
+ * information for all modules at once.
+ * @param $skin String: Skin name
+ * @param $deps Array: Array of file names
+ */
+ public function setFileDependencies( $skin, $deps ) {
+ $this->fileDeps[$skin] = $deps;
+ }
+
+ /**
+ * Get the last modification timestamp of the message blob for this
+ * module in a given language.
+ * @param $lang String: Language code
+ * @return Integer: UNIX timestamp, or 0 if no blob found
+ */
+ public function getMsgBlobMtime( $lang ) {
+ if ( !isset( $this->msgBlobMtime[$lang] ) ) {
+ if ( !count( $this->getMessages() ) )
+ return 0;
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
+ 'mr_resource' => $this->getName(),
+ 'mr_lang' => $lang
+ ), __METHOD__
+ );
+ $this->msgBlobMtime[$lang] = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0;
+ }
+ return $this->msgBlobMtime[$lang];
+ }
+
+ /**
+ * Set a preloaded message blob last modification timestamp. Used so we
+ * can load this information for all modules at once.
+ * @param $lang String: Language code
+ * @param $mtime Integer: UNIX timestamp or 0 if there is no such blob
+ */
+ public function setMsgBlobMtime( $lang, $mtime ) {
+ $this->msgBlobMtime[$lang] = $mtime;
+ }
+
+ /* Abstract Methods */
+
+ /**
+ * Get this module's last modification timestamp for a given
+ * combination of language, skin and debug mode flag. This is typically
+ * the highest of each of the relevant components' modification
+ * timestamps. Whenever anything happens that changes the module's
+ * contents for these parameters, the mtime should increase.
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return Integer: UNIX timestamp
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ // 0 would mean now
+ return 1;
+ }
+
+ /**
+ * Check whether this module is known to be empty. If a child class
+ * has an easy and cheap way to determine that this module is
+ * definitely going to be empty, it should override this method to
+ * return true in that case. Callers may optimize the request for this
+ * module away if this function returns true.
+ * @param $context ResourceLoaderContext: Context object
+ * @return Boolean
+ */
+ public function isKnownEmpty( ResourceLoaderContext $context ) {
+ return false;
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php
new file mode 100644
index 00000000..977d16bb
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -0,0 +1,63 @@
+<?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 Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+/**
+ * Module for site customizations
+ */
+class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
+
+ /* Protected Methods */
+
+ /**
+ * Gets list of pages used by this module
+ *
+ * @return Array: List of pages
+ */
+ protected function getPages( ResourceLoaderContext $context ) {
+ global $wgHandheldStyle;
+
+ $pages = array(
+ 'MediaWiki:Common.js' => array( 'type' => 'script' ),
+ 'MediaWiki:Common.css' => array( 'type' => 'style' ),
+ 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.js' => array( 'type' => 'script' ),
+ 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.css' => array( 'type' => 'style' ),
+ 'MediaWiki:Print.css' => array( 'type' => 'style', 'media' => 'print' ),
+ );
+ if ( $wgHandheldStyle ) {
+ $pages['MediaWiki:Handheld.css'] = array(
+ 'type' => 'style',
+ 'media' => 'handheld' );
+ }
+ return $pages;
+ }
+
+ /* Methods */
+
+ /**
+ * Gets group name
+ *
+ * @return String: Name of group
+ */
+ public function getGroup() {
+ return 'site';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
new file mode 100644
index 00000000..2a3ba343
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -0,0 +1,225 @@
+<?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 Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+class ResourceLoaderStartUpModule extends ResourceLoaderModule {
+
+ /* Protected Members */
+
+ protected $modifiedTime = array();
+
+ /* Protected Methods */
+
+ protected function getConfig( $context ) {
+ global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
+ $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang,
+ $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
+ $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
+ $wgSitename, $wgFileExtensions, $wgExtensionAssetsPath,
+ $wgResourceLoaderMaxQueryLength;
+
+ // Pre-process information
+ $separatorTransTable = $wgContLang->separatorTransformTable();
+ $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
+ $compactSeparatorTransTable = array(
+ implode( "\t", array_keys( $separatorTransTable ) ),
+ implode( "\t", $separatorTransTable ),
+ );
+ $digitTransTable = $wgContLang->digitTransformTable();
+ $digitTransTable = $digitTransTable ? $digitTransTable : array();
+ $compactDigitTransTable = array(
+ implode( "\t", array_keys( $digitTransTable ) ),
+ implode( "\t", $digitTransTable ),
+ );
+ $mainPage = Title::newMainPage();
+
+ // Build list of variables
+ $vars = array(
+ 'wgLoadScript' => $wgLoadScript,
+ 'debug' => $context->getDebug(),
+ 'skin' => $context->getSkin(),
+ 'stylepath' => $wgStylePath,
+ 'wgUrlProtocols' => wfUrlProtocols(),
+ 'wgArticlePath' => $wgArticlePath,
+ 'wgScriptPath' => $wgScriptPath,
+ 'wgScriptExtension' => $wgScriptExtension,
+ 'wgScript' => $wgScript,
+ 'wgVariantArticlePath' => $wgVariantArticlePath,
+ 'wgActionPaths' => $wgActionPaths,
+ 'wgServer' => $wgServer,
+ 'wgUserLanguage' => $context->getLanguage(),
+ 'wgContentLanguage' => $wgContLang->getCode(),
+ 'wgVersion' => $wgVersion,
+ 'wgEnableAPI' => $wgEnableAPI,
+ 'wgEnableWriteAPI' => $wgEnableWriteAPI,
+ 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
+ 'wgDigitTransformTable' => $compactDigitTransTable,
+ 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
+ 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
+ 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
+ 'wgSiteName' => $wgSitename,
+ 'wgFileExtensions' => array_values( $wgFileExtensions ),
+ 'wgDBname' => $wgDBname,
+ 'wgExtensionAssetsPath' => $wgExtensionAssetsPath,
+ 'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength,
+ );
+ if ( $wgContLang->hasVariants() ) {
+ $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
+ }
+ if ( $wgUseAjax && $wgEnableMWSuggest ) {
+ $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
+ }
+
+ wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
+
+ return $vars;
+ }
+
+ /**
+ * Gets registration code for all modules
+ *
+ * @param $context ResourceLoaderContext object
+ * @return String: JavaScript code for registering all modules with the client loader
+ */
+ public static function getModuleRegistrations( ResourceLoaderContext $context ) {
+ global $wgCacheEpoch;
+ wfProfileIn( __METHOD__ );
+
+ $out = '';
+ $registrations = array();
+ $resourceLoader = $context->getResourceLoader();
+ foreach ( $resourceLoader->getModuleNames() as $name ) {
+ $module = $resourceLoader->getModule( $name );
+ // Support module loader scripts
+ $loader = $module->getLoaderScript();
+ if ( $loader !== false ) {
+ $deps = $module->getDependencies();
+ $group = $module->getGroup();
+ $version = wfTimestamp( TS_ISO_8601_BASIC,
+ round( $module->getModifiedTime( $context ), -2 ) );
+ $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
+ }
+ // Automatically register module
+ else {
+ $mtime = max( $module->getModifiedTime( $context ), wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
+ // Modules without dependencies or a group pass two arguments (name, timestamp) to
+ // mediaWiki.loader.register()
+ if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
+ $registrations[] = array( $name, $mtime );
+ }
+ // Modules with dependencies but no group pass three arguments
+ // (name, timestamp, dependencies) to mediaWiki.loader.register()
+ else if ( $module->getGroup() === null ) {
+ $registrations[] = array(
+ $name, $mtime, $module->getDependencies() );
+ }
+ // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
+ // to mediaWiki.loader.register()
+ else {
+ $registrations[] = array(
+ $name, $mtime, $module->getDependencies(), $module->getGroup() );
+ }
+ }
+ }
+ $out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
+
+ wfProfileOut( __METHOD__ );
+ return $out;
+ }
+
+ /* Methods */
+
+ public function getScript( ResourceLoaderContext $context ) {
+ global $IP, $wgLoadScript;
+
+ $out = file_get_contents( "$IP/resources/startup.js" );
+ if ( $context->getOnly() === 'scripts' ) {
+ // Build load query for jquery and mediawiki modules
+ $query = array(
+ 'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
+ 'only' => 'scripts',
+ 'lang' => $context->getLanguage(),
+ 'skin' => $context->getSkin(),
+ 'debug' => $context->getDebug() ? 'true' : 'false',
+ 'version' => wfTimestamp( TS_ISO_8601_BASIC, round( max(
+ $context->getResourceLoader()->getModule( 'jquery' )->getModifiedTime( $context ),
+ $context->getResourceLoader()->getModule( 'mediawiki' )->getModifiedTime( $context )
+ ), -2 ) )
+ );
+ // Ensure uniform query order
+ ksort( $query );
+
+ // Startup function
+ $configuration = $this->getConfig( $context );
+ $registrations = self::getModuleRegistrations( $context );
+ $out .= "var startUp = function() {\n" .
+ "\t$registrations\n" .
+ "\t" . Xml::encodeJsCall( 'mediaWiki.config.set', array( $configuration ) ) .
+ "};\n";
+
+ // Conditional script injection
+ $scriptTag = Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) );
+ $out .= "if ( isCompatible() ) {\n" .
+ "\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
+ "}\n" .
+ "delete isCompatible;";
+ }
+
+ return $out;
+ }
+
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ global $IP, $wgCacheEpoch;
+
+ $hash = $context->getHash();
+ if ( isset( $this->modifiedTime[$hash] ) ) {
+ return $this->modifiedTime[$hash];
+ }
+
+ // Call preloadModuleInfo() on ALL modules as we're about
+ // to call getModifiedTime() on all of them
+ $loader = $context->getResourceLoader();
+ $loader->preloadModuleInfo( $loader->getModuleNames(), $context );
+
+ $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
+ // ATTENTION!: Because of the line above, this is not going to cause
+ // infinite recursion - think carefully before making changes to this
+ // code!
+ $time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
+ foreach ( $loader->getModuleNames() as $name ) {
+ $module = $loader->getModule( $name );
+ $time = max( $time, $module->getModifiedTime( $context ) );
+ }
+ return $this->modifiedTime[$hash] = $time;
+ }
+
+ public function getFlip( $context ) {
+ global $wgContLang;
+
+ return $wgContLang->getDir() !== $context->getDirection();
+ }
+
+ /* Methods */
+
+ public function getGroup() {
+ return 'startup';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php
new file mode 100644
index 00000000..c7186653
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderUserModule.php
@@ -0,0 +1,50 @@
+<?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 Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+/**
+ * Module for user customizations
+ */
+class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
+
+ /* Protected Methods */
+
+ protected function getPages( ResourceLoaderContext $context ) {
+ if ( $context->getUser() ) {
+ $username = $context->getUser();
+ return array(
+ "User:$username/common.js" => array( 'type' => 'script' ),
+ "User:$username/" . $context->getSkin() . '.js' =>
+ array( 'type' => 'script' ),
+ "User:$username/common.css" => array( 'type' => 'style' ),
+ "User:$username/" . $context->getSkin() . '.css' =>
+ array( 'type' => 'style' ),
+ );
+ }
+ return array();
+ }
+
+ /* Methods */
+
+ public function getGroup() {
+ return 'user';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
new file mode 100644
index 00000000..ae654b8f
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.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
+ * @author Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+/**
+ * Module for user preference customizations
+ */
+class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
+
+ /* Protected Members */
+
+ protected $modifiedTime = array();
+
+ /* Methods */
+
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ $hash = $context->getHash();
+ if ( isset( $this->modifiedTime[$hash] ) ) {
+ return $this->modifiedTime[$hash];
+ }
+
+ global $wgUser;
+
+ if ( $context->getUser() === $wgUser->getName() ) {
+ return $this->modifiedTime[$hash] = $wgUser->getTouched();
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Fetch the context's user options, or if it doesn't match current user,
+ * the default options.
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return Array: List of user options keyed by option name
+ */
+ protected function contextUserOptions( ResourceLoaderContext $context ) {
+ global $wgUser;
+
+ // Verify identity -- this is a private module
+ if ( $context->getUser() === $wgUser->getName() ) {
+ return $wgUser->getOptions();
+ } else {
+ return User::getDefaultOptions();
+ }
+ }
+
+ public function getScript( ResourceLoaderContext $context ) {
+ return Xml::encodeJsCall( 'mediaWiki.user.options.set',
+ array( $this->contextUserOptions( $context ) ) );
+ }
+
+ public function getStyles( ResourceLoaderContext $context ) {
+ global $wgAllowUserCssPrefs;
+
+ if ( $wgAllowUserCssPrefs ) {
+ $options = $this->contextUserOptions( $context );
+
+ // Build CSS rules
+ $rules = array();
+ if ( $options['underline'] < 2 ) {
+ $rules[] = "a { text-decoration: " .
+ ( $options['underline'] ? 'underline' : 'none' ) . "; }";
+ }
+ if ( $options['highlightbroken'] ) {
+ $rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n";
+ } else {
+ $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }";
+ $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }";
+ $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }";
+ }
+ if ( $options['justify'] ) {
+ $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
+ }
+ if ( !$options['showtoc'] ) {
+ $rules[] = "#toc { display: none; }\n";
+ }
+ if ( !$options['editsection'] ) {
+ $rules[] = ".editsection { display: none; }\n";
+ }
+ if ( $options['editfont'] !== 'default' ) {
+ $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
+ }
+ $style = implode( "\n", $rules );
+ if ( $this->getFlip( $context ) ) {
+ $style = CSSJanus::transform( $style, true, false );
+ }
+ return array( 'all' => $style );
+ }
+ return array();
+ }
+
+ public function getFlip( $context ) {
+ global $wgContLang;
+
+ return $wgContLang->getDir() !== $context->getDirection();
+ }
+
+ public function getGroup() {
+ return 'private';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
new file mode 100644
index 00000000..93e66eb0
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -0,0 +1,171 @@
+<?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 Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+defined( 'MEDIAWIKI' ) || die( 1 );
+
+/**
+ * Abstraction for resource loader modules which pull from wiki pages
+ *
+ * This can only be used for wiki pages in the MediaWiki and User namespaces,
+ * because of its dependence on the functionality of
+ * Title::isValidCssJsSubpage.
+ */
+abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
+
+ /* Protected Members */
+
+ // In-object cache for title mtimes
+ protected $titleMtimes = array();
+
+ /* Abstract Protected Methods */
+
+ abstract protected function getPages( ResourceLoaderContext $context );
+
+ /* Protected Methods */
+
+ protected function getContent( $title ) {
+ if ( $title->getNamespace() === NS_MEDIAWIKI ) {
+ $dbkey = $title->getDBkey();
+ return wfEmptyMsg( $dbkey ) ? '' : wfMsgExt( $dbkey, 'content' );
+ }
+ if ( !$title->isCssJsSubpage() ) {
+ return null;
+ }
+ $revision = Revision::newFromTitle( $title );
+ if ( !$revision ) {
+ return null;
+ }
+ return $revision->getRawText();
+ }
+
+ /* Methods */
+
+ public function getScript( ResourceLoaderContext $context ) {
+ $scripts = '';
+ foreach ( $this->getPages( $context ) as $titleText => $options ) {
+ if ( $options['type'] !== 'script' ) {
+ continue;
+ }
+ $title = Title::newFromText( $titleText );
+ if ( !$title ) {
+ continue;
+ }
+ $script = $this->getContent( $title );
+ if ( strval( $script ) !== '' ) {
+ if ( strpos( $titleText, '*/' ) === false ) {
+ $scripts .= "/* $titleText */\n";
+ }
+ $scripts .= $script . "\n";
+ }
+ }
+ return $scripts;
+ }
+
+ public function getStyles( ResourceLoaderContext $context ) {
+ global $wgScriptPath;
+
+ $styles = array();
+ foreach ( $this->getPages( $context ) as $titleText => $options ) {
+ if ( $options['type'] !== 'style' ) {
+ continue;
+ }
+ $title = Title::newFromText( $titleText );
+ if ( !$title ) {
+ continue;
+ }
+ $media = isset( $options['media'] ) ? $options['media'] : 'all';
+ $style = $this->getContent( $title );
+ if ( strval( $style ) === '' ) {
+ continue;
+ }
+ if ( $this->getFlip( $context ) ) {
+ $style = CSSJanus::transform( $style, true, false );
+ }
+ $style = CSSMin::remap( $style, false, $wgScriptPath, true );
+ if ( !isset( $styles[$media] ) ) {
+ $styles[$media] = '';
+ }
+ if ( strpos( $titleText, '*/' ) === false ) {
+ $styles[$media] .= "/* $titleText */\n";
+ }
+ $styles[$media] .= $style . "\n";
+ }
+ return $styles;
+ }
+
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
+ $mtimes = $this->getTitleMtimes( $context );
+ if ( count( $mtimes ) ) {
+ $modifiedTime = max( $modifiedTime, max( $mtimes ) );
+ }
+ return $modifiedTime;
+ }
+
+ public function isKnownEmpty( ResourceLoaderContext $context ) {
+ return count( $this->getTitleMtimes( $context ) ) == 0;
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return bool
+ */
+ public function getFlip( $context ) {
+ global $wgContLang;
+
+ return $wgContLang->getDir() !== $context->getDirection();
+ }
+
+ /**
+ * Get the modification times of all titles that would be loaded for
+ * a given context.
+ * @param $context ResourceLoaderContext: Context object
+ * @return array( prefixed DB key => UNIX timestamp ), nonexistent titles are dropped
+ */
+ protected function getTitleMtimes( ResourceLoaderContext $context ) {
+ $hash = $context->getHash();
+ if ( isset( $this->titleMtimes[$hash] ) ) {
+ return $this->titleMtimes[$hash];
+ }
+
+ $this->titleMtimes[$hash] = array();
+ $batch = new LinkBatch;
+ foreach ( $this->getPages( $context ) as $titleText => $options ) {
+ $batch->addObj( Title::newFromText( $titleText ) );
+ }
+
+ if ( !$batch->isEmpty() ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'page',
+ array( 'page_namespace', 'page_title', 'page_touched' ),
+ $batch->constructSet( 'page', $dbr ),
+ __METHOD__
+ );
+ foreach ( $res as $row ) {
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $this->titleMtimes[$hash][$title->getPrefixedDBkey()] =
+ wfTimestamp( TS_UNIX, $row->page_touched );
+ }
+ }
+ return $this->titleMtimes[$hash];
+ }
+}
diff --git a/includes/revisiondelete/RevisionDelete.php b/includes/revisiondelete/RevisionDelete.php
new file mode 100644
index 00000000..00afb053
--- /dev/null
+++ b/includes/revisiondelete/RevisionDelete.php
@@ -0,0 +1,690 @@
+<?php
+/**
+ * List for revision table items
+ */
+class RevDel_RevisionList extends RevDel_List {
+ var $currentRevId;
+ var $type = 'revision';
+ var $idField = 'rev_id';
+ var $dateField = 'rev_timestamp';
+ var $authorIdField = 'rev_user';
+ var $authorNameField = 'rev_user_text';
+
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+ return $db->select( array('revision','page'), '*',
+ array(
+ 'rev_page' => $this->title->getArticleID(),
+ 'rev_id' => $ids,
+ 'rev_page = page_id'
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_id DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDel_RevisionItem( $this, $row );
+ }
+
+ public function getCurrent() {
+ if ( is_null( $this->currentRevId ) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $this->currentRevId = $dbw->selectField(
+ 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
+ }
+ return $this->currentRevId;
+ }
+
+ public function getSuppressBit() {
+ return Revision::DELETED_RESTRICTED;
+ }
+
+ public function doPreCommitUpdates() {
+ $this->title->invalidateCache();
+ return Status::newGood();
+ }
+
+ public function doPostCommitUpdates() {
+ $this->title->purgeSquid();
+ // Extensions that require referencing previous revisions may need this
+ wfRunHooks( 'ArticleRevisionVisibilitySet', array( &$this->title ) );
+ return Status::newGood();
+ }
+}
+
+/**
+ * Item class for a revision table row
+ */
+class RevDel_RevisionItem extends RevDel_Item {
+ var $revision;
+
+ public function __construct( $list, $row ) {
+ parent::__construct( $list, $row );
+ $this->revision = new Revision( $row );
+ }
+
+ public function canView() {
+ return $this->revision->userCan( Revision::DELETED_RESTRICTED );
+ }
+
+ public function canViewContent() {
+ return $this->revision->userCan( Revision::DELETED_TEXT );
+ }
+
+ public function getBits() {
+ return $this->revision->mDeleted;
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ // Update revision table
+ $dbw->update( 'revision',
+ array( 'rev_deleted' => $bits ),
+ array(
+ 'rev_id' => $this->revision->getId(),
+ 'rev_page' => $this->revision->getPage(),
+ 'rev_deleted' => $this->getBits()
+ ),
+ __METHOD__
+ );
+ if ( !$dbw->affectedRows() ) {
+ // Concurrent fail!
+ return false;
+ }
+ // Update recentchanges table
+ $dbw->update( 'recentchanges',
+ array(
+ 'rc_deleted' => $bits,
+ 'rc_patrolled' => 1
+ ),
+ array(
+ 'rc_this_oldid' => $this->revision->getId(), // condition
+ // non-unique timestamp index
+ 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
+ ),
+ __METHOD__
+ );
+ return true;
+ }
+
+ public function isDeleted() {
+ return $this->revision->isDeleted( Revision::DELETED_TEXT );
+ }
+
+ public function isHideCurrentOp( $newBits ) {
+ return ( $newBits & Revision::DELETED_TEXT )
+ && $this->list->getCurrent() == $this->getId();
+ }
+
+ /**
+ * Get the HTML link to the revision text.
+ * Overridden by RevDel_ArchiveItem.
+ */
+ protected function getRevisionLink() {
+ global $wgLang;
+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $date;
+ }
+ return $this->special->skin->link(
+ $this->list->title,
+ $date,
+ array(),
+ array(
+ 'oldid' => $this->revision->getId(),
+ 'unhide' => 1
+ )
+ );
+ }
+
+ /**
+ * Get the HTML link to the diff.
+ * Overridden by RevDel_ArchiveItem
+ */
+ protected function getDiffLink() {
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return wfMsgHtml('diff');
+ } else {
+ return
+ $this->special->skin->link(
+ $this->list->title,
+ wfMsgHtml('diff'),
+ array(),
+ array(
+ 'diff' => $this->revision->getId(),
+ 'oldid' => 'prev',
+ 'unhide' => 1
+ ),
+ array(
+ 'known',
+ 'noclasses'
+ )
+ );
+ }
+ }
+
+ public function getHTML() {
+ $difflink = $this->getDiffLink();
+ $revlink = $this->getRevisionLink();
+ $userlink = $this->special->skin->revUserLink( $this->revision );
+ $comment = $this->special->skin->revComment( $this->revision );
+ if ( $this->isDeleted() ) {
+ $revlink = "<span class=\"history-deleted\">$revlink</span>";
+ }
+ return "<li>($difflink) $revlink $userlink $comment</li>";
+ }
+}
+
+/**
+ * List for archive table items, i.e. revisions deleted via action=delete
+ */
+class RevDel_ArchiveList extends RevDel_RevisionList {
+ var $type = 'archive';
+ var $idField = 'ar_timestamp';
+ var $dateField = 'ar_timestamp';
+ var $authorIdField = 'ar_user';
+ var $authorNameField = 'ar_user_text';
+
+ public function doQuery( $db ) {
+ $timestamps = array();
+ foreach ( $this->ids as $id ) {
+ $timestamps[] = $db->timestamp( $id );
+ }
+ return $db->select( 'archive', '*',
+ array(
+ 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey(),
+ 'ar_timestamp' => $timestamps
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'ar_timestamp DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDel_ArchiveItem( $this, $row );
+ }
+
+ public function doPreCommitUpdates() {
+ return Status::newGood();
+ }
+
+ public function doPostCommitUpdates() {
+ return Status::newGood();
+ }
+}
+
+/**
+ * Item class for a archive table row
+ */
+class RevDel_ArchiveItem extends RevDel_RevisionItem {
+ public function __construct( $list, $row ) {
+ RevDel_Item::__construct( $list, $row );
+ $this->revision = Revision::newFromArchiveRow( $row,
+ array( 'page' => $this->list->title->getArticleId() ) );
+ }
+
+ public function getId() {
+ # Convert DB timestamp to MW timestamp
+ return $this->revision->getTimestamp();
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'archive',
+ array( 'ar_deleted' => $bits ),
+ array( 'ar_namespace' => $this->list->title->getNamespace(),
+ 'ar_title' => $this->list->title->getDBkey(),
+ // use timestamp for index
+ 'ar_timestamp' => $this->row->ar_timestamp,
+ 'ar_rev_id' => $this->row->ar_rev_id,
+ 'ar_deleted' => $this->getBits()
+ ),
+ __METHOD__ );
+ return (bool)$dbw->affectedRows();
+ }
+
+ protected function getRevisionLink() {
+ global $wgLang;
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $date;
+ }
+ return $this->special->skin->link( $undelete, $date, array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'timestamp' => $this->revision->getTimestamp()
+ ) );
+ }
+
+ protected function getDiffLink() {
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return wfMsgHtml( 'diff' );
+ }
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'diff' => 'prev',
+ 'timestamp' => $this->revision->getTimestamp()
+ ) );
+ }
+}
+
+/**
+ * List for oldimage table items
+ */
+class RevDel_FileList extends RevDel_List {
+ var $type = 'oldimage';
+ var $idField = 'oi_archive_name';
+ var $dateField = 'oi_timestamp';
+ var $authorIdField = 'oi_user';
+ var $authorNameField = 'oi_user_text';
+ var $storeBatch, $deleteBatch, $cleanupBatch;
+
+ public function doQuery( $db ) {
+ $archiveNames = array();
+ foreach( $this->ids as $timestamp ) {
+ $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
+ }
+ return $db->select( 'oldimage', '*',
+ array(
+ 'oi_name' => $this->title->getDBkey(),
+ 'oi_archive_name' => $archiveNames
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'oi_timestamp DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDel_FileItem( $this, $row );
+ }
+
+ public function clearFileOps() {
+ $this->deleteBatch = array();
+ $this->storeBatch = array();
+ $this->cleanupBatch = array();
+ }
+
+ public function doPreCommitUpdates() {
+ $status = Status::newGood();
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ if ( $this->storeBatch ) {
+ $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ if ( $this->deleteBatch ) {
+ $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
+ }
+ if ( !$status->isOK() ) {
+ // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
+ // modified (but destined for rollback) causes data loss
+ return $status;
+ }
+ if ( $this->cleanupBatch ) {
+ $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
+ }
+ return $status;
+ }
+
+ public function doPostCommitUpdates() {
+ $file = wfLocalFile( $this->title );
+ $file->purgeCache();
+ $file->purgeDescription();
+ return Status::newGood();
+ }
+
+ public function getSuppressBit() {
+ return File::DELETED_RESTRICTED;
+ }
+}
+
+/**
+ * Item class for an oldimage table row
+ */
+class RevDel_FileItem extends RevDel_Item {
+ var $file;
+
+ public function __construct( $list, $row ) {
+ parent::__construct( $list, $row );
+ $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
+ }
+
+ public function getId() {
+ $parts = explode( '!', $this->row->oi_archive_name );
+ return $parts[0];
+ }
+
+ public function canView() {
+ return $this->file->userCan( File::DELETED_RESTRICTED );
+ }
+
+ public function canViewContent() {
+ return $this->file->userCan( File::DELETED_FILE );
+ }
+
+ public function getBits() {
+ return $this->file->getVisibility();
+ }
+
+ public function setBits( $bits ) {
+ # Queue the file op
+ # FIXME: move to LocalFile.php
+ if ( $this->isDeleted() ) {
+ if ( $bits & File::DELETED_FILE ) {
+ # Still deleted
+ } else {
+ # Newly undeleted
+ $key = $this->file->getStorageKey();
+ $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
+ $this->list->storeBatch[] = array(
+ $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
+ 'public',
+ $this->file->getRel()
+ );
+ $this->list->cleanupBatch[] = $key;
+ }
+ } elseif ( $bits & File::DELETED_FILE ) {
+ # Newly deleted
+ $key = $this->file->getStorageKey();
+ $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
+ $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
+ }
+
+ # Do the database operations
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'oldimage',
+ array( 'oi_deleted' => $bits ),
+ array(
+ 'oi_name' => $this->row->oi_name,
+ 'oi_timestamp' => $this->row->oi_timestamp,
+ 'oi_deleted' => $this->getBits()
+ ),
+ __METHOD__
+ );
+ return (bool)$dbw->affectedRows();
+ }
+
+ public function isDeleted() {
+ return $this->file->isDeleted( File::DELETED_FILE );
+ }
+
+ /**
+ * Get the link to the file.
+ * Overridden by RevDel_ArchivedFileItem.
+ */
+ protected function getLink() {
+ global $wgLang, $wgUser;
+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
+ if ( $this->isDeleted() ) {
+ # Hidden files...
+ if ( !$this->canViewContent() ) {
+ $link = $date;
+ } else {
+ $link = $this->special->skin->link(
+ $this->special->getTitle(),
+ $date, array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $this->file->getArchiveName(),
+ 'token' => $wgUser->editToken( $this->file->getArchiveName() )
+ )
+ );
+ }
+ return '<span class="history-deleted">' . $link . '</span>';
+ } else {
+ # Regular files...
+ return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
+ }
+ }
+ /**
+ * Generate a user tool link cluster if the current user is allowed to view it
+ * @return string HTML
+ */
+ protected function getUserTools() {
+ if( $this->file->userCan( Revision::DELETED_USER ) ) {
+ $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
+ $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
+ } else {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ }
+ if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
+ return '<span class="history-deleted">' . $link . '</span>';
+ }
+ return $link;
+ }
+
+ /**
+ * Wrap and format the file's comment block, if the current
+ * user is allowed to view it.
+ *
+ * @return string HTML
+ */
+ protected function getComment() {
+ if( $this->file->userCan( File::DELETED_COMMENT ) ) {
+ $block = $this->special->skin->commentBlock( $this->file->description );
+ } else {
+ $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+ }
+ if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
+ return "<span class=\"history-deleted\">$block</span>";
+ }
+ return $block;
+ }
+
+ public function getHTML() {
+ global $wgLang;
+ $data =
+ wfMsg(
+ 'widthheight',
+ $wgLang->formatNum( $this->file->getWidth() ),
+ $wgLang->formatNum( $this->file->getHeight() )
+ ) .
+ ' (' .
+ wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
+ ')';
+
+ return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
+ $data . ' ' . $this->getComment(). '</li>';
+ }
+}
+
+/**
+ * List for filearchive table items
+ */
+class RevDel_ArchivedFileList extends RevDel_FileList {
+ var $type = 'filearchive';
+ var $idField = 'fa_id';
+ var $dateField = 'fa_timestamp';
+ var $authorIdField = 'fa_user';
+ var $authorNameField = 'fa_user_text';
+
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+ return $db->select( 'filearchive', '*',
+ array(
+ 'fa_name' => $this->title->getDBkey(),
+ 'fa_id' => $ids
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'fa_id DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDel_ArchivedFileItem( $this, $row );
+ }
+}
+
+/**
+ * Item class for a filearchive table row
+ */
+class RevDel_ArchivedFileItem extends RevDel_FileItem {
+ public function __construct( $list, $row ) {
+ RevDel_Item::__construct( $list, $row );
+ $this->file = ArchivedFile::newFromRow( $row );
+ }
+
+ public function getId() {
+ return $this->row->fa_id;
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'filearchive',
+ array( 'fa_deleted' => $bits ),
+ array(
+ 'fa_id' => $this->row->fa_id,
+ 'fa_deleted' => $this->getBits(),
+ ),
+ __METHOD__
+ );
+ return (bool)$dbw->affectedRows();
+ }
+
+ protected function getLink() {
+ global $wgLang, $wgUser;
+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $key = $this->file->getKey();
+ # Hidden files...
+ if( !$this->canViewContent() ) {
+ $link = $date;
+ } else {
+ $link = $this->special->skin->link( $undelete, $date, array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $key,
+ 'token' => $wgUser->editToken( $key )
+ )
+ );
+ }
+ if( $this->isDeleted() ) {
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ }
+ return $link;
+ }
+}
+
+/**
+ * List for logging table items
+ */
+class RevDel_LogList extends RevDel_List {
+ var $type = 'logging';
+ var $idField = 'log_id';
+ var $dateField = 'log_timestamp';
+ var $authorIdField = 'log_user';
+ var $authorNameField = 'log_user_text';
+
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+ return $db->select( 'logging', '*',
+ array( 'log_id' => $ids ),
+ __METHOD__,
+ array( 'ORDER BY' => 'log_id DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDel_LogItem( $this, $row );
+ }
+
+ public function getSuppressBit() {
+ return Revision::DELETED_RESTRICTED;
+ }
+
+ public function getLogAction() {
+ return 'event';
+ }
+
+ public function getLogParams( $params ) {
+ return array(
+ implode( ',', $params['ids'] ),
+ "ofield={$params['oldBits']}",
+ "nfield={$params['newBits']}"
+ );
+ }
+}
+
+/**
+ * Item class for a logging table row
+ */
+class RevDel_LogItem extends RevDel_Item {
+ public function canView() {
+ return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
+ }
+
+ public function canViewContent() {
+ return true; // none
+ }
+
+ public function getBits() {
+ return $this->row->log_deleted;
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'recentchanges',
+ array(
+ 'rc_deleted' => $bits,
+ 'rc_patrolled' => 1
+ ),
+ array(
+ 'rc_logid' => $this->row->log_id,
+ 'rc_timestamp' => $this->row->log_timestamp // index
+ ),
+ __METHOD__
+ );
+ $dbw->update( 'logging',
+ array( 'log_deleted' => $bits ),
+ array(
+ 'log_id' => $this->row->log_id,
+ 'log_deleted' => $this->getBits()
+ ),
+ __METHOD__
+ );
+ return (bool)$dbw->affectedRows();
+ }
+
+ public function getHTML() {
+ global $wgLang;
+
+ $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
+ $paramArray = LogPage::extractParams( $this->row->log_params );
+ $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
+
+ // Log link for this page
+ $loglink = $this->special->skin->link(
+ SpecialPage::getTitleFor( 'Log' ),
+ wfMsgHtml( 'log' ),
+ array(),
+ array( 'page' => $title->getPrefixedText() )
+ );
+ // Action text
+ if( !$this->canView() ) {
+ $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+ } else {
+ $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
+ $this->special->skin, $paramArray, true, true );
+ if( $this->row->log_deleted & LogPage::DELETED_ACTION )
+ $action = '<span class="history-deleted">' . $action . '</span>';
+ }
+ // User links
+ $userLink = $this->special->skin->userLink( $this->row->log_user,
+ User::WhoIs( $this->row->log_user ) );
+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
+ $userLink = '<span class="history-deleted">' . $userLink . '</span>';
+ }
+ // Comment
+ $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
+ $comment = '<span class="history-deleted">' . $comment . '</span>';
+ }
+ return "<li>($loglink) $date $userLink $action $comment</li>";
+ }
+}
diff --git a/includes/revisiondelete/RevisionDeleteAbstracts.php b/includes/revisiondelete/RevisionDeleteAbstracts.php
new file mode 100644
index 00000000..073c25ba
--- /dev/null
+++ b/includes/revisiondelete/RevisionDeleteAbstracts.php
@@ -0,0 +1,454 @@
+<?php
+
+/**
+ * Abstract base class for a list of deletable items
+ */
+abstract class RevDel_List {
+ var $special, $title, $ids, $res, $current;
+ var $type = null; // override this
+ var $idField = null; // override this
+ var $dateField = false; // override this
+ var $authorIdField = false; // override this
+ var $authorNameField = false; // override this
+
+ /**
+ * @param $special The parent SpecialPage
+ * @param $title The target title
+ * @param $ids Array of IDs
+ */
+ public function __construct( $special, $title, $ids ) {
+ $this->special = $special;
+ $this->title = $title;
+ $this->ids = $ids;
+ }
+
+ /**
+ * Get the internal type name of this list. Equal to the table name.
+ */
+ public function getType() {
+ return $this->type;
+ }
+
+ /**
+ * Get the DB field name associated with the ID list
+ */
+ public function getIdField() {
+ return $this->idField;
+ }
+
+ /**
+ * Get the DB field name storing timestamps
+ */
+ public function getTimestampField() {
+ return $this->dateField;
+ }
+
+ /**
+ * Get the DB field name storing user ids
+ */
+ public function getAuthorIdField() {
+ return $this->authorIdField;
+ }
+
+ /**
+ * Get the DB field name storing user names
+ */
+ public function getAuthorNameField() {
+ return $this->authorNameField;
+ }
+ /**
+ * Set the visibility for the revisions in this list. Logging and
+ * transactions are done here.
+ *
+ * @param $params Associative array of parameters. Members are:
+ * value: The integer value to set the visibility to
+ * comment: The log comment.
+ * @return Status
+ */
+ public function setVisibility( $params ) {
+ $bitPars = $params['value'];
+ $comment = $params['comment'];
+
+ $this->res = false;
+ $dbw = wfGetDB( DB_MASTER );
+ $this->doQuery( $dbw );
+ $dbw->begin();
+ $status = Status::newGood();
+ $missing = array_flip( $this->ids );
+ $this->clearFileOps();
+ $idsForLog = array();
+ $authorIds = $authorIPs = array();
+
+ for ( $this->reset(); $this->current(); $this->next() ) {
+ $item = $this->current();
+ unset( $missing[ $item->getId() ] );
+
+ $oldBits = $item->getBits();
+ // Build the actual new rev_deleted bitfield
+ $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
+
+ if ( $oldBits == $newBits ) {
+ $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ } elseif ( $oldBits == 0 && $newBits != 0 ) {
+ $opType = 'hide';
+ } elseif ( $oldBits != 0 && $newBits == 0 ) {
+ $opType = 'show';
+ } else {
+ $opType = 'modify';
+ }
+
+ if ( $item->isHideCurrentOp( $newBits ) ) {
+ // Cannot hide current version text
+ $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ }
+ if ( !$item->canView() ) {
+ // Cannot access this revision
+ $msg = ($opType == 'show') ?
+ 'revdelete-show-no-access' : 'revdelete-modify-no-access';
+ $status->error( $msg, $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ }
+ // Cannot just "hide from Sysops" without hiding any fields
+ if( $newBits == Revision::DELETED_RESTRICTED ) {
+ $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ }
+
+ // Update the revision
+ $ok = $item->setBits( $newBits );
+
+ if ( $ok ) {
+ $idsForLog[] = $item->getId();
+ $status->successCount++;
+ if( $item->getAuthorId() > 0 ) {
+ $authorIds[] = $item->getAuthorId();
+ } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
+ $authorIPs[] = $item->getAuthorName();
+ }
+ } else {
+ $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ }
+ }
+
+ // Handle missing revisions
+ foreach ( $missing as $id => $unused ) {
+ $status->error( 'revdelete-modify-missing', $id );
+ $status->failCount++;
+ }
+
+ if ( $status->successCount == 0 ) {
+ $status->ok = false;
+ $dbw->rollback();
+ return $status;
+ }
+
+ // Save success count
+ $successCount = $status->successCount;
+
+ // Move files, if there are any
+ $status->merge( $this->doPreCommitUpdates() );
+ if ( !$status->isOK() ) {
+ // Fatal error, such as no configured archive directory
+ $dbw->rollback();
+ return $status;
+ }
+
+ // Log it
+ $this->updateLog( array(
+ 'title' => $this->title,
+ 'count' => $successCount,
+ 'newBits' => $newBits,
+ 'oldBits' => $oldBits,
+ 'comment' => $comment,
+ 'ids' => $idsForLog,
+ 'authorIds' => $authorIds,
+ 'authorIPs' => $authorIPs
+ ) );
+ $dbw->commit();
+
+ // Clear caches
+ $status->merge( $this->doPostCommitUpdates() );
+ return $status;
+ }
+
+ /**
+ * Reload the list data from the master DB. This can be done after setVisibility()
+ * to allow $item->getHTML() to show the new data.
+ */
+ function reloadFromMaster() {
+ $dbw = wfGetDB( DB_MASTER );
+ $this->res = $this->doQuery( $dbw );
+ }
+
+ /**
+ * Record a log entry on the action
+ * @param $params Associative array of parameters:
+ * newBits: The new value of the *_deleted bitfield
+ * oldBits: The old value of the *_deleted bitfield.
+ * title: The target title
+ * ids: The ID list
+ * comment: The log comment
+ * authorsIds: The array of the user IDs of the offenders
+ * authorsIPs: The array of the IP/anon user offenders
+ */
+ protected function updateLog( $params ) {
+ // Get the URL param's corresponding DB field
+ $field = RevisionDeleter::getRelationType( $this->getType() );
+ if( !$field ) {
+ throw new MWException( "Bad log URL param type!" );
+ }
+ // Put things hidden from sysops in the oversight log
+ if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
+ $logType = 'suppress';
+ } else {
+ $logType = 'delete';
+ }
+ // Add params for effected 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 );
+ // 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 );
+ }
+
+ /**
+ * Get the log action for this list type
+ */
+ public function getLogAction() {
+ return 'revision';
+ }
+
+ /**
+ * Get log parameter array.
+ * @param $params Associative array of log parameters, same as updateLog()
+ * @return array
+ */
+ public function getLogParams( $params ) {
+ return array(
+ $this->getType(),
+ implode( ',', $params['ids'] ),
+ "ofield={$params['oldBits']}",
+ "nfield={$params['newBits']}"
+ );
+ }
+
+ /**
+ * Initialise the current iteration pointer
+ */
+ protected function initCurrent() {
+ $row = $this->res->current();
+ if ( $row ) {
+ $this->current = $this->newItem( $row );
+ } else {
+ $this->current = false;
+ }
+ }
+
+ /**
+ * Start iteration. This must be called before current() or next().
+ * @return First list item
+ */
+ public function reset() {
+ if ( !$this->res ) {
+ $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
+ } else {
+ $this->res->rewind();
+ }
+ $this->initCurrent();
+ return $this->current;
+ }
+
+ /**
+ * Get the current list item, or false if we are at the end
+ */
+ public function current() {
+ return $this->current;
+ }
+
+ /**
+ * Move the iteration pointer to the next list item, and return it.
+ */
+ public function next() {
+ $this->res->next();
+ $this->initCurrent();
+ return $this->current;
+ }
+
+ /**
+ * Get the number of items in the list.
+ */
+ public function length() {
+ if( !$this->res ) {
+ return 0;
+ } else {
+ return $this->res->numRows();
+ }
+ }
+
+ /**
+ * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
+ * STUB
+ */
+ public function clearFileOps() {
+ }
+
+ /**
+ * A hook for setVisibility(): do batch updates pre-commit.
+ * STUB
+ * @return Status
+ */
+ public function doPreCommitUpdates() {
+ return Status::newGood();
+ }
+
+ /**
+ * A hook for setVisibility(): do any necessary updates post-commit.
+ * STUB
+ * @return Status
+ */
+ public function doPostCommitUpdates() {
+ return Status::newGood();
+ }
+
+ /**
+ * Create an item object from a DB result row
+ * @param $row stdclass
+ */
+ abstract public function newItem( $row );
+
+ /**
+ * Do the DB query to iterate through the objects.
+ * @param $db Database object to use for the query
+ */
+ abstract public function doQuery( $db );
+
+ /**
+ * Get the integer value of the flag used for suppression
+ */
+ abstract public function getSuppressBit();
+}
+
+/**
+ * Abstract base class for deletable items
+ */
+abstract class RevDel_Item {
+ /** The parent SpecialPage */
+ var $special;
+
+ /** The parent RevDel_List */
+ var $list;
+
+ /** The DB result row */
+ var $row;
+
+ /**
+ * @param $list RevDel_List
+ * @param $row DB result row
+ */
+ public function __construct( $list, $row ) {
+ $this->special = $list->special;
+ $this->list = $list;
+ $this->row = $row;
+ }
+
+ /**
+ * Get the ID, as it would appear in the ids URL parameter
+ */
+ public function getId() {
+ $field = $this->list->getIdField();
+ return $this->row->$field;
+ }
+
+ /**
+ * Get the date, formatted with $wgLang
+ */
+ public function formatDate() {
+ global $wgLang;
+ return $wgLang->date( $this->getTimestamp() );
+ }
+
+ /**
+ * Get the time, formatted with $wgLang
+ */
+ public function formatTime() {
+ global $wgLang;
+ return $wgLang->time( $this->getTimestamp() );
+ }
+
+ /**
+ * Get the timestamp in MW 14-char form
+ */
+ public function getTimestamp() {
+ $field = $this->list->getTimestampField();
+ return wfTimestamp( TS_MW, $this->row->$field );
+ }
+
+ /**
+ * Get the author user ID
+ */
+ public function getAuthorId() {
+ $field = $this->list->getAuthorIdField();
+ return intval( $this->row->$field );
+ }
+
+ /**
+ * Get the author user name
+ */
+ public function getAuthorName() {
+ $field = $this->list->getAuthorNameField();
+ return strval( $this->row->$field );
+ }
+
+ /**
+ * Returns true if the item is "current", and the operation to set the given
+ * bits can't be executed for that reason
+ * STUB
+ */
+ public function isHideCurrentOp( $newBits ) {
+ return false;
+ }
+
+ /**
+ * Returns true if the current user can view the item
+ */
+ abstract public function canView();
+
+ /**
+ * Returns true if the current user can view the item text/file
+ */
+ abstract public function canViewContent();
+
+ /**
+ * Get the current deletion bitfield value
+ */
+ abstract public function getBits();
+
+ /**
+ * Get the HTML of the list item. Should be include <li></li> tags.
+ * This is used to show the list in HTML form, by the special page.
+ */
+ abstract public function getHTML();
+
+ /**
+ * Set the visibility of the item. This should do any necessary DB queries.
+ *
+ * The DB update query should have a condition which forces it to only update
+ * if the value in the DB matches the value fetched earlier with the SELECT.
+ * If the update fails because it did not match, the function should return
+ * false. This prevents concurrency problems.
+ *
+ * @return boolean success
+ */
+ abstract public function setBits( $newBits );
+}
diff --git a/includes/revisiondelete/RevisionDeleter.php b/includes/revisiondelete/RevisionDeleter.php
new file mode 100644
index 00000000..d47fcecf
--- /dev/null
+++ b/includes/revisiondelete/RevisionDeleter.php
@@ -0,0 +1,270 @@
+<?php
+/**
+ * Revision/log/file deletion backend
+ *
+ * @file
+ */
+
+/**
+ * Temporary b/c interface, collection of static functions.
+ * @ingroup SpecialPage
+ */
+class RevisionDeleter {
+ /**
+ * Checks for a change in the bitfield for a certain option and updates the
+ * provided array accordingly.
+ *
+ * @param $desc String: description to add to the array if the option was
+ * enabled / disabled.
+ * @param $field Integer: the bitmask describing the single option.
+ * @param $diff Integer: the xor of the old and new bitfields.
+ * @param $new Integer: the new bitfield
+ * @param $arr Array: the array to update.
+ */
+ protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
+ if( $diff & $field ) {
+ $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
+ }
+ }
+
+ /**
+ * Gets an array of message keys describing the changes made to the visibility
+ * of the revision. If the resulting array is $arr, then $arr[0] will contain an
+ * array of strings describing the items that were hidden, $arr[2] will contain
+ * an array of strings describing the items that were unhidden, and $arr[3] will
+ * contain an array with a single string, which can be one of "applied
+ * restrictions to sysops", "removed restrictions from sysops", or null.
+ *
+ * @param $n Integer: the new bitfield.
+ * @param $o Integer: the old bitfield.
+ * @return An array as described above.
+ */
+ protected static function getChanges( $n, $o ) {
+ $diff = $n ^ $o;
+ $ret = array( 0 => array(), 1 => array(), 2 => array() );
+ // Build bitfield changes in language
+ self::checkItem( 'revdelete-content',
+ Revision::DELETED_TEXT, $diff, $n, $ret );
+ self::checkItem( 'revdelete-summary',
+ Revision::DELETED_COMMENT, $diff, $n, $ret );
+ self::checkItem( 'revdelete-uname',
+ Revision::DELETED_USER, $diff, $n, $ret );
+ // Restriction application to sysops
+ if( $diff & Revision::DELETED_RESTRICTED ) {
+ if( $n & Revision::DELETED_RESTRICTED )
+ $ret[2][] = 'revdelete-restricted';
+ else
+ $ret[2][] = 'revdelete-unrestricted';
+ }
+ return $ret;
+ }
+
+ /**
+ * Gets a log message to describe the given revision visibility change. This
+ * message will be of the form "[hid {content, edit summary, username}];
+ * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
+ *
+ * @param $count Integer: The number of effected revisions.
+ * @param $nbitfield Integer: The new bitfield for the revision.
+ * @param $obitfield Integer: The old bitfield for the revision.
+ * @param $isForLog Boolean
+ * @param $forContent Boolean
+ */
+ public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false, $forContent = false ) {
+ global $wgLang, $wgContLang;
+
+ $lang = $forContent ? $wgContLang : $wgLang;
+ $msgFunc = $forContent ? "wfMsgForContent" : "wfMsg";
+
+ $changes = self::getChanges( $nbitfield, $obitfield );
+ array_walk($changes, 'RevisionDeleter::expandMessageArray', $forContent);
+
+ $changesText = array();
+
+ if( count( $changes[0] ) ) {
+ $changesText[] = $msgFunc( 'revdelete-hid', $lang->commaList( $changes[0] ) );
+ }
+ if( count( $changes[1] ) ) {
+ $changesText[] = $msgFunc( 'revdelete-unhid', $lang->commaList( $changes[1] ) );
+ }
+
+ $s = $lang->semicolonList( $changesText );
+ if( count( $changes[2] ) ) {
+ $s .= $s ? ' (' . $changes[2][0] . ')' : ' ' . $changes[2][0];
+ }
+
+ $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
+ return wfMsgExt( $msg, $forContent ? array( 'parsemag', 'content' ) : array( 'parsemag' ), $s, $lang->formatNum($count) );
+ }
+
+ private static function expandMessageArray(& $msg, $key, $forContent) {
+ if ( is_array ($msg) ) {
+ array_walk($msg, 'RevisionDeleter::expandMessageArray', $forContent);
+ } else {
+ if ( $forContent ) {
+ $msg = wfMsgForContent($msg);
+ } else {
+ $msg = wfMsg($msg);
+ }
+ }
+ }
+
+ // Get DB field name for URL param...
+ // Future code for other things may also track
+ // other types of revision-specific changes.
+ // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
+ public static function getRelationType( $typeName ) {
+ if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
+ $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
+ }
+ if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
+ $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
+ $list = new $class( null, null, null );
+ return $list->getIdField();
+ } else {
+ return null;
+ }
+ }
+
+ // Checks if a revision still exists in the revision table.
+ // If it doesn't, returns the corresponding ar_timestamp field
+ // so that this key can be used instead.
+ public static function checkRevisionExistence( $title, $revid ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $exists = $dbr->selectField( 'revision', '1',
+ array( 'rev_id' => $revid ), __METHOD__ );
+
+ if ( $exists ) {
+ return true;
+ }
+
+ $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
+ array( 'ar_namespace' => $title->getNamespace(),
+ 'ar_title' => $title->getDBkey(),
+ 'ar_rev_id' => $revid ), __METHOD__ );
+
+ return $timestamp;
+ }
+
+ // Creates utility links for log entries.
+ public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
+ global $wgLang;
+
+ if( count($paramArray) >= 2 ) {
+ // Different revision types use different URL params...
+ $originalKey = $key = $paramArray[0];
+ // $paramArray[1] is a CSV of the IDs
+ $Ids = explode( ',', $paramArray[1] );
+
+ $revert = array();
+
+ // For if undeleted revisions are found amidst deleted ones.
+ $undeletedRevisions = array();
+
+ // This is not going to work if some revs are deleted and some
+ // aren't.
+ if ($key == 'revision') {
+ foreach( $Ids as $k => $id ) {
+ $existResult =
+ self::checkRevisionExistence( $title, $id );
+
+ if ($existResult !== true) {
+ $key = 'archive';
+ $Ids[$k] = $existResult;
+ } else {
+ // Undeleted revision amidst deleted ones
+ unset($Ids[$k]);
+ $undeletedRevisions[] = $id;
+ }
+ }
+
+ if ( $key == $originalKey ) {
+ $Ids = $undeletedRevisions;
+ $undeletedRevisions = array();
+ }
+ }
+
+ // Diff link for single rev deletions
+ if( count($Ids) == 1 && !count($undeletedRevisions) ) {
+ // Live revision diffs...
+ if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
+ $revert[] = $skin->link(
+ $title,
+ $messages['diff'],
+ array(),
+ array(
+ 'diff' => intval( $Ids[0] ),
+ 'unhide' => 1
+ ),
+ array( 'known', 'noclasses' )
+ );
+ // Deleted revision diffs...
+ } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
+ $revert[] = $skin->link(
+ SpecialPage::getTitleFor( 'Undelete' ),
+ $messages['diff'],
+ array(),
+ array(
+ 'target' => $title->getPrefixedDBKey(),
+ 'diff' => 'prev',
+ 'timestamp' => $Ids[0]
+ ),
+ array( 'known', 'noclasses' )
+ );
+ }
+ }
+
+ // View/modify link...
+ if ( count($undeletedRevisions) ) {
+ // FIXME THIS IS A HORRIBLE HORRIBLE HACK AND SHOULD DIE
+ // It's not possible to pass a list of both deleted and
+ // undeleted revisions to SpecialRevisionDelete, so we're
+ // stuck with two links. See bug 23363.
+ $restoreLinks = array();
+
+ $restoreLinks[] = $skin->link(
+ SpecialPage::getTitleFor( 'Revisiondelete' ),
+ $messages['revdel-restore-visible'],
+ array(),
+ array(
+ 'target' => $title->getPrefixedText(),
+ 'type' => $originalKey,
+ 'ids' => implode(',', $undeletedRevisions),
+ ),
+ array( 'known', 'noclasses' )
+ );
+
+ $restoreLinks[] = $skin->link(
+ SpecialPage::getTitleFor( 'Revisiondelete' ),
+ $messages['revdel-restore-deleted'],
+ array(),
+ array(
+ 'target' => $title->getPrefixedText(),
+ 'type' => $key,
+ 'ids' => implode(',', $Ids),
+ ),
+ array( 'known', 'noclasses' )
+ );
+
+ $revert[] = $messages['revdel-restore'] . ' [' .
+ $wgLang->pipeList( $restoreLinks ) . ']';
+ } else {
+ $revert[] = $skin->link(
+ SpecialPage::getTitleFor( 'Revisiondelete' ),
+ $messages['revdel-restore'],
+ array(),
+ array(
+ 'target' => $title->getPrefixedText(),
+ 'type' => $key,
+ 'ids' => implode(',', $Ids),
+ ),
+ array( 'known', 'noclasses' )
+ );
+ }
+
+ // Pipe links
+ return wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
+ }
+ return '';
+ }
+} \ No newline at end of file
diff --git a/includes/search/SearchEngine.php b/includes/search/SearchEngine.php
index f4ca700d..17482da2 100644
--- a/includes/search/SearchEngine.php
+++ b/includes/search/SearchEngine.php
@@ -1,12 +1,16 @@
<?php
/**
- * @defgroup Search Search
+ * Basic search engine
*
* @file
* @ingroup Search
*/
/**
+ * @defgroup Search Search
+ */
+
+/**
* Contain a class for special pages
* @ingroup Search
*/
@@ -18,6 +22,14 @@ class SearchEngine {
var $namespaces = array( NS_MAIN );
var $showRedirects = false;
+ function __construct($db = null) {
+ if ( $db ) {
+ $this->db = $db;
+ } else {
+ $this->db = wfGetDB( DB_SLAVE );
+ }
+ }
+
/**
* Perform a full text search query and return a result set.
* If title searches are not supported or disabled, return null.
@@ -41,12 +53,12 @@ class SearchEngine {
function searchTitle( $term ) {
return null;
}
-
+
/** If this search backend can list/unlist redirects */
function acceptListRedirects() {
return true;
}
-
+
/**
* When overridden in derived class, performs database-specific conversions
* on text to be used for searching or updating search index.
@@ -56,7 +68,10 @@ class SearchEngine {
* @return string
*/
public function normalizeText( $string ) {
- return $string;
+ global $wgContLang;
+
+ // Some languages such as Chinese require word segmentation
+ return $wgContLang->segmentByWord( $string );
}
/**
@@ -66,7 +81,7 @@ class SearchEngine {
function transformSearchTerm( $term ) {
return $term;
}
-
+
/**
* If an exact title match can be found, or a very slightly close match,
* return the title. If no match, returns NULL.
@@ -76,41 +91,53 @@ class SearchEngine {
*/
public static function getNearMatch( $searchterm ) {
$title = self::getNearMatchInternal( $searchterm );
-
+
wfRunHooks( 'SearchGetNearMatchComplete', array( $searchterm, &$title ) );
return $title;
}
/**
+ * Do a near match (see SearchEngine::getNearMatch) and wrap it into a
+ * SearchResultSet.
+ *
+ * @param $searchterm string
+ * @return SearchResultSet
+ */
+ public static function getNearMatchResultSet( $searchterm ) {
+ return new SearchNearMatchResultSet( self::getNearMatch( $searchterm ) );
+ }
+
+ /**
* Really find the title match.
*/
private static function getNearMatchInternal( $searchterm ) {
global $wgContLang;
- $allSearchTerms = array($searchterm);
+ $allSearchTerms = array( $searchterm );
if ( $wgContLang->hasVariants() ) {
- $allSearchTerms = array_merge($allSearchTerms,$wgContLang->convertLinkToAllVariants($searchterm));
+ $allSearchTerms = array_merge( $allSearchTerms, $wgContLang->autoConvertToAllVariants( $searchterm ) );
}
- if( !wfRunHooks( 'SearchGetNearMatchBefore', array( $allSearchTerms, &$titleResult ) ) ) {
+ $titleResult = null;
+ if ( !wfRunHooks( 'SearchGetNearMatchBefore', array( $allSearchTerms, &$titleResult ) ) ) {
return $titleResult;
}
- foreach($allSearchTerms as $term) {
+ foreach ( $allSearchTerms as $term ) {
# Exact match? No need to look further.
$title = Title::newFromText( $term );
- if (is_null($title))
+ if ( is_null( $title ) )
return null;
if ( $title->getNamespace() == NS_SPECIAL || $title->isExternal() || $title->exists() ) {
return $title;
}
-
+
# See if it still otherwise has content is some sane sense
$article = MediaWiki::articleFromTitle( $title );
- if( $article->hasViewableContent() ) {
+ if ( $article->hasViewableContent() ) {
return $title;
}
@@ -136,14 +163,14 @@ class SearchEngine {
}
# Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
- $title = Title::newFromText( $wgContLang->ucwordbreaks($term) );
+ $title = Title::newFromText( $wgContLang->ucwordbreaks( $term ) );
if ( $title && $title->exists() ) {
return $title;
}
// Give hooks a chance at better match variants
$title = null;
- if( !wfRunHooks( 'SearchGetNearMatch', array( $term, &$title ) ) ) {
+ if ( !wfRunHooks( 'SearchGetNearMatch', array( $term, &$title ) ) ) {
return $title;
}
}
@@ -151,7 +178,7 @@ class SearchEngine {
$title = Title::newFromText( $searchterm );
# Entering an IP address goes to the contributions page
- if ( ( $title->getNamespace() == NS_USER && User::isIP($title->getText() ) )
+ if ( ( $title->getNamespace() == NS_USER && User::isIP( $title->getText() ) )
|| User::isIP( trim( $searchterm ) ) ) {
return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
}
@@ -165,22 +192,22 @@ class SearchEngine {
# Go to images that exist even if there's no local page.
# There may have been a funny upload, or it may be on a shared
# file repository such as Wikimedia Commons.
- if( $title->getNamespace() == NS_FILE ) {
+ if ( $title->getNamespace() == NS_FILE ) {
$image = wfFindFile( $title );
- if( $image ) {
+ if ( $image ) {
return $title;
}
}
# MediaWiki namespace? Page may be "implied" if not customized.
# Just return it, with caps forced as the message system likes it.
- if( $title->getNamespace() == NS_MEDIAWIKI ) {
+ if ( $title->getNamespace() == NS_MEDIAWIKI ) {
return Title::makeTitle( NS_MEDIAWIKI, $wgContLang->ucfirst( $title->getText() ) );
}
# Quoted term? Try without the quotes...
$matches = array();
- if( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
+ if ( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
return SearchEngine::getNearMatch( $matches[1] );
}
@@ -219,28 +246,28 @@ class SearchEngine {
*
* @param $query String
*/
- function replacePrefixes( $query ){
+ function replacePrefixes( $query ) {
global $wgContLang;
$parsed = $query;
- if( strpos($query,':') === false ) { // nothing to do
+ if ( strpos( $query, ':' ) === false ) { // nothing to do
wfRunHooks( 'SearchEngineReplacePrefixesComplete', array( $this, $query, &$parsed ) );
return $parsed;
}
-
- $allkeyword = wfMsgForContent('searchall').":";
- if( strncmp($query, $allkeyword, strlen($allkeyword)) == 0 ){
+
+ $allkeyword = wfMsgForContent( 'searchall' ) . ":";
+ if ( strncmp( $query, $allkeyword, strlen( $allkeyword ) ) == 0 ) {
$this->namespaces = null;
- $parsed = substr($query,strlen($allkeyword));
- } else if( strpos($query,':') !== false ) {
- $prefix = substr($query,0,strpos($query,':'));
- $index = $wgContLang->getNsIndex($prefix);
- if($index !== false){
- $this->namespaces = array($index);
- $parsed = substr($query,strlen($prefix)+1);
+ $parsed = substr( $query, strlen( $allkeyword ) );
+ } else if ( strpos( $query, ':' ) !== false ) {
+ $prefix = substr( $query, 0, strpos( $query, ':' ) );
+ $index = $wgContLang->getNsIndex( $prefix );
+ if ( $index !== false ) {
+ $this->namespaces = array( $index );
+ $parsed = substr( $query, strlen( $prefix ) + 1 );
}
}
- if(trim($parsed) == '')
+ if ( trim( $parsed ) == '' )
$parsed = $query; // prefix was the whole query
wfRunHooks( 'SearchEngineReplacePrefixesComplete', array( $this, $query, &$parsed ) );
@@ -255,16 +282,16 @@ class SearchEngine {
public static function searchableNamespaces() {
global $wgContLang;
$arr = array();
- foreach( $wgContLang->getNamespaces() as $ns => $name ) {
- if( $ns >= NS_MAIN ) {
+ foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
+ if ( $ns >= NS_MAIN ) {
$arr[$ns] = $name;
}
}
-
+
wfRunHooks( 'SearchableNamespaces', array( &$arr ) );
return $arr;
}
-
+
/**
* Extract default namespaces to search from the given user's
* settings, returning a list of index numbers.
@@ -274,78 +301,78 @@ class SearchEngine {
*/
public static function userNamespaces( $user ) {
global $wgSearchEverythingOnlyLoggedIn;
-
+
// get search everything preference, that can be set to be read for logged-in users
$searcheverything = false;
- if( ( $wgSearchEverythingOnlyLoggedIn && $user->isLoggedIn() )
+ if ( ( $wgSearchEverythingOnlyLoggedIn && $user->isLoggedIn() )
|| !$wgSearchEverythingOnlyLoggedIn )
- $searcheverything = $user->getOption('searcheverything');
-
- // searcheverything overrides other options
- if( $searcheverything )
- return array_keys(SearchEngine::searchableNamespaces());
-
+ $searcheverything = $user->getOption( 'searcheverything' );
+
+ // searcheverything overrides other options
+ if ( $searcheverything )
+ return array_keys( SearchEngine::searchableNamespaces() );
+
$arr = Preferences::loadOldSearchNs( $user );
$searchableNamespaces = SearchEngine::searchableNamespaces();
-
- $arr = array_intersect( $arr, array_keys($searchableNamespaces) ); // Filter
-
+
+ $arr = array_intersect( $arr, array_keys( $searchableNamespaces ) ); // Filter
+
return $arr;
}
-
+
/**
* Find snippet highlight settings for a given user
*
* @param $user User
- * @return Array contextlines, contextchars
+ * @return Array contextlines, contextchars
*/
- public static function userHighlightPrefs( &$user ){
- //$contextlines = $user->getOption( 'contextlines', 5 );
- //$contextchars = $user->getOption( 'contextchars', 50 );
+ public static function userHighlightPrefs( &$user ) {
+ // $contextlines = $user->getOption( 'contextlines', 5 );
+ // $contextchars = $user->getOption( 'contextchars', 50 );
$contextlines = 2; // Hardcode this. Old defaults sucked. :)
$contextchars = 75; // same as above.... :P
- return array($contextlines, $contextchars);
+ return array( $contextlines, $contextchars );
}
-
+
/**
* An array of namespaces indexes to be searched by default
- *
- * @return Array
+ *
+ * @return Array
*/
- public static function defaultNamespaces(){
+ public static function defaultNamespaces() {
global $wgNamespacesToBeSearchedDefault;
-
- return array_keys($wgNamespacesToBeSearchedDefault, true);
+
+ return array_keys( $wgNamespacesToBeSearchedDefault, true );
}
-
+
/**
* Get a list of namespace names useful for showing in tooltips
* and preferences
*
* @param $namespaces Array
*/
- public static function namespacesAsText( $namespaces ){
+ public static function namespacesAsText( $namespaces ) {
global $wgContLang;
-
- $formatted = array_map( array($wgContLang,'getFormattedNsText'), $namespaces );
- foreach( $formatted as $key => $ns ){
- if ( empty($ns) )
+
+ $formatted = array_map( array( $wgContLang, 'getFormattedNsText' ), $namespaces );
+ foreach ( $formatted as $key => $ns ) {
+ if ( empty( $ns ) )
$formatted[$key] = wfMsg( 'blanknamespace' );
}
return $formatted;
}
-
+
/**
* Return the help namespaces to be shown on Special:Search
- *
- * @return Array
+ *
+ * @return Array
*/
public static function helpNamespaces() {
global $wgNamespacesToBeSearchedHelp;
-
+
return array_keys( $wgNamespacesToBeSearchedHelp, true );
}
-
+
/**
* Return a 'cleaned up' search string
*
@@ -364,14 +391,15 @@ class SearchEngine {
*/
public static function create() {
global $wgSearchType;
- $dbr = wfGetDB( DB_SLAVE );
- if( $wgSearchType ) {
+ $dbr = null;
+ if ( $wgSearchType ) {
$class = $wgSearchType;
} else {
+ $dbr = wfGetDB( DB_SLAVE );
$class = $dbr->getSearchEngine();
}
$search = new $class( $dbr );
- $search->setLimitOffset(0,0);
+ $search->setLimitOffset( 0, 0 );
return $search;
}
@@ -399,34 +427,34 @@ class SearchEngine {
function updateTitle( $id, $title ) {
// no-op
}
-
+
/**
* Get OpenSearch suggestion template
- *
+ *
* @return String
*/
public static function getOpenSearchTemplate() {
- global $wgOpenSearchTemplate, $wgServer, $wgScriptPath;
- if( $wgOpenSearchTemplate ) {
+ global $wgOpenSearchTemplate, $wgServer;
+ if ( $wgOpenSearchTemplate ) {
return $wgOpenSearchTemplate;
- } else {
+ } else {
$ns = implode( '|', SearchEngine::defaultNamespaces() );
- if( !$ns ) $ns = "0";
- return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace='.$ns;
+ if ( !$ns ) $ns = "0";
+ return $wgServer . wfScript( 'api' ) . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
}
}
-
+
/**
- * Get internal MediaWiki Suggest template
- *
+ * Get internal MediaWiki Suggest template
+ *
* @return String
*/
public static function getMWSuggestTemplate() {
- global $wgMWSuggestTemplate, $wgServer, $wgScriptPath;
- if($wgMWSuggestTemplate)
+ global $wgMWSuggestTemplate, $wgServer;
+ if ( $wgMWSuggestTemplate )
return $wgMWSuggestTemplate;
- else
- return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace={namespaces}&suggest';
+ else
+ return $wgServer . wfScript( 'api' ) . '?action=opensearch&search={searchTerms}&namespace={namespaces}&suggest';
}
}
@@ -486,27 +514,27 @@ class SearchResultSet {
/**
* @return String: suggested query, null if none
*/
- function getSuggestionQuery(){
+ function getSuggestionQuery() {
return null;
}
/**
* @return String: HTML highlighted suggested query, '' if none
*/
- function getSuggestionSnippet(){
+ function getSuggestionSnippet() {
return '';
}
-
+
/**
* Return information about how and from where the results were fetched,
- * should be useful for diagnostics and debugging
+ * should be useful for diagnostics and debugging
*
* @return String
*/
function getInfo() {
return null;
}
-
+
/**
* Return a result set of hits on other (multiple) wikis associated with this one
*
@@ -515,7 +543,7 @@ class SearchResultSet {
function getInterwikiResults() {
return null;
}
-
+
/**
* Check if there are results on other wikis
*
@@ -524,7 +552,6 @@ class SearchResultSet {
function hasInterwikiResults() {
return $this->getInterwikiResults() != null;
}
-
/**
* Fetches next search result, or false.
@@ -558,20 +585,27 @@ class SqlSearchResultSet extends SearchResultSet {
}
function numRows() {
+ if ( $this->mResultSet === false )
+ return false;
+
return $this->mResultSet->numRows();
}
function next() {
- if ($this->mResultSet === false )
+ if ( $this->mResultSet === false )
return false;
$row = $this->mResultSet->fetchObject();
- if ($row === false)
+ if ( $row === false )
return false;
- return new SearchResult($row);
+
+ return SearchResult::newFromRow( $row );
}
function free() {
+ if ( $this->mResultSet === false )
+ return false;
+
$this->mResultSet->free();
}
}
@@ -580,7 +614,7 @@ class SqlSearchResultSet extends SearchResultSet {
* @ingroup Search
*/
class SearchResultTooMany {
- ## Some search engines may bail out if too many matches are found
+ # # Some search engines may bail out if too many matches are found
}
@@ -594,32 +628,78 @@ class SearchResult {
var $mRevision = null;
var $mImage = null;
- function __construct( $row ) {
- $this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
- if( !is_null($this->mTitle) ){
+ /**
+ * Return a new SearchResult and initializes it with a title.
+ *
+ * @param $title Title
+ * @return SearchResult
+ */
+ public static function newFromTitle( $title ) {
+ $result = new self();
+ $result->initFromTitle( $title );
+ return $result;
+ }
+ /**
+ * Return a new SearchResult and initializes it with a row.
+ *
+ * @param $row object
+ * @return SearchResult
+ */
+ public static function newFromRow( $row ) {
+ $result = new self();
+ $result->initFromRow( $row );
+ return $result;
+ }
+
+ public function __construct( $row = null ) {
+ if ( !is_null( $row ) ) {
+ // Backwards compatibility with pre-1.17 callers
+ $this->initFromRow( $row );
+ }
+ }
+
+ /**
+ * Initialize from a database row. Makes a Title and passes that to
+ * initFromTitle.
+ *
+ * @param $row object
+ */
+ protected function initFromRow( $row ) {
+ $this->initFromTitle( Title::makeTitle( $row->page_namespace, $row->page_title ) );
+ }
+
+ /**
+ * Initialize from a Title and if possible initializes a corresponding
+ * Revision and File.
+ *
+ * @param $title Title
+ */
+ protected function initFromTitle( $title ) {
+ $this->mTitle = $title;
+ if ( !is_null( $this->mTitle ) ) {
$this->mRevision = Revision::newFromTitle( $this->mTitle );
- if( $this->mTitle->getNamespace() === NS_FILE )
+ if ( $this->mTitle->getNamespace() === NS_FILE )
$this->mImage = wfFindFile( $this->mTitle );
}
}
-
+
/**
* Check if this is result points to an invalid title
*
* @return Boolean
*/
- function isBrokenTitle(){
- if( is_null($this->mTitle) )
+ function isBrokenTitle() {
+ if ( is_null( $this->mTitle ) )
return true;
return false;
}
-
+
/**
* Check if target page is missing, happens when index is out of date
- *
+ *
* @return Boolean
*/
- function isMissingRevision(){
+ function isMissingRevision() {
return !$this->mRevision && !$this->mImage;
}
@@ -640,36 +720,36 @@ class SearchResult {
/**
* Lazy initialization of article text from DB
*/
- protected function initText(){
- if( !isset($this->mText) ){
- if($this->mRevision != null)
+ protected function initText() {
+ if ( !isset( $this->mText ) ) {
+ if ( $this->mRevision != null )
$this->mText = $this->mRevision->getText();
else // TODO: can we fetch raw wikitext for commons images?
$this->mText = '';
-
+
}
}
-
+
/**
* @param $terms Array: terms to highlight
- * @return String: highlighted text snippet, null (and not '') if not supported
+ * @return String: highlighted text snippet, null (and not '') if not supported
*/
- function getTextSnippet($terms){
+ function getTextSnippet( $terms ) {
global $wgUser, $wgAdvancedSearchHighlighting;
$this->initText();
- list($contextlines,$contextchars) = SearchEngine::userHighlightPrefs($wgUser);
+ list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs( $wgUser );
$h = new SearchHighlighter();
- if( $wgAdvancedSearchHighlighting )
+ if ( $wgAdvancedSearchHighlighting )
return $h->highlightText( $this->mText, $terms, $contextlines, $contextchars );
else
return $h->highlightSimple( $this->mText, $terms, $contextlines, $contextchars );
}
-
+
/**
* @param $terms Array: terms to highlight
* @return String: highlighted title, '' if not supported
*/
- function getTitleSnippet($terms){
+ function getTitleSnippet( $terms ) {
return '';
}
@@ -677,46 +757,46 @@ class SearchResult {
* @param $terms Array: terms to highlight
* @return String: highlighted redirect name (redirect to this page), '' if none or not supported
*/
- function getRedirectSnippet($terms){
+ function getRedirectSnippet( $terms ) {
return '';
}
/**
* @return Title object for the redirect to this page, null if none or not supported
*/
- function getRedirectTitle(){
+ function getRedirectTitle() {
return null;
}
/**
* @return string highlighted relevant section name, null if none or not supported
*/
- function getSectionSnippet(){
+ function getSectionSnippet() {
return '';
}
/**
* @return Title object (pagename+fragment) for the section, null if none or not supported
*/
- function getSectionTitle(){
+ function getSectionTitle() {
return null;
}
/**
* @return String: timestamp
*/
- function getTimestamp(){
- if( $this->mRevision )
+ function getTimestamp() {
+ if ( $this->mRevision )
return $this->mRevision->getTimestamp();
- else if( $this->mImage )
+ else if ( $this->mImage )
return $this->mImage->getTimestamp();
- return '';
+ return '';
}
/**
* @return Integer: number of words
*/
- function getWordCount(){
+ function getWordCount() {
$this->initText();
return str_word_count( $this->mText );
}
@@ -724,38 +804,63 @@ class SearchResult {
/**
* @return Integer: size in bytes
*/
- function getByteSize(){
+ function getByteSize() {
$this->initText();
return strlen( $this->mText );
}
-
+
/**
* @return Boolean if hit has related articles
*/
- function hasRelated(){
+ function hasRelated() {
return false;
}
-
+
/**
* @return String: interwiki prefix of the title (return iw even if title is broken)
*/
- function getInterwikiPrefix(){
+ function getInterwikiPrefix() {
return '';
}
}
+/**
+ * A SearchResultSet wrapper for SearchEngine::getNearMatch
+ */
+class SearchNearMatchResultSet extends SearchResultSet {
+ private $fetched = false;
+ /**
+ * @param $match mixed Title if matched, else null
+ */
+ public function __construct( $match ) {
+ $this->result = $match;
+ }
+ public function hasResult() {
+ return (bool)$this->result;
+ }
+ public function numRows() {
+ return $this->hasResults() ? 1 : 0;
+ }
+ public function next() {
+ if ( $this->fetched || !$this->result ) {
+ return false;
+ }
+ $this->fetched = true;
+ return SearchResult::newFromTitle( $this->result );
+ }
+}
/**
* Highlight bits of wikitext
- *
+ *
* @ingroup Search
*/
-class SearchHighlighter {
+class SearchHighlighter {
var $mCleanWikitext = true;
-
- function SearchHighlighter($cleanupWikitext = true){
+
+ function __construct( $cleanupWikitext = true ) {
$this->mCleanWikitext = $cleanupWikitext;
}
-
+
/**
* Default implementation of wikitext highlighting
*
@@ -766,23 +871,23 @@ class SearchHighlighter {
* @return String
*/
public function highlightText( $text, $terms, $contextlines, $contextchars ) {
- global $wgLang, $wgContLang;
+ global $wgContLang;
global $wgSearchHighlightBoundaries;
$fname = __METHOD__;
-
- if($text == '')
+
+ if ( $text == '' )
return '';
-
+
// spli text into text + templates/links/tables
$spat = "/(\\{\\{)|(\\[\\[[^\\]:]+:)|(\n\\{\\|)";
// first capture group is for detecting nested templates/links/tables/references
$endPatterns = array(
1 => '/(\{\{)|(\}\})/', // template
2 => '/(\[\[)|(\]\])/', // image
- 3 => "/(\n\\{\\|)|(\n\\|\\})/"); // table
-
+ 3 => "/(\n\\{\\|)|(\n\\|\\})/" ); // table
+
// FIXME: this should prolly be a hook or something
- if(function_exists('wfCite')){
+ if ( function_exists( 'wfCite' ) ) {
$spat .= '|(<ref>)'; // references via cite extension
$endPatterns[4] = '/(<ref>)|(<\/ref>)/';
}
@@ -791,213 +896,215 @@ class SearchHighlighter {
$otherExt = array(); // other extracts
wfProfileIn( "$fname-split" );
$start = 0;
- $textLen = strlen($text);
+ $textLen = strlen( $text );
$count = 0; // sequence number to maintain ordering
- while( $start < $textLen ){
+ while ( $start < $textLen ) {
// find start of template/image/table
- if( preg_match( $spat, $text, $matches, PREG_OFFSET_CAPTURE, $start ) ){
- $epat = '';
- foreach($matches as $key => $val){
- if($key > 0 && $val[1] != -1){
- if($key == 2){
+ if ( preg_match( $spat, $text, $matches, PREG_OFFSET_CAPTURE, $start ) ) {
+ $epat = '';
+ foreach ( $matches as $key => $val ) {
+ if ( $key > 0 && $val[1] != - 1 ) {
+ if ( $key == 2 ) {
// see if this is an image link
- $ns = substr($val[0],2,-1);
- if( $wgContLang->getNsIndex($ns) != NS_FILE )
+ $ns = substr( $val[0], 2, - 1 );
+ if ( $wgContLang->getNsIndex( $ns ) != NS_FILE )
break;
-
+
}
$epat = $endPatterns[$key];
- $this->splitAndAdd( $textExt, $count, substr( $text, $start, $val[1] - $start ) );
+ $this->splitAndAdd( $textExt, $count, substr( $text, $start, $val[1] - $start ) );
$start = $val[1];
break;
}
}
- if( $epat ){
+ if ( $epat ) {
// find end (and detect any nested elements)
- $level = 0;
+ $level = 0;
$offset = $start + 1;
$found = false;
- while( preg_match( $epat, $text, $endMatches, PREG_OFFSET_CAPTURE, $offset ) ){
- if( array_key_exists(2,$endMatches) ){
+ while ( preg_match( $epat, $text, $endMatches, PREG_OFFSET_CAPTURE, $offset ) ) {
+ if ( array_key_exists( 2, $endMatches ) ) {
// found end
- if($level == 0){
- $len = strlen($endMatches[2][0]);
+ if ( $level == 0 ) {
+ $len = strlen( $endMatches[2][0] );
$off = $endMatches[2][1];
- $this->splitAndAdd( $otherExt, $count,
+ $this->splitAndAdd( $otherExt, $count,
substr( $text, $start, $off + $len - $start ) );
$start = $off + $len;
$found = true;
break;
- } else{
+ } else {
// end of nested element
$level -= 1;
}
- } else{
+ } else {
// nested
$level += 1;
}
- $offset = $endMatches[0][1] + strlen($endMatches[0][0]);
+ $offset = $endMatches[0][1] + strlen( $endMatches[0][0] );
}
- if( ! $found ){
+ if ( ! $found ) {
// couldn't find appropriate closing tag, skip
- $this->splitAndAdd( $textExt, $count, substr( $text, $start, strlen($matches[0][0]) ) );
- $start += strlen($matches[0][0]);
+ $this->splitAndAdd( $textExt, $count, substr( $text, $start, strlen( $matches[0][0] ) ) );
+ $start += strlen( $matches[0][0] );
}
continue;
}
}
// else: add as text extract
- $this->splitAndAdd( $textExt, $count, substr($text,$start) );
+ $this->splitAndAdd( $textExt, $count, substr( $text, $start ) );
break;
}
-
+
$all = $textExt + $otherExt; // these have disjunct key sets
-
+
wfProfileOut( "$fname-split" );
-
+
// prepare regexps
- foreach( $terms as $index => $term ) {
+ foreach ( $terms as $index => $term ) {
// manually do upper/lowercase stuff for utf-8 since PHP won't do it
- if(preg_match('/[\x80-\xff]/', $term) ){
- $terms[$index] = preg_replace_callback('/./us',array($this,'caseCallback'),$terms[$index]);
+ if ( preg_match( '/[\x80-\xff]/', $term ) ) {
+ $terms[$index] = preg_replace_callback( '/./us', array( $this, 'caseCallback' ), $terms[$index] );
} else {
$terms[$index] = $term;
}
}
$anyterm = implode( '|', $terms );
- $phrase = implode("$wgSearchHighlightBoundaries+", $terms );
+ $phrase = implode( "$wgSearchHighlightBoundaries+", $terms );
// FIXME: a hack to scale contextchars, a correct solution
// would be to have contextchars actually be char and not byte
// length, and do proper utf-8 substrings and lengths everywhere,
// but PHP is making that very hard and unclean to implement :(
- $scale = strlen($anyterm) / mb_strlen($anyterm);
+ $scale = strlen( $anyterm ) / mb_strlen( $anyterm );
$contextchars = intval( $contextchars * $scale );
-
+
$patPre = "(^|$wgSearchHighlightBoundaries)";
- $patPost = "($wgSearchHighlightBoundaries|$)";
-
- $pat1 = "/(".$phrase.")/ui";
- $pat2 = "/$patPre(".$anyterm.")$patPost/ui";
-
+ $patPost = "($wgSearchHighlightBoundaries|$)";
+
+ $pat1 = "/(" . $phrase . ")/ui";
+ $pat2 = "/$patPre(" . $anyterm . ")$patPost/ui";
+
wfProfileIn( "$fname-extract" );
-
+
$left = $contextlines;
$snippets = array();
- $offsets = array();
-
+ $offsets = array();
+
// show beginning only if it contains all words
$first = 0;
$firstText = '';
- foreach($textExt as $index => $line){
- if(strlen($line)>0 && $line[0] != ';' && $line[0] != ':'){
+ foreach ( $textExt as $index => $line ) {
+ if ( strlen( $line ) > 0 && $line[0] != ';' && $line[0] != ':' ) {
$firstText = $this->extract( $line, 0, $contextchars * $contextlines );
$first = $index;
break;
}
}
- if( $firstText ){
+ if ( $firstText ) {
$succ = true;
// check if first text contains all terms
- foreach($terms as $term){
- if( ! preg_match("/$patPre".$term."$patPost/ui", $firstText) ){
+ foreach ( $terms as $term ) {
+ if ( ! preg_match( "/$patPre" . $term . "$patPost/ui", $firstText ) ) {
$succ = false;
break;
}
}
- if( $succ ){
+ if ( $succ ) {
$snippets[$first] = $firstText;
- $offsets[$first] = 0;
+ $offsets[$first] = 0;
}
}
- if( ! $snippets ) {
- // match whole query on text
- $this->process($pat1, $textExt, $left, $contextchars, $snippets, $offsets);
+ if ( ! $snippets ) {
+ // match whole query on text
+ $this->process( $pat1, $textExt, $left, $contextchars, $snippets, $offsets );
// match whole query on templates/tables/images
- $this->process($pat1, $otherExt, $left, $contextchars, $snippets, $offsets);
+ $this->process( $pat1, $otherExt, $left, $contextchars, $snippets, $offsets );
// match any words on text
- $this->process($pat2, $textExt, $left, $contextchars, $snippets, $offsets);
+ $this->process( $pat2, $textExt, $left, $contextchars, $snippets, $offsets );
// match any words on templates/tables/images
- $this->process($pat2, $otherExt, $left, $contextchars, $snippets, $offsets);
-
- ksort($snippets);
+ $this->process( $pat2, $otherExt, $left, $contextchars, $snippets, $offsets );
+
+ ksort( $snippets );
}
-
+
// add extra chars to each snippet to make snippets constant size
- $extended = array();
- if( count( $snippets ) == 0){
+ $extended = array();
+ if ( count( $snippets ) == 0 ) {
// couldn't find the target words, just show beginning of article
- $targetchars = $contextchars * $contextlines;
- $snippets[$first] = '';
- $offsets[$first] = 0;
- } else{
- // if begin of the article contains the whole phrase, show only that !!
- if( array_key_exists($first,$snippets) && preg_match($pat1,$snippets[$first])
- && $offsets[$first] < $contextchars * 2 ){
- $snippets = array ($first => $snippets[$first]);
+ if ( array_key_exists( $first, $all ) ) {
+ $targetchars = $contextchars * $contextlines;
+ $snippets[$first] = '';
+ $offsets[$first] = 0;
}
-
+ } else {
+ // if begin of the article contains the whole phrase, show only that !!
+ if ( array_key_exists( $first, $snippets ) && preg_match( $pat1, $snippets[$first] )
+ && $offsets[$first] < $contextchars * 2 ) {
+ $snippets = array ( $first => $snippets[$first] );
+ }
+
// calc by how much to extend existing snippets
- $targetchars = intval( ($contextchars * $contextlines) / count ( $snippets ) );
- }
+ $targetchars = intval( ( $contextchars * $contextlines ) / count ( $snippets ) );
+ }
- foreach($snippets as $index => $line){
+ foreach ( $snippets as $index => $line ) {
$extended[$index] = $line;
- $len = strlen($line);
- if( $len < $targetchars - 20 ){
+ $len = strlen( $line );
+ if ( $len < $targetchars - 20 ) {
// complete this line
- if($len < strlen( $all[$index] )){
- $extended[$index] = $this->extract( $all[$index], $offsets[$index], $offsets[$index]+$targetchars, $offsets[$index]);
+ if ( $len < strlen( $all[$index] ) ) {
+ $extended[$index] = $this->extract( $all[$index], $offsets[$index], $offsets[$index] + $targetchars, $offsets[$index] );
$len = strlen( $extended[$index] );
}
-
+
// add more lines
$add = $index + 1;
- while( $len < $targetchars - 20
- && array_key_exists($add,$all)
- && !array_key_exists($add,$snippets) ){
+ while ( $len < $targetchars - 20
+ && array_key_exists( $add, $all )
+ && !array_key_exists( $add, $snippets ) ) {
$offsets[$add] = 0;
- $tt = "\n".$this->extract( $all[$add], 0, $targetchars - $len, $offsets[$add] );
+ $tt = "\n" . $this->extract( $all[$add], 0, $targetchars - $len, $offsets[$add] );
$extended[$add] = $tt;
$len += strlen( $tt );
- $add++;
+ $add++;
}
- }
+ }
}
-
- //$snippets = array_map('htmlspecialchars', $extended);
+
+ // $snippets = array_map('htmlspecialchars', $extended);
$snippets = $extended;
- $last = -1;
+ $last = - 1;
$extract = '';
- foreach($snippets as $index => $line){
- if($last == -1)
+ foreach ( $snippets as $index => $line ) {
+ if ( $last == - 1 )
$extract .= $line; // first line
- elseif($last+1 == $index && $offsets[$last]+strlen($snippets[$last]) >= strlen($all[$last]))
- $extract .= " ".$line; // continous lines
+ elseif ( $last + 1 == $index && $offsets[$last] + strlen( $snippets[$last] ) >= strlen( $all[$last] ) )
+ $extract .= " " . $line; // continous lines
else
$extract .= '<b> ... </b>' . $line;
$last = $index;
}
- if( $extract )
+ if ( $extract )
$extract .= '<b> ... </b>';
-
+
$processed = array();
- foreach($terms as $term){
- if( ! isset($processed[$term]) ){
- $pat3 = "/$patPre(".$term.")$patPost/ui"; // highlight word
+ foreach ( $terms as $term ) {
+ if ( ! isset( $processed[$term] ) ) {
+ $pat3 = "/$patPre(" . $term . ")$patPost/ui"; // highlight word
$extract = preg_replace( $pat3,
"\\1<span class='searchmatch'>\\2</span>\\3", $extract );
$processed[$term] = true;
}
}
-
+
wfProfileOut( "$fname-extract" );
-
+
return $extract;
}
-
+
/**
* Split text into lines and add it to extracts array
*
@@ -1005,28 +1112,28 @@ class SearchHighlighter {
* @param $count Integer
* @param $text String
*/
- function splitAndAdd(&$extracts, &$count, $text){
- $split = explode( "\n", $this->mCleanWikitext? $this->removeWiki($text) : $text );
- foreach($split as $line){
- $tt = trim($line);
- if( $tt )
+ function splitAndAdd( &$extracts, &$count, $text ) {
+ $split = explode( "\n", $this->mCleanWikitext ? $this->removeWiki( $text ) : $text );
+ foreach ( $split as $line ) {
+ $tt = trim( $line );
+ if ( $tt )
$extracts[$count++] = $tt;
}
}
-
+
/**
* Do manual case conversion for non-ascii chars
*
* @param $matches Array
*/
- function caseCallback($matches){
+ function caseCallback( $matches ) {
global $wgContLang;
- if( strlen($matches[0]) > 1 ){
- return '['.$wgContLang->lc($matches[0]).$wgContLang->uc($matches[0]).']';
+ if ( strlen( $matches[0] ) > 1 ) {
+ return '[' . $wgContLang->lc( $matches[0] ) . $wgContLang->uc( $matches[0] ) . ']';
} else
return $matches[0];
}
-
+
/**
* Extract part of the text from start to end, but by
* not chopping up words
@@ -1035,29 +1142,27 @@ class SearchHighlighter {
* @param $end Integer
* @param $posStart Integer: (out) actual start position
* @param $posEnd Integer: (out) actual end position
- * @return String
+ * @return String
*/
- function extract($text, $start, $end, &$posStart = null, &$posEnd = null ){
- global $wgContLang;
-
- if( $start != 0)
+ function extract( $text, $start, $end, &$posStart = null, &$posEnd = null ) {
+ if ( $start != 0 )
$start = $this->position( $text, $start, 1 );
- if( $end >= strlen($text) )
- $end = strlen($text);
+ if ( $end >= strlen( $text ) )
+ $end = strlen( $text );
else
$end = $this->position( $text, $end );
-
- if(!is_null($posStart))
+
+ if ( !is_null( $posStart ) )
$posStart = $start;
- if(!is_null($posEnd))
+ if ( !is_null( $posEnd ) )
$posEnd = $end;
-
- if($end > $start)
- return substr($text, $start, $end-$start);
+
+ if ( $end > $start )
+ return substr( $text, $start, $end - $start );
else
return '';
- }
-
+ }
+
/**
* Find a nonletter near a point (index) in the text
*
@@ -1066,117 +1171,117 @@ class SearchHighlighter {
* @param $offset Integer: offset to found index
* @return Integer: nearest nonletter index, or beginning of utf8 char if none
*/
- function position($text, $point, $offset=0 ){
+ function position( $text, $point, $offset = 0 ) {
$tolerance = 10;
$s = max( 0, $point - $tolerance );
- $l = min( strlen($text), $point + $tolerance ) - $s;
+ $l = min( strlen( $text ), $point + $tolerance ) - $s;
$m = array();
- if( preg_match('/[ ,.!?~!@#$%^&*\(\)+=\-\\\|\[\]"\'<>]/', substr($text,$s,$l), $m, PREG_OFFSET_CAPTURE ) ){
+ if ( preg_match( '/[ ,.!?~!@#$%^&*\(\)+=\-\\\|\[\]"\'<>]/', substr( $text, $s, $l ), $m, PREG_OFFSET_CAPTURE ) ) {
return $m[0][1] + $s + $offset;
- } else{
+ } else {
// check if point is on a valid first UTF8 char
$char = ord( $text[$point] );
- while( $char >= 0x80 && $char < 0xc0 ) {
+ while ( $char >= 0x80 && $char < 0xc0 ) {
// skip trailing bytes
$point++;
- if($point >= strlen($text))
- return strlen($text);
+ if ( $point >= strlen( $text ) )
+ return strlen( $text );
$char = ord( $text[$point] );
}
return $point;
-
+
}
}
-
+
/**
* Search extracts for a pattern, and return snippets
*
* @param $pattern String: regexp for matching lines
- * @param $extracts Array: extracts to search
+ * @param $extracts Array: extracts to search
* @param $linesleft Integer: number of extracts to make
* @param $contextchars Integer: length of snippet
* @param $out Array: map for highlighted snippets
* @param $offsets Array: map of starting points of snippets
* @protected
*/
- function process( $pattern, $extracts, &$linesleft, &$contextchars, &$out, &$offsets ){
- if($linesleft == 0)
+ function process( $pattern, $extracts, &$linesleft, &$contextchars, &$out, &$offsets ) {
+ if ( $linesleft == 0 )
return; // nothing to do
- foreach($extracts as $index => $line){
- if( array_key_exists($index,$out) )
+ foreach ( $extracts as $index => $line ) {
+ if ( array_key_exists( $index, $out ) )
continue; // this line already highlighted
-
+
$m = array();
if ( !preg_match( $pattern, $line, $m, PREG_OFFSET_CAPTURE ) )
continue;
-
+
$offset = $m[0][1];
- $len = strlen($m[0][0]);
- if($offset + $len < $contextchars)
- $begin = 0;
- elseif( $len > $contextchars)
+ $len = strlen( $m[0][0] );
+ if ( $offset + $len < $contextchars )
+ $begin = 0;
+ elseif ( $len > $contextchars )
$begin = $offset;
else
- $begin = $offset + intval( ($len - $contextchars) / 2 );
-
+ $begin = $offset + intval( ( $len - $contextchars ) / 2 );
+
$end = $begin + $contextchars;
-
+
$posBegin = $begin;
// basic snippet from this line
- $out[$index] = $this->extract($line,$begin,$end,$posBegin);
+ $out[$index] = $this->extract( $line, $begin, $end, $posBegin );
$offsets[$index] = $posBegin;
- $linesleft--;
- if($linesleft == 0)
+ $linesleft--;
+ if ( $linesleft == 0 )
return;
}
}
-
- /**
+
+ /**
* Basic wikitext removal
* @protected
*/
- function removeWiki($text) {
+ function removeWiki( $text ) {
$fname = __METHOD__;
wfProfileIn( $fname );
-
- //$text = preg_replace("/'{2,5}/", "", $text);
- //$text = preg_replace("/\[[a-z]+:\/\/[^ ]+ ([^]]+)\]/", "\\2", $text);
- //$text = preg_replace("/\[\[([^]|]+)\]\]/", "\\1", $text);
- //$text = preg_replace("/\[\[([^]]+\|)?([^|]]+)\]\]/", "\\2", $text);
- //$text = preg_replace("/\\{\\|(.*?)\\|\\}/", "", $text);
- //$text = preg_replace("/\\[\\[[A-Za-z_-]+:([^|]+?)\\]\\]/", "", $text);
- $text = preg_replace("/\\{\\{([^|]+?)\\}\\}/", "", $text);
- $text = preg_replace("/\\{\\{([^|]+\\|)(.*?)\\}\\}/", "\\2", $text);
- $text = preg_replace("/\\[\\[([^|]+?)\\]\\]/", "\\1", $text);
- $text = preg_replace_callback("/\\[\\[([^|]+\\|)(.*?)\\]\\]/", array($this,'linkReplace'), $text);
- //$text = preg_replace("/\\[\\[([^|]+\\|)(.*?)\\]\\]/", "\\2", $text);
- $text = preg_replace("/<\/?[^>]+>/", "", $text);
- $text = preg_replace("/'''''/", "", $text);
- $text = preg_replace("/('''|<\/?[iIuUbB]>)/", "", $text);
- $text = preg_replace("/''/", "", $text);
-
+
+ // $text = preg_replace("/'{2,5}/", "", $text);
+ // $text = preg_replace("/\[[a-z]+:\/\/[^ ]+ ([^]]+)\]/", "\\2", $text);
+ // $text = preg_replace("/\[\[([^]|]+)\]\]/", "\\1", $text);
+ // $text = preg_replace("/\[\[([^]]+\|)?([^|]]+)\]\]/", "\\2", $text);
+ // $text = preg_replace("/\\{\\|(.*?)\\|\\}/", "", $text);
+ // $text = preg_replace("/\\[\\[[A-Za-z_-]+:([^|]+?)\\]\\]/", "", $text);
+ $text = preg_replace( "/\\{\\{([^|]+?)\\}\\}/", "", $text );
+ $text = preg_replace( "/\\{\\{([^|]+\\|)(.*?)\\}\\}/", "\\2", $text );
+ $text = preg_replace( "/\\[\\[([^|]+?)\\]\\]/", "\\1", $text );
+ $text = preg_replace_callback( "/\\[\\[([^|]+\\|)(.*?)\\]\\]/", array( $this, 'linkReplace' ), $text );
+ // $text = preg_replace("/\\[\\[([^|]+\\|)(.*?)\\]\\]/", "\\2", $text);
+ $text = preg_replace( "/<\/?[^>]+>/", "", $text );
+ $text = preg_replace( "/'''''/", "", $text );
+ $text = preg_replace( "/('''|<\/?[iIuUbB]>)/", "", $text );
+ $text = preg_replace( "/''/", "", $text );
+
wfProfileOut( $fname );
return $text;
}
-
+
/**
* callback to replace [[target|caption]] kind of links, if
* the target is category or image, leave it
*
* @param $matches Array
*/
- function linkReplace($matches){
- $colon = strpos( $matches[1], ':' );
- if( $colon === false )
+ function linkReplace( $matches ) {
+ $colon = strpos( $matches[1], ':' );
+ if ( $colon === false )
return $matches[2]; // replace with caption
global $wgContLang;
$ns = substr( $matches[1], 0, $colon );
- $index = $wgContLang->getNsIndex($ns);
- if( $index !== false && ($index == NS_FILE || $index == NS_CATEGORY) )
- return $matches[0]; // return the whole thing
+ $index = $wgContLang->getNsIndex( $ns );
+ if ( $index !== false && ( $index == NS_FILE || $index == NS_CATEGORY ) )
+ return $matches[0]; // return the whole thing
else
return $matches[2];
-
+
}
/**
@@ -1190,11 +1295,11 @@ class SearchHighlighter {
* @return String
*/
public function highlightSimple( $text, $terms, $contextlines, $contextchars ) {
- global $wgLang, $wgContLang;
+ global $wgContLang;
$fname = __METHOD__;
$lines = explode( "\n", $text );
-
+
$terms = implode( '|', $terms );
$max = intval( $contextchars ) + 1;
$pat1 = "/(.*)($terms)(.{0,$max})/i";
@@ -1213,7 +1318,7 @@ class SearchHighlighter {
continue;
}
--$contextlines;
- $pre = $wgContLang->truncate( $m[1], -$contextchars );
+ $pre = $wgContLang->truncate( $m[1], - $contextchars );
if ( count( $m ) < 3 ) {
$post = '';
@@ -1231,10 +1336,10 @@ class SearchHighlighter {
$extract .= "${line}\n";
}
wfProfileOut( "$fname-extract" );
-
+
return $extract;
}
-
+
}
/**
diff --git a/includes/search/SearchIBM_DB2.php b/includes/search/SearchIBM_DB2.php
index d7587186..8cedd6f2 100644
--- a/includes/search/SearchIBM_DB2.php
+++ b/includes/search/SearchIBM_DB2.php
@@ -1,23 +1,25 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
+ * IBM DB2 search engine
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup Search
*/
@@ -27,8 +29,13 @@
* @ingroup Search
*/
class SearchIBM_DB2 extends SearchEngine {
+
+ /**
+ * Creates an instance of this class
+ * @param $db DatabaseIbm_db2: database object
+ */
function __construct($db) {
- $this->db = $db;
+ parent::__construct( $db );
}
/**
@@ -102,8 +109,8 @@ class SearchIBM_DB2 extends SearchEngine {
/**
* Construct the full SQL query to do the search.
* The guts shoulds be constructed in queryMain()
- * @param string $filteredTerm String
- * @param bool $fulltext Boolean
+ * @param $filteredTerm String
+ * @param $fulltext Boolean
*/
function getQuery( $filteredTerm, $fulltext ) {
return $this->queryLimit($this->queryMain($filteredTerm, $fulltext) . ' ' .
@@ -125,8 +132,8 @@ class SearchIBM_DB2 extends SearchEngine {
/**
* Get the base part of the search query.
*
- * @param string $filteredTerm String
- * @param bool $fulltext Boolean
+ * @param $filteredTerm String
+ * @param $fulltext Boolean
* @return String
*/
function queryMain( $filteredTerm, $fulltext ) {
diff --git a/includes/search/SearchMssql.php b/includes/search/SearchMssql.php
new file mode 100644
index 00000000..8b850fae
--- /dev/null
+++ b/includes/search/SearchMssql.php
@@ -0,0 +1,254 @@
+<?php
+/**
+ * Mssql search engine
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Search
+ */
+
+/**
+ * Search engine hook base class for Mssql (ConText).
+ * @ingroup Search
+ */
+class SearchMssql extends SearchEngine {
+
+ /**
+ * Creates an instance of this class
+ * @param $db DatabaseMssql: database object
+ */
+ function __construct( $db ) {
+ parent::__construct( $db );
+ }
+
+ /**
+ * Perform a full text search query and return a result set.
+ *
+ * @param $term String: raw search term
+ * @return MssqlSearchResultSet
+ * @access public
+ */
+ function searchText( $term ) {
+ $resultSet = $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), true ) ) );
+ return new MssqlSearchResultSet( $resultSet, $this->searchTerms );
+ }
+
+ /**
+ * Perform a title-only search query and return a result set.
+ *
+ * @param $term String: raw search term
+ * @return MssqlSearchResultSet
+ * @access public
+ */
+ function searchTitle( $term ) {
+ $resultSet = $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), false ) ) );
+ return new MssqlSearchResultSet( $resultSet, $this->searchTerms );
+ }
+
+
+ /**
+ * Return a partial WHERE clause to exclude redirects, if so set
+ *
+ * @return String
+ * @private
+ */
+ function queryRedirect() {
+ if ( $this->showRedirects ) {
+ return '';
+ } else {
+ return 'AND page_is_redirect=0';
+ }
+ }
+
+ /**
+ * Return a partial WHERE clause to limit the search to the given namespaces
+ *
+ * @return String
+ * @private
+ */
+ function queryNamespaces() {
+ $namespaces = implode( ',', $this->namespaces );
+ if ( $namespaces == '' ) {
+ $namespaces = '0';
+ }
+ return 'AND page_namespace IN (' . $namespaces . ')';
+ }
+
+ /**
+ * Return a LIMIT clause to limit results on the query.
+ *
+ * @return String
+ * @private
+ */
+ function queryLimit( $sql ) {
+ return $this->db->limitResult( $sql, $this->limit, $this->offset );
+ }
+
+ /**
+ * Does not do anything for generic search engine
+ * subclasses may define this though
+ *
+ * @return String
+ * @private
+ */
+ function queryRanking( $filteredTerm, $fulltext ) {
+ return ' ORDER BY ftindex.[RANK] DESC'; // return ' ORDER BY score(1)';
+ }
+
+ /**
+ * Construct the full SQL query to do the search.
+ * The guts shoulds be constructed in queryMain()
+ *
+ * @param $filteredTerm String
+ * @param $fulltext Boolean
+ * @private
+ */
+ function getQuery( $filteredTerm, $fulltext ) {
+ return $this->queryLimit( $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
+ $this->queryRedirect() . ' ' .
+ $this->queryNamespaces() . ' ' .
+ $this->queryRanking( $filteredTerm, $fulltext ) . ' ' );
+ }
+
+
+ /**
+ * Picks which field to index on, depending on what type of query.
+ *
+ * @param $fulltext Boolean
+ * @return string
+ */
+ function getIndexField( $fulltext ) {
+ return $fulltext ? 'si_text' : 'si_title';
+ }
+
+ /**
+ * Get the base part of the search query.
+ *
+ * @param $filteredTerm String
+ * @param $fulltext Boolean
+ * @return String
+ * @private
+ */
+ function queryMain( $filteredTerm, $fulltext ) {
+ $match = $this->parseQuery( $filteredTerm, $fulltext );
+ $page = $this->db->tableName( 'page' );
+ $searchindex = $this->db->tableName( 'searchindex' );
+
+ return 'SELECT page_id, page_namespace, page_title, ftindex.[RANK]' .
+ "FROM $page,FREETEXTTABLE($searchindex , $match, LANGUAGE 'English') as ftindex " .
+ 'WHERE page_id=ftindex.[KEY] ';
+ }
+
+ /** @todo document */
+ function parseQuery( $filteredText, $fulltext ) {
+ global $wgContLang;
+ $lc = SearchEngine::legalSearchChars();
+ $this->searchTerms = array();
+
+ # FIXME: This doesn't handle parenthetical expressions.
+ $m = array();
+ $q = array();
+
+ if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
+ $filteredText, $m, PREG_SET_ORDER ) ) {
+ foreach ( $m as $terms ) {
+ $q[] = $terms[1] . $wgContLang->normalizeForSearch( $terms[2] );
+
+ if ( !empty( $terms[3] ) ) {
+ $regexp = preg_quote( $terms[3], '/' );
+ if ( $terms[4] )
+ $regexp .= "[0-9A-Za-z_]+";
+ } else {
+ $regexp = preg_quote( str_replace( '"', '', $terms[2] ), '/' );
+ }
+ $this->searchTerms[] = $regexp;
+ }
+ }
+
+ $searchon = $this->db->strencode( join( ',', $q ) );
+ $field = $this->getIndexField( $fulltext );
+ return "$field, '$searchon'";
+ }
+
+ /**
+ * Create or update the search index record for the given page.
+ * Title and text should be pre-processed.
+ *
+ * @param $id Integer
+ * @param $title String
+ * @param $text String
+ */
+ function update( $id, $title, $text ) {
+ // We store the column data as UTF-8 byte order marked binary stream
+ // because we are invoking the plain text IFilter on it so that, and we want it
+ // to properly decode the stream as UTF-8. SQL doesn't support UTF8 as a data type
+ // but the indexer will correctly handle it by this method. Since all we are doing
+ // is passing this data to the indexer and never retrieving it via PHP, this will save space
+ $table = $this->db->tableName( 'searchindex' );
+ $utf8bom = '0xEFBBBF';
+ $si_title = $utf8bom . bin2hex( $title );
+ $si_text = $utf8bom . bin2hex( $text );
+ $sql = "DELETE FROM $table WHERE si_page = $id;";
+ $sql .= "INSERT INTO $table (si_page, si_title, si_text) VALUES ($id, $si_title, $si_text)";
+ return $this->db->query( $sql, 'SearchMssql::update' );
+ }
+
+ /**
+ * Update a search index record's title only.
+ * Title should be pre-processed.
+ *
+ * @param $id Integer
+ * @param $title String
+ */
+ function updateTitle( $id, $title ) {
+ $table = $this->db->tableName( 'searchindex' );
+
+ // see update for why we are using the utf8bom
+ $utf8bom = '0xEFBBBF';
+ $si_title = $utf8bom . bin2hex( $title );
+ $sql = "DELETE FROM $table WHERE si_page = $id;";
+ $sql .= "INSERT INTO $table (si_page, si_title, si_text) VALUES ($id, $si_title, 0x00)";
+ return $this->db->query( $sql, 'SearchMssql::updateTitle' );
+ }
+}
+
+/**
+ * @ingroup Search
+ */
+class MssqlSearchResultSet extends SearchResultSet {
+ function __construct( $resultSet, $terms ) {
+ $this->mResultSet = $resultSet;
+ $this->mTerms = $terms;
+ }
+
+ function termMatches() {
+ return $this->mTerms;
+ }
+
+ function numRows() {
+ return $this->mResultSet->numRows();
+ }
+
+ function next() {
+ $row = $this->mResultSet->fetchObject();
+ if ( $row === false )
+ return false;
+ return new SearchResult( $row );
+ }
+}
+
+
diff --git a/includes/search/SearchMySQL.php b/includes/search/SearchMySQL.php
index 0c238be8..b92682ad 100644
--- a/includes/search/SearchMySQL.php
+++ b/includes/search/SearchMySQL.php
@@ -1,23 +1,25 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
+ * MySQL search engine
+ *
+ * Copyright (C) 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup Search
*/
@@ -30,9 +32,12 @@ class SearchMySQL extends SearchEngine {
var $strictMatching = true;
static $mMinSearchLength;
- /** @todo document */
+ /**
+ * Creates an instance of this class
+ * @param $db DatabaseMysql: database object
+ */
function __construct( $db ) {
- $this->db = $db;
+ parent::__construct( $db );
}
/**
@@ -325,8 +330,7 @@ class SearchMySQL extends SearchEngine {
wfProfileIn( __METHOD__ );
- // Some languages such as Chinese require word segmentation
- $out = $wgContLang->wordSegmentation( $string );
+ $out = parent::normalizeText( $string );
// MySQL fulltext index doesn't grok utf-8, so we
// need to fold cases and convert to hex
@@ -401,7 +405,7 @@ class SearchMySQL extends SearchEngine {
* @ingroup Search
*/
class MySQLSearchResultSet extends SqlSearchResultSet {
- function MySQLSearchResultSet( $resultSet, $terms, $totalHits=null ) {
+ function __construct( $resultSet, $terms, $totalHits=null ) {
parent::__construct( $resultSet, $terms );
$this->mTotalHits = $totalHits;
}
@@ -409,4 +413,4 @@ class MySQLSearchResultSet extends SqlSearchResultSet {
function getTotalHits() {
return $this->mTotalHits;
}
-} \ No newline at end of file
+}
diff --git a/includes/search/SearchMySQL4.php b/includes/search/SearchMySQL4.php
deleted file mode 100644
index 3e2bb2d1..00000000
--- a/includes/search/SearchMySQL4.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
-/**
- * @file
- * @ingroup Search
- */
-
-/**
- * Search engine hook for MySQL 4+
- * This class retained for backwards compatibility...
- * The meat's been moved to SearchMySQL, since the 3.x variety is gone.
- * @ingroup Search
- * @deprecated
- */
-class SearchMySQL4 extends SearchMySQL {
- /* whee */
-}
diff --git a/includes/search/SearchOracle.php b/includes/search/SearchOracle.php
index e4c5deee..15c386ce 100644
--- a/includes/search/SearchOracle.php
+++ b/includes/search/SearchOracle.php
@@ -1,23 +1,25 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
+ * Oracle search engine
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup Search
*/
@@ -54,9 +56,13 @@ class SearchOracle extends SearchEngine {
'TRSYN' => 1,
'TT' => 1,
'WITHIN' => 1);
-
+
+ /**
+ * Creates an instance of this class
+ * @param $db DatabasePostgres: database object
+ */
function __construct($db) {
- $this->db = $db;
+ parent::__construct( $db );
}
/**
@@ -240,16 +246,24 @@ class SearchOracle extends SearchEngine {
'si_title' => $title,
'si_text' => $text
), 'SearchOracle::update' );
- $dbw->query("CALL ctx_ddl.sync_index('si_text_idx')");
- $dbw->query("CALL ctx_ddl.sync_index('si_title_idx')");
+
+ // Sync the index
+ // We need to specify the DB name (i.e. user/schema) here so that
+ // it can work from the installer, where
+ // ALTER SESSION SET CURRENT_SCHEMA = ...
+ // was used.
+ $dbw->query( "CALL ctx_ddl.sync_index(" .
+ $dbw->addQuotes( $dbw->getDBname() . '.' . trim( $dbw->tableName( 'si_text_idx' ), '"' ) ) . ")" );
+ $dbw->query( "CALL ctx_ddl.sync_index(" .
+ $dbw->addQuotes( $dbw->getDBname() . '.' . trim( $dbw->tableName( 'si_title_idx' ), '"' ) ) . ")" );
}
/**
* Update a search index record's title only.
* Title should be pre-processed.
*
- * @param int $id
- * @param string $title
+ * @param $id Integer
+ * @param $title String
*/
function updateTitle($id, $title) {
$dbw = wfGetDB(DB_MASTER);
diff --git a/includes/search/SearchPostgres.php b/includes/search/SearchPostgres.php
index 0006fa82..9d6d1539 100644
--- a/includes/search/SearchPostgres.php
+++ b/includes/search/SearchPostgres.php
@@ -1,23 +1,25 @@
<?php
-# Copyright (C) 2006-2007 Greg Sabino Mullane <greg@turnstep.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
+ * PostgreSQL search engine
+ *
+ * Copyright © 2006-2007 Greg Sabino Mullane <greg@turnstep.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup Search
*/
@@ -27,9 +29,12 @@
* @ingroup Search
*/
class SearchPostgres extends SearchEngine {
-
+ /**
+ * Creates an instance of this class
+ * @param $db DatabaseSqlite: database object
+ */
function __construct( $db ) {
- $this->db = $db;
+ parent::__construct( $db );
}
/**
@@ -129,24 +134,21 @@ class SearchPostgres extends SearchEngine {
/**
* Construct the full SQL query to do the search.
- * @param $filteredTerm String
+ * @param $term String
* @param $fulltext String
+ * @param $colname
*/
function searchQuery( $term, $fulltext, $colname ) {
- global $wgDBversion;
+ $postgresVersion = $this->db->getServerVersion();
- if ( !isset( $wgDBversion ) ) {
- $this->db->getServerVersion();
- $wgDBversion = $this->db->numeric_version;
- }
- $prefix = $wgDBversion < 8.3 ? "'default'," : '';
+ $prefix = $postgresVersion < 8.3 ? "'default'," : '';
# Get the SQL fragment for the given term
$searchstring = $this->parseQuery( $term );
## We need a separate query here so gin does not complain about empty searches
$SQL = "SELECT to_tsquery($prefix $searchstring)";
- $res = $this->db->doQuery($SQL);
+ $res = $this->db->query($SQL);
if (!$res) {
## TODO: Better output (example to catch: one 'two)
die ("Sorry, that was not a valid search string. Please go back and try again");
@@ -166,8 +168,8 @@ class SearchPostgres extends SearchEngine {
}
}
- $rankscore = $wgDBversion > 8.2 ? 5 : 1;
- $rank = $wgDBversion < 8.3 ? 'rank' : 'ts_rank';
+ $rankscore = $postgresVersion > 8.2 ? 5 : 1;
+ $rank = $postgresVersion < 8.3 ? 'rank' : 'ts_rank';
$query = "SELECT page_id, page_namespace, page_title, ".
"$rank($fulltext, to_tsquery($prefix $searchstring), $rankscore) AS score ".
"FROM page p, revision r, pagecontent c WHERE p.page_latest = r.rev_id " .
@@ -204,7 +206,7 @@ class SearchPostgres extends SearchEngine {
$SQL = "UPDATE pagecontent SET textvector = NULL WHERE old_id IN ".
"(SELECT rev_text_id FROM revision WHERE rev_page = " . intval( $pageid ) .
" ORDER BY rev_text_id DESC OFFSET 1)";
- $this->db->doQuery($SQL);
+ $this->db->query($SQL);
return true;
}
diff --git a/includes/search/SearchSqlite.php b/includes/search/SearchSqlite.php
index fb55efec..6accc31b 100644
--- a/includes/search/SearchSqlite.php
+++ b/includes/search/SearchSqlite.php
@@ -1,22 +1,22 @@
<?php
-# SQLite search backend, based upon SearchMysql
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
+ * SQLite search backend, based upon SearchMysql
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup Search
*/
@@ -26,15 +26,12 @@
* @ingroup Search
*/
class SearchSqlite extends SearchEngine {
- // Cached because SearchUpdate keeps recreating our class
- private static $fulltextSupported = null;
-
/**
* Creates an instance of this class
* @param $db DatabaseSqlite: database object
*/
function __construct( $db ) {
- $this->db = $db;
+ parent::__construct( $db );
}
/**
@@ -42,18 +39,11 @@ class SearchSqlite extends SearchEngine {
* @return Boolean
*/
function fulltextSearchSupported() {
- if ( self::$fulltextSupported === null ) {
- self::$fulltextSupported = $this->db->selectField(
- 'updatelog',
- 'ul_key',
- array( 'ul_key' => 'fts3' ),
- __METHOD__ ) !== false;
- }
- return self::$fulltextSupported;
+ return $this->db->checkForEnabledSearch();
}
- /**
- * Parse the user's query and transform it into an SQL fragment which will
+ /**
+ * Parse the user's query and transform it into an SQL fragment which will
* become part of a WHERE clause
*/
function parseQuery( $filteredText, $fulltext ) {
@@ -67,7 +57,7 @@ class SearchSqlite extends SearchEngine {
$filteredText, $m, PREG_SET_ORDER ) ) {
foreach( $m as $bits ) {
@list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
-
+
if( $nonQuoted != '' ) {
$term = $nonQuoted;
$quote = '';
@@ -86,7 +76,7 @@ class SearchSqlite extends SearchEngine {
} else {
$variants = array( $term );
}
-
+
// The low-level search index does some processing on input to work
// around problems with minimum lengths and encoding in MySQL's
// fulltext engine.
@@ -94,12 +84,12 @@ class SearchSqlite extends SearchEngine {
$strippedVariants = array_map(
array( $wgContLang, 'normalizeForSearch' ),
$variants );
-
+
// Some languages such as Chinese force all variants to a canonical
// form when stripping to the low-level search index, so to be sure
// let's check our variants list for unique items after stripping.
$strippedVariants = array_unique( $strippedVariants );
-
+
$searchon .= $modifier;
if( count( $strippedVariants) > 1 )
$searchon .= '(';
@@ -114,7 +104,7 @@ class SearchSqlite extends SearchEngine {
}
if( count( $strippedVariants) > 1 )
$searchon .= ')';
-
+
// Match individual terms or quoted phrase in result highlighting...
// Note that variants will be introduced in a later stage for highlighting!
$regexp = $this->regexTerm( $term, $wildcard );
@@ -129,10 +119,10 @@ class SearchSqlite extends SearchEngine {
$field = $this->getIndexField( $fulltext );
return " $field MATCH '$searchon' ";
}
-
+
function regexTerm( $string, $wildcard ) {
global $wgContLang;
-
+
$regex = preg_quote( $string, '/' );
if( $wgContLang->hasWordBreaks() ) {
if( $wildcard ) {
@@ -172,7 +162,7 @@ class SearchSqlite extends SearchEngine {
function searchTitle( $term ) {
return $this->searchInternal( $term, false );
}
-
+
protected function searchInternal( $term, $fulltext ) {
global $wgCountTotalSearchHits, $wgContLang;
@@ -182,7 +172,7 @@ class SearchSqlite extends SearchEngine {
$filteredTerm = $this->filter( $wgContLang->lc( $term ) );
$resultSet = $this->db->query( $this->getQuery( $filteredTerm, $fulltext ) );
-
+
$total = null;
if( $wgCountTotalSearchHits ) {
$totalResult = $this->db->query( $this->getCountQuery( $filteredTerm, $fulltext ) );
@@ -192,7 +182,7 @@ class SearchSqlite extends SearchEngine {
}
$totalResult->free();
}
-
+
return new SqliteSearchResultSet( $resultSet, $this->searchTerms, $total );
}
@@ -226,7 +216,7 @@ class SearchSqlite extends SearchEngine {
/**
* Returns a query with limit for number of results set.
- * @param $sql String:
+ * @param $sql String:
* @return String
*/
function limitResult( $sql ) {
@@ -246,7 +236,7 @@ class SearchSqlite extends SearchEngine {
$this->queryNamespaces()
);
}
-
+
/**
* Picks which field to index on, depending on what type of query.
* @param $fulltext Boolean
@@ -300,7 +290,7 @@ class SearchSqlite extends SearchEngine {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'searchindex', array( 'rowid' => $id ), __METHOD__ );
-
+
$dbw->insert( 'searchindex',
array(
'rowid' => $id,
@@ -333,7 +323,7 @@ class SearchSqlite extends SearchEngine {
* @ingroup Search
*/
class SqliteSearchResultSet extends SqlSearchResultSet {
- function SqliteSearchResultSet( $resultSet, $terms, $totalHits=null ) {
+ function __construct( $resultSet, $terms, $totalHits=null ) {
parent::__construct( $resultSet, $terms );
$this->mTotalHits = $totalHits;
}
@@ -341,4 +331,4 @@ class SqliteSearchResultSet extends SqlSearchResultSet {
function getTotalHits() {
return $this->mTotalHits;
}
-} \ No newline at end of file
+}
diff --git a/includes/search/SearchUpdate.php b/includes/search/SearchUpdate.php
index e30c70e6..5262faa4 100644
--- a/includes/search/SearchUpdate.php
+++ b/includes/search/SearchUpdate.php
@@ -1,6 +1,16 @@
<?php
/**
+ * Search index updater
+ *
* See deferred.txt
+ *
+ * @file
+ * @ingroup Search
+ */
+
+/**
+ * Database independant search index updater
+ *
* @ingroup Search
*/
class SearchUpdate {
@@ -8,7 +18,7 @@ class SearchUpdate {
/* private */ var $mId = 0, $mNamespace, $mTitle, $mText;
/* private */ var $mTitleWords;
- function SearchUpdate( $id, $title, $text = false ) {
+ function __construct( $id, $title, $text = false ) {
$nt = Title::newFromText( $title );
if( $nt ) {
$this->mId = $id;
@@ -29,23 +39,23 @@ class SearchUpdate {
if( $wgDisableSearchUpdate || !$this->mId ) {
return false;
}
- $fname = 'SearchUpdate::doUpdate';
- wfProfileIn( $fname );
+
+ wfProfileIn( __METHOD__ );
$search = SearchEngine::create();
$lc = SearchEngine::legalSearchChars() . '&#;';
if( $this->mText === false ) {
$search->updateTitle($this->mId,
- Title::indexTitle( $this->mNamespace, $this->mTitle ));
- wfProfileOut( $fname );
+ $search->normalizeText( Title::indexTitle( $this->mNamespace, $this->mTitle ) ) );
+ wfProfileOut( __METHOD__ );
return;
}
# Language-specific strip/conversion
$text = $wgContLang->normalizeForSearch( $this->mText );
- wfProfileIn( $fname.'-regexps' );
+ 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",
@@ -92,20 +102,21 @@ class SearchUpdate {
# Strip wiki '' and '''
$text = preg_replace( "/''[']*/", " ", $text );
- wfProfileOut( "$fname-regexps" );
+ wfProfileOut( __METHOD__ . '-regexps' );
wfRunHooks( 'SearchUpdate', array( $this->mId, $this->mNamespace, $this->mTitle, &$text ) );
# Perform the actual update
- $search->update($this->mId, Title::indexTitle( $this->mNamespace, $this->mTitle ),
- $text);
+ $search->update($this->mId, $search->normalizeText( Title::indexTitle( $this->mNamespace, $this->mTitle ) ),
+ $search->normalizeText( $text ) );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
}
}
/**
* Placeholder class
+ *
* @ingroup Search
*/
class SearchUpdateMyISAM extends SearchUpdate {
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
index 7d907fb5..f016ab92 100644
--- a/includes/specials/SpecialActiveusers.php
+++ b/includes/specials/SpecialActiveusers.php
@@ -1,37 +1,40 @@
<?php
-# Copyright (C) 2008 Aaron Schulz
-#
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
+/**
+ * Implements Special:Activeusers
+ *
+ * Copyright © 2008 Aaron Schulz
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
/**
* This class is used to get a list of active users. The ones with specials
* rights (sysop, bureaucrat, developer) will have them displayed
* next to their names.
*
- * @file
* @ingroup SpecialPage
*/
class ActiveUsersPager extends UsersPager {
function __construct( $group = null ) {
- global $wgRequest, $wgRCMaxAge;
- $this->RCMaxAge = ceil( $wgRCMaxAge / ( 3600 * 24 ) ); // Constant
-
+ global $wgRequest, $wgActiveUserDays;
+ $this->RCMaxAge = $wgActiveUserDays;
$un = $wgRequest->getText( 'username' );
$this->requestedUser = '';
if ( $un != '' ) {
@@ -40,15 +43,15 @@ class ActiveUsersPager extends UsersPager {
$this->requestedUser = $username->getText();
}
}
-
+
$this->setupOptions();
-
+
parent::__construct();
}
public function setupOptions() {
global $wgRequest;
-
+
$this->opts = new FormOptions();
$this->opts->add( 'hidebots', false, FormOptions::BOOL );
@@ -57,10 +60,12 @@ class ActiveUsersPager extends UsersPager {
$this->opts->fetchValuesFromRequest( $wgRequest );
$this->groups = array();
- if ($this->opts->getValue('hidebots') == 1)
+ if ( $this->opts->getValue( 'hidebots' ) == 1 ) {
$this->groups['bot'] = true;
- if ($this->opts->getValue('hidesysops') == 1)
+ }
+ if ( $this->opts->getValue( 'hidesysops' ) == 1 ) {
$this->groups['sysop'] = true;
+ }
}
function getIndexField() {
@@ -72,7 +77,8 @@ class ActiveUsersPager extends UsersPager {
$conds = array( 'rc_user > 0' ); // Users - no anons
$conds[] = 'ipb_deleted IS NULL'; // don't show hidden names
$conds[] = "rc_log_type IS NULL OR rc_log_type != 'newusers'";
-
+ $conds[] = "rc_timestamp >= '{$dbr->timestamp( wfTimestamp( TS_UNIX ) - $this->RCMaxAge*24*3600 )}'";
+
if( $this->requestedUser != '' ) {
$conds[] = 'rc_user_text >= ' . $dbr->addQuotes( $this->requestedUser );
}
@@ -101,14 +107,15 @@ class ActiveUsersPager extends UsersPager {
function formatRow( $row ) {
global $wgLang;
$userName = $row->user_name;
-
+
$ulinks = $this->getSkin()->userLink( $row->user_id, $userName );
$ulinks .= $this->getSkin()->userToolLinks( $row->user_id, $userName );
$list = array();
foreach( self::getGroups( $row->user_id ) as $group ) {
- if (isset($this->groups[$group]))
+ if ( isset( $this->groups[$group] ) ) {
return;
+ }
$list[] = self::buildGroupLink( $group );
}
$groups = $wgLang->commaList( $list );
@@ -126,14 +133,14 @@ class ActiveUsersPager extends UsersPager {
}
function getPageHeader() {
- global $wgScript, $wgRequest;
+ global $wgScript;
$self = $this->getTitle();
- $limit = $this->mLimit ? Xml::hidden( 'limit', $this->mLimit ) : '';
+ $limit = $this->mLimit ? Html::hidden( 'limit', $this->mLimit ) : '';
- $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); # Form tag
+ $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); # Form tag
$out .= Xml::fieldset( wfMsg( 'activeusers' ) ) . "\n";
- $out .= Xml::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n";
+ $out .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n";
$out .= Xml::inputLabel( wfMsg( 'activeusers-from' ), 'username', 'offset', 20, $this->requestedUser ) . '<br />';# Username field
@@ -141,10 +148,10 @@ class ActiveUsersPager extends UsersPager {
$out .= Xml::checkLabel( wfMsg('activeusers-hidesysops'), 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ) ) . '<br />';
- $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n";# Submit button and form bottom
+ $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n";# Submit button and form bottom
$out .= Xml::closeElement( 'fieldset' );
$out .= Xml::closeElement( 'form' );
-
+
return $out;
}
}
@@ -157,7 +164,7 @@ class SpecialActiveUsers extends SpecialPage {
/**
* Constructor
*/
- public function __construct() {
+ public function __construct() {
parent::__construct( 'Activeusers' );
}
@@ -167,18 +174,19 @@ class SpecialActiveUsers extends SpecialPage {
* @param $par Mixed: parameter passed to the page or null
*/
public function execute( $par ) {
- global $wgOut, $wgLang, $wgRCMaxAge;
+ global $wgOut, $wgLang, $wgActiveUserDays;
$this->setHeaders();
+ $this->outputHeader();
$up = new ActiveUsersPager();
# getBody() first to check, if empty
$usersbody = $up->getBody();
- $s = Html::rawElement( 'div', array( 'class' => 'mw-activeusers-intro' ),
- wfMsgExt( 'activeusers-intro', array( 'parsemag', 'escape' ), $wgLang->formatNum( ceil( $wgRCMaxAge / 86400 ) ) )
- );
+ $s = Html::rawElement( 'div', array( 'class' => 'mw-activeusers-intro' ),
+ wfMsgExt( 'activeusers-intro', array( 'parsemag', 'escape' ), $wgLang->formatNum( $wgActiveUserDays ) )
+ );
$s .= $up->getPageHeader();
if( $usersbody ) {
diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php
index 1745bf6c..296c6f50 100644
--- a/includes/specials/SpecialAllmessages.php
+++ b/includes/specials/SpecialAllmessages.php
@@ -1,6 +1,29 @@
<?php
/**
+ * Implements Special:Allmessages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
* Use this special page to get a list of the MediaWiki system messages.
+ *
* @file
* @ingroup SpecialPage
*/
@@ -58,7 +81,7 @@ class SpecialAllmessages extends SpecialPage {
$out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) .
Xml::fieldset( wfMsg( 'allmessages-filter-legend' ) ) .
- Xml::hidden( 'title', $this->getTitle() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::openElement( 'table', array( 'class' => 'mw-allmessages-table' ) ) . "\n" .
'<tr>
<td class="mw-label">' .
@@ -77,19 +100,19 @@ class SpecialAllmessages extends SpecialPage {
'filter',
'unmodified',
'mw-allmessages-form-filter-unmodified',
- ( $this->filter == 'unmodified' ? true : false )
+ ( $this->filter == 'unmodified' )
) .
Xml::radioLabel( wfMsg( 'allmessages-filter-all' ),
'filter',
'all',
'mw-allmessages-form-filter-all',
- ( $this->filter == 'all' ? true : false )
+ ( $this->filter == 'all' )
) .
Xml::radioLabel( wfMsg( 'allmessages-filter-modified' ),
'filter',
'modified',
'mw-allmessages-form-filter-modified',
- ( $this->filter == 'modified' ? true : false )
+ ( $this->filter == 'modified' )
) .
"</td>\n
</tr>
@@ -101,7 +124,7 @@ class SpecialAllmessages extends SpecialPage {
Xml::openElement( 'select', array( 'id' => 'mw-allmessages-form-lang', 'name' => 'lang' ) );
foreach( $languages as $lang => $name ) {
- $selected = $lang == $this->langCode ? true : false;
+ $selected = $lang == $this->langCode;
$out .= Xml::option( $lang . ' - ' . $name, $lang, $selected ) . "\n";
}
$out .= Xml::closeElement( 'select' ) .
@@ -134,10 +157,7 @@ class AllmessagesTablePager extends TablePager {
$this->mPage = $page;
$this->mConds = $conds;
$this->mDefaultDirection = true; // always sort ascending
- // We want to have an option for people to view *all* the messages,
- // so they can use Ctrl+F to search them. 5000 is the maximum that
- // will get through WebRequest::getLimitOffset().
- $this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 => wfMsg('limitall') );
+ $this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 );
global $wgLang, $wgContLang, $wgRequest;
@@ -182,8 +202,8 @@ class AllmessagesTablePager extends TablePager {
// Normalise message names so they look like page titles
$messageNames = array_map( array( $this->lang, 'ucfirst' ), $messageNames );
- wfProfileIn( __METHOD__ );
+ wfProfileOut( __METHOD__ );
return $messageNames;
}
@@ -207,7 +227,7 @@ class AllmessagesTablePager extends TablePager {
$pageFlags = $talkFlags = array();
- while( $s = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $s ) {
if( $s->page_namespace == NS_MEDIAWIKI ) {
if( $this->foreign ) {
$title = explode( '/', $s->page_title );
@@ -223,7 +243,6 @@ class AllmessagesTablePager extends TablePager {
$talkFlags[$s->page_title] = true;
}
}
- $dbr->freeResult( $res );
wfProfileOut( __METHOD__ . '-db' );
@@ -327,7 +346,7 @@ class AllmessagesTablePager extends TablePager {
$s .= Xml::openElement( 'tr', $this->getRowAttrs( $row, true ) );
$formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) );
if ( $formatted == '' ) {
- $formatted = '&nbsp;';
+ $formatted = '&#160;';
}
$s .= Xml::tags( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted )
. "</tr>\n";
@@ -375,43 +394,4 @@ class AllmessagesTablePager extends TablePager {
return '';
}
}
-/* Overloads the relevant methods of the real ResultsWrapper so it
- * doesn't go anywhere near an actual database.
- */
-class FakeResultWrapper extends ResultWrapper {
-
- var $result = array();
- var $db = null; // And it's going to stay that way :D
- var $pos = 0;
- var $currentRow = null;
- function __construct( $array ){
- $this->result = $array;
- }
-
- function numRows() {
- return count( $this->result );
- }
-
- function fetchRow() {
- $this->currentRow = $this->result[$this->pos++];
- return $this->currentRow;
- }
-
- function seek( $row ) {
- $this->pos = $row;
- }
-
- function free() {}
-
- // Callers want to be able to access fields with $this->fieldName
- function fetchObject(){
- $this->currentRow = $this->result[$this->pos++];
- return (object)$this->currentRow;
- }
-
- function rewind() {
- $this->pos = 0;
- $this->currentRow = null;
- }
-}
diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php
index 19816dcd..5fa1aa47 100644
--- a/includes/specials/SpecialAllpages.php
+++ b/includes/specials/SpecialAllpages.php
@@ -1,7 +1,29 @@
<?php
-
/**
* Implements Special:Allpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Implements Special:Allpages
+ *
* @ingroup SpecialPage
*/
class SpecialAllpages extends IncludableSpecialPage {
@@ -32,8 +54,8 @@ class SpecialAllpages extends IncludableSpecialPage {
/**
* Entry point : initialise variables and call subfunctions.
+ *
* @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL)
- * @param $specialPage See the SpecialPage object.
*/
function execute( $par ) {
global $wgRequest, $wgOut, $wgContLang;
@@ -66,9 +88,10 @@ class SpecialAllpages extends IncludableSpecialPage {
/**
* HTML for the top form
- * @param integer $namespace A namespace constant (default NS_MAIN).
- * @param string $from dbKey we are starting listing at.
- * @param string $to dbKey we are ending listing at.
+ *
+ * @param $namespace Integer: a namespace constant (default NS_MAIN).
+ * @param $from String: dbKey we are starting listing at.
+ * @param $to String: dbKey we are ending listing at.
*/
function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '' ) {
global $wgScript;
@@ -76,7 +99,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
$out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- $out .= Xml::hidden( 'title', $t->getPrefixedText() );
+ $out .= Html::hidden( 'title', $t->getPrefixedText() );
$out .= Xml::openElement( 'fieldset' );
$out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) );
$out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
@@ -113,7 +136,9 @@ class SpecialAllpages extends IncludableSpecialPage {
}
/**
- * @param integer $namespace (default NS_MAIN)
+ * @param $namespace Integer (default NS_MAIN)
+ * @param $from String: list all pages from this name
+ * @param $to String: list all pages to this name
*/
function showToplevel( $namespace = NS_MAIN, $from = '', $to = '' ) {
global $wgOut;
@@ -164,7 +189,8 @@ class SpecialAllpages extends IncludableSpecialPage {
array ('LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC')
);
- if( $s = $dbr->fetchObject( $res ) ) {
+ $s = $dbr->fetchObject( $res );
+ if( $s ) {
array_push( $lines, $s->page_title );
} else {
// Final chunk, but ended prematurely. Go back and find the end.
@@ -174,7 +200,8 @@ class SpecialAllpages extends IncludableSpecialPage {
array_push( $lines, $endTitle );
$done = true;
}
- if( $s = $res->fetchObject() ) {
+ $s = $res->fetchObject();
+ if( $s ) {
array_push( $lines, $s->page_title );
$lastTitle = $s->page_title;
} else {
@@ -234,9 +261,10 @@ class SpecialAllpages extends IncludableSpecialPage {
/**
* Show a line of "ABC to DEF" ranges of articles
- * @param string $inpoint Lower limit of pagenames
- * @param string $outpout Upper limit of pagenames
- * @param integer $namespace (Default NS_MAIN)
+ *
+ * @param $inpoint String: lower limit of pagenames
+ * @param $outpoint String: upper limit of pagenames
+ * @param $namespace Integer (Default NS_MAIN)
*/
function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) {
global $wgContLang;
@@ -258,9 +286,9 @@ class SpecialAllpages extends IncludableSpecialPage {
}
/**
- * @param integer $namespace (Default NS_MAIN)
- * @param string $from list all pages from this name (default FALSE)
- * @param string $to list all pages to this name (default FALSE)
+ * @param $namespace Integer (Default NS_MAIN)
+ * @param $from String: list all pages from this name (default FALSE)
+ * @param $to String: list all pages to this name (default FALSE)
*/
function showChunk( $namespace = NS_MAIN, $from = false, $to = false ) {
global $wgOut, $wgUser, $wgContLang, $wgLang;
@@ -280,7 +308,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$namespace = NS_MAIN;
} else {
list( $namespace, $fromKey, $from ) = $fromList;
- list( $namespace2, $toKey, $to ) = $toList;
+ list( , $toKey, $to ) = $toList;
$dbr = wfGetDB( DB_SLAVE );
$conds = array(
@@ -316,7 +344,7 @@ class SpecialAllpages extends IncludableSpecialPage {
if( $n % 3 == 0 ) {
$out .= '<tr>';
}
- $out .= "<td width=\"33%\">$link</td>";
+ $out .= "<td style=\"width:33%\">$link</td>";
$n++;
if( $n % 3 == 0 ) {
$out .= "</tr>\n";
@@ -437,8 +465,8 @@ class SpecialAllpages extends IncludableSpecialPage {
}
/**
- * @param int $ns the namespace of the article
- * @param string $text the name of the article
+ * @param $ns Integer: the namespace of the article
+ * @param $text String: the name of the article
* @return array( int namespace, string dbkey, string pagename ) or NULL on error
* @static (sort of)
* @access private
diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php
index 92192435..2d5047d2 100644
--- a/includes/specials/SpecialAncientpages.php
+++ b/includes/specials/SpecialAncientpages.php
@@ -1,11 +1,29 @@
<?php
/**
+ * Implements Special:Ancientpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
/**
* Implements Special:Ancientpages
+ *
* @ingroup SpecialPage
*/
class AncientPagesPage extends QueryPage {
@@ -21,28 +39,10 @@ class AncientPagesPage extends QueryPage {
function isSyndicated() { return false; }
function getSQL() {
- global $wgDBtype;
$db = wfGetDB( DB_SLAVE );
$page = $db->tableName( 'page' );
$revision = $db->tableName( 'revision' );
-
- switch ($wgDBtype) {
- case 'mysql':
- $epoch = 'UNIX_TIMESTAMP(rev_timestamp)';
- break;
- case 'ibm_db2':
- // TODO implement proper conversion to a Unix epoch
- $epoch = 'rev_timestamp';
- break;
- case 'oracle':
- $epoch = '((trunc(rev_timestamp) - to_date(\'19700101\',\'YYYYMMDD\')) * 86400)';
- break;
- case 'sqlite':
- $epoch = 'rev_timestamp';
- break;
- default:
- $epoch = 'EXTRACT(epoch FROM rev_timestamp)';
- }
+ $epoch = $db->unixTimestamp( 'rev_timestamp' );
return
"SELECT 'Ancientpages' as type,
diff --git a/includes/specials/SpecialBlankpage.php b/includes/specials/SpecialBlankpage.php
index e1fadd02..aaa45a80 100644
--- a/includes/specials/SpecialBlankpage.php
+++ b/includes/specials/SpecialBlankpage.php
@@ -1,5 +1,27 @@
<?php
/**
+ * Implements Special:Blankpage
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 designed for basic benchmarking of
* MediaWiki since it doesn't really do much.
*
diff --git a/includes/specials/SpecialBlockip.php b/includes/specials/SpecialBlockip.php
index 16720dd1..28a0f3f1 100644
--- a/includes/specials/SpecialBlockip.php
+++ b/includes/specials/SpecialBlockip.php
@@ -1,52 +1,78 @@
<?php
/**
- * Constructor for Special:Blockip page
+ * Implements Special:Blockip
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * Constructor
+ * A special page that allows users with 'block' right to block users from
+ * editing pages and other actions
+ *
+ * @ingroup SpecialPage
*/
-function wfSpecialBlockip( $par ) {
- global $wgUser, $wgOut, $wgRequest;
+class IPBlockForm extends SpecialPage {
+ var $BlockAddress, $BlockExpiry, $BlockReason, $BlockReasonList, $BlockOther, $BlockAnonOnly, $BlockCreateAccount,
+ $BlockEnableAutoblock, $BlockEmail, $BlockHideName, $BlockAllowUsertalk, $BlockReblock;
+ // The maximum number of edits a user can have and still be hidden
+ const HIDEUSER_CONTRIBLIMIT = 1000;
- # Can't block when the database is locked
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
- # Permission check
- if( !$wgUser->isAllowed( 'block' ) ) {
- $wgOut->permissionRequired( 'block' );
- return;
+ public function __construct() {
+ parent::__construct( 'Blockip', 'block' );
}
- $ipb = new IPBlockForm( $par );
+ public function execute( $par ) {
+ global $wgUser, $wgOut, $wgRequest;
- $action = $wgRequest->getVal( 'action' );
- if( 'success' == $action ) {
- $ipb->showSuccess();
- } elseif( $wgRequest->wasPosted() && 'submit' == $action &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
- $ipb->doSubmit();
- } else {
- $ipb->showForm( '' );
- }
-}
+ # Can't block when the database is locked
+ if( wfReadOnly() ) {
+ $wgOut->readOnlyPage();
+ return;
+ }
+ # Permission check
+ if( !$this->userCanExecute( $wgUser ) ) {
+ $wgOut->permissionRequired( 'block' );
+ return;
+ }
-/**
- * Form object for the Special:Blockip page.
- *
- * @ingroup SpecialPage
- */
-class IPBlockForm {
- var $BlockAddress, $BlockExpiry, $BlockReason;
- // The maximum number of edits a user can have and still be hidden
- const HIDEUSER_CONTRIBLIMIT = 1000;
+ $this->setup( $par );
+
+ # bug 15810: blocked admins should have limited access here
+ if ( $wgUser->isBlocked() ) {
+ $status = IPBlockForm::checkUnblockSelf( $this->BlockAddress );
+ if ( $status !== true ) {
+ throw new ErrorPageError( 'badaccess', $status );
+ }
+ }
- public function __construct( $par ) {
+ $action = $wgRequest->getVal( 'action' );
+ if( 'success' == $action ) {
+ $this->showSuccess();
+ } elseif( $wgRequest->wasPosted() && 'submit' == $action &&
+ $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
+ $this->doSubmit();
+ } else {
+ $this->showForm( '' );
+ }
+ }
+
+ private function setup( $par ) {
global $wgRequest, $wgUser, $wgBlockAllowsUTEdit;
$this->BlockAddress = $wgRequest->getVal( 'wpBlockAddress', $wgRequest->getVal( 'ip', $par ) );
@@ -105,7 +131,7 @@ class IPBlockForm {
$msg = wfMsgReal( $key, $err );
$wgOut->setSubtitle( wfMsgHtml( 'formerror' ) );
$wgOut->addHTML( Xml::tags( 'p', array( 'class' => 'error' ), $msg ) );
- } elseif( $this->BlockAddress ) {
+ } elseif( $this->BlockAddress !== null ) {
# Get other blocks, i.e. from GlobalBlocking or TorBlock extension
wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockedMsgs, $this->BlockAddress ) );
@@ -149,7 +175,7 @@ class IPBlockForm {
# Username/IP is blocked already locally
if( $alreadyBlocked ) {
- $wgOut->addWikiMsg( 'ipb-needreblock', $this->BlockAddress );
+ $wgOut->wrapWikiMsg( "<div class='mw-ipb-needreblock'>\n$1\n</div>", array( 'ipb-needreblock', $this->BlockAddress ) );
}
$scBlockExpiryOptions = wfMsgForContent( 'ipboptions' );
@@ -163,16 +189,15 @@ class IPBlockForm {
list( $show, $value ) = explode( ':', $option );
$show = htmlspecialchars( $show );
$value = htmlspecialchars( $value );
- $blockExpiryFormOptions .= Xml::option( $show, $value, $this->BlockExpiry === $value ? true : false ) . "\n";
+ $blockExpiryFormOptions .= Xml::option( $show, $value, $this->BlockExpiry === $value ) . "\n";
}
$reasonDropDown = Xml::listDropDown( 'wpBlockReasonList',
wfMsgForContent( 'ipbreason-dropdown' ),
wfMsgForContent( 'ipbreasonotherlist' ), $this->BlockReasonList, 'wpBlockDropDown', 4 );
- global $wgStylePath, $wgStyleVersion;
+ $wgOut->addModules( 'mediawiki.legacy.block' );
$wgOut->addHTML(
- Xml::tags( 'script', array( 'type' => 'text/javascript', 'src' => "$wgStylePath/common/block.js?$wgStyleVersion" ), '' ) .
Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), 'id' => 'blockip' ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'blockip-legend' ) ) .
@@ -242,7 +267,7 @@ class IPBlockForm {
</td>
</tr>
<tr id='wpAnonOnlyRow'>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg( 'ipbanononly' ),
'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly,
@@ -250,7 +275,7 @@ class IPBlockForm {
</td>
</tr>
<tr id='wpCreateAccountRow'>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg( 'ipbcreateaccount' ),
'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount,
@@ -258,7 +283,7 @@ class IPBlockForm {
</td>
</tr>
<tr id='wpEnableAutoblockRow'>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg( 'ipbenableautoblock' ),
'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock,
@@ -270,7 +295,7 @@ class IPBlockForm {
if( self::canBlockEmail( $wgUser ) ) {
$wgOut->addHTML("
<tr id='wpEnableEmailBan'>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg( 'ipbemailban' ),
'wpEmailBan', 'wpEmailBan', $this->BlockEmail,
@@ -284,7 +309,7 @@ class IPBlockForm {
if( $wgUser->isAllowed( 'hideuser' ) ) {
$wgOut->addHTML("
<tr id='wpEnableHideUser'>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-input'><strong>" .
Xml::checkLabel( wfMsg( 'ipbhidename' ),
'wpHideName', 'wpHideName', $this->BlockHideName,
@@ -299,7 +324,7 @@ class IPBlockForm {
if( $wgUser->isLoggedIn() ) {
$wgOut->addHTML("
<tr id='wpEnableWatchUser'>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg( 'ipbwatchuser' ),
'wpWatchUser', 'wpWatchUser', $this->BlockWatchUser,
@@ -314,7 +339,7 @@ class IPBlockForm {
if( $wgBlockAllowsUTEdit ){
$wgOut->addHTML("
<tr id='wpAllowUsertalkRow'>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg( 'ipballowusertalk' ),
'wpAllowUsertalk', 'wpAllowUsertalk', $this->BlockAllowUsertalk,
@@ -326,18 +351,18 @@ class IPBlockForm {
$wgOut->addHTML("
<tr>
- <td style='padding-top: 1em'>&nbsp;</td>
+ <td style='padding-top: 1em'>&#160;</td>
<td class='mw-submit' style='padding-top: 1em'>" .
Xml::submitButton( wfMsg( $alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit' ),
- array( 'name' => 'wpBlock', 'tabindex' => '13', 'accesskey' => 's' ) ) . "
+ array( 'name' => 'wpBlock', 'tabindex' => '13' )
+ + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'blockip-block' ) ). "
</td>
</tr>" .
Xml::closeElement( 'table' ) .
- Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
- ( $alreadyBlocked ? Xml::hidden( 'wpChangeBlock', 1 ) : "" ) .
+ Html::hidden( 'wpEditToken', $wgUser->editToken() ) .
+ ( $alreadyBlocked ? Html::hidden( 'wpChangeBlock', 1 ) : "" ) .
Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' ) .
- Xml::tags( 'script', array( 'type' => 'text/javascript' ), 'updateBlockOptions()' ) . "\n"
+ Xml::closeElement( 'form' )
);
$wgOut->addHTML( $this->getConvenienceLinks() );
@@ -353,13 +378,39 @@ class IPBlockForm {
/**
* Can we do an email block?
- * @param User $user The sysop wanting to make a block
- * @return boolean
+ * @param $user User: the sysop wanting to make a block
+ * @return Boolean
*/
public static function canBlockEmail( $user ) {
global $wgEnableUserEmail, $wgSysopEmailBans;
return ( $wgEnableUserEmail && $wgSysopEmailBans && $user->isAllowed( 'blockemail' ) );
}
+
+ /**
+ * bug 15810: blocked admins should not be able to block/unblock
+ * others, and probably shouldn't be able to unblock themselves
+ * either.
+ * @param $user User, Int or String
+ */
+ public static function checkUnblockSelf( $user ) {
+ global $wgUser;
+ if ( is_int( $user ) ) {
+ $user = User::newFromId( $user );
+ } elseif ( is_string( $user ) ) {
+ $user = User::newFromName( $user );
+ }
+ if( $user instanceof User && $user->getId() == $wgUser->getId() ) {
+ # User is trying to unblock themselves
+ if ( $wgUser->isAllowed( 'unblockself' ) ) {
+ return true;
+ } else {
+ return 'ipbnounblockself';
+ }
+ } else {
+ # User is trying to block/unblock someone else
+ return 'ipbblocked';
+ }
+ }
/**
* Backend block code.
@@ -382,7 +433,7 @@ class IPBlockForm {
$matches = array();
if( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) {
# IPv4
- if( $wgSysopRangeBans ) {
+ if( $wgSysopRangeBans && $wgBlockCIDRLimit['IPv4'] != 32 ) {
if( !IP::isIPv4( $this->BlockAddress ) || $matches[2] > 32 ) {
return array( 'ip_range_invalid' );
} elseif ( $matches[2] < $wgBlockCIDRLimit['IPv4'] ) {
@@ -395,7 +446,7 @@ class IPBlockForm {
}
} elseif( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) {
# IPv6
- if( $wgSysopRangeBans ) {
+ if( $wgSysopRangeBans && $wgBlockCIDRLimit['IPv6'] != 128 ) {
if( !IP::isIPv6( $this->BlockAddress ) || $matches[2] > 128 ) {
return array( 'ip_range_invalid' );
} elseif( $matches[2] < $wgBlockCIDRLimit['IPv6'] ) {
@@ -410,7 +461,7 @@ class IPBlockForm {
# Username block
if( $wgSysopUserBans ) {
$user = User::newFromName( $this->BlockAddress );
- if( !is_null( $user ) && $user->getId() ) {
+ if( $user instanceof User && $user->getId() ) {
# Use canonical name
$userId = $user->getId();
$this->BlockAddress = $user->getName();
@@ -642,7 +693,7 @@ class IPBlockForm {
);
// Add suppression block entries if allowed
- if( $wgUser->isAllowed( 'hideuser' ) ) {
+ if( $wgUser->isAllowed( 'suppressionlog' ) ) {
LogEventsList::showLogExtract( $out, 'suppress', $title->getPrefixedText(), '',
array(
'lim' => 10,
@@ -759,32 +810,20 @@ class IPBlockForm {
* @return string
*/
private function getBlockListLink( $skin ) {
- $list = SpecialPage::getTitleFor( 'Ipblocklist' );
- $query = array();
-
- if( $this->BlockAddress ) {
- $addr = strtr( $this->BlockAddress, '_', ' ' );
- $message = wfMsg( 'ipb-blocklist-addr', $addr );
- $query['ip'] = $this->BlockAddress;
- } else {
- $message = wfMsg( 'ipb-blocklist' );
- }
-
return $skin->linkKnown(
- $list,
- htmlspecialchars( $message ),
- array(),
- $query
+ SpecialPage::getTitleFor( 'Ipblocklist' ),
+ wfMsg( 'ipb-blocklist' )
);
}
/**
* Block a list of selected users
- * @param array $users
- * @param string $reason
- * @param string $tag replaces user pages
- * @param string $talkTag replaces user talk pages
- * @returns array, list of html-safe usernames
+ *
+ * @param $users Array
+ * @param $reason String
+ * @param $tag String: replaces user pages
+ * @param $talkTag String: replaces user talk pages
+ * @return Array: list of html-safe usernames
*/
public static function doMassUserBlock( $users, $reason = '', $tag = '', $talkTag = '' ) {
global $wgUser;
diff --git a/includes/specials/SpecialBlockme.php b/includes/specials/SpecialBlockme.php
index f222e3c6..f5131f5f 100644
--- a/includes/specials/SpecialBlockme.php
+++ b/includes/specials/SpecialBlockme.php
@@ -1,37 +1,59 @@
<?php
/**
+ * Implements Special:Blockme
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 called by proxy_check.php to block open proxies
*
+ * @ingroup SpecialPage
*/
-function wfSpecialBlockme() {
- global $wgRequest, $wgBlockOpenProxies, $wgOut, $wgProxyKey;
-
- $ip = wfGetIP();
+class SpecialBlockme extends UnlistedSpecialPage {
- if( !$wgBlockOpenProxies || $wgRequest->getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) {
- $wgOut->addWikiMsg( 'proxyblocker-disabled' );
- return;
+ function __construct() {
+ parent::__construct( 'Blockme' );
}
- $blockerName = wfMsg( "proxyblocker" );
- $reason = wfMsg( "proxyblockreason" );
-
- $u = User::newFromName( $blockerName );
- $id = $u->idForName();
- if ( !$id ) {
- $u = User::newFromName( $blockerName );
- $u->addToDatabase();
- $u->setPassword( bin2hex( mt_rand(0, 0x7fffffff ) ) );
- $u->saveSettings();
- $id = $u->getID();
- }
+ function execute( $par ) {
+ global $wgRequest, $wgOut, $wgBlockOpenProxies, $wgProxyKey;
+
+ $this->setHeaders();
+ $this->outputHeader();
+
+ $ip = wfGetIP();
+ if( !$wgBlockOpenProxies || $wgRequest->getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) {
+ $wgOut->addWikiMsg( 'proxyblocker-disabled' );
+ return;
+ }
- $block = new Block( $ip, 0, $id, $reason, wfTimestampNow() );
- $block->insert();
+ $user = User::newFromName( wfMsgForContent( 'proxyblocker' ) );
+ if ( !$user->isLoggedIn() ) {
+ $user->addToDatabase();
+ }
+ $id = $user->getId();
+ $reason = wfMsg( 'proxyblockreason' );
- $wgOut->addWikiMsg( "proxyblocksuccess" );
+ $block = new Block( $ip, 0, $id, $reason, wfTimestampNow() );
+ $block->insert();
+
+ $wgOut->addWikiMsg( 'proxyblocksuccess' );
+ }
}
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
index 8ee5467a..67fb5404 100644
--- a/includes/specials/SpecialBooksources.php
+++ b/includes/specials/SpecialBooksources.php
@@ -1,4 +1,25 @@
<?php
+/**
+ * Implements Special:Booksources
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
/**
* Special page outputs information on sourcing a book with a particular ISBN
@@ -35,7 +56,7 @@ class SpecialBookSources extends SpecialPage {
$wgOut->addHTML( $this->makeForm() );
if( strlen( $this->isbn ) > 0 ) {
if( !self::isValidISBN( $this->isbn ) ) {
- $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1</div>", 'booksources-invalid-isbn' );
+ $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' );
}
$this->showList();
}
@@ -48,7 +69,6 @@ class SpecialBookSources extends SpecialPage {
public static function isValidISBN( $isbn ) {
$isbn = self::cleanIsbn( $isbn );
$sum = 0;
- $check = -1;
if( strlen( $isbn ) == 13 ) {
for( $i = 0; $i < 12; $i++ ) {
if($i % 2 == 0) {
@@ -78,7 +98,6 @@ class SpecialBookSources extends SpecialPage {
return false;
}
-
/**
* Trim ISBN and remove characters which aren't required
*
@@ -99,9 +118,9 @@ class SpecialBookSources extends SpecialPage {
$title = self::getTitleFor( 'Booksources' );
$form = '<fieldset><legend>' . wfMsgHtml( 'booksources-search-legend' ) . '</legend>';
$form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- $form .= Xml::hidden( 'title', $title->getPrefixedText() );
+ $form .= Html::hidden( 'title', $title->getPrefixedText() );
$form .= '<p>' . Xml::inputLabel( wfMsg( 'booksources-isbn' ), 'isbn', 'isbn', 20, $this->isbn );
- $form .= '&nbsp;' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '</p>';
+ $form .= '&#160;' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '</p>';
$form .= Xml::closeElement( 'form' );
$form .= '</fieldset>';
return $form;
@@ -147,6 +166,6 @@ class SpecialBookSources extends SpecialPage {
*/
private function makeListItem( $label, $url ) {
$url = str_replace( '$1', $this->isbn, $url );
- return '<li><a href="' . htmlspecialchars( $url ) . '">' . htmlspecialchars( $label ) . '</a></li>';
+ return '<li><a href="' . htmlspecialchars( $url ) . '" class="external">' . htmlspecialchars( $label ) . '</a></li>';
}
}
diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php
index b6ae2ada..98b02126 100644
--- a/includes/specials/SpecialBrokenRedirects.php
+++ b/includes/specials/SpecialBrokenRedirects.php
@@ -1,12 +1,30 @@
<?php
/**
+ * Implements Special:Brokenredirects
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 listing redirects to non existent page. Those should be
+ * A special page listing redirects tonon existent page. Those should be
* fixed to point to an existing page.
+ *
* @ingroup SpecialPage
*/
class BrokenRedirectsPage extends PageQueryPage {
@@ -61,7 +79,7 @@ class BrokenRedirectsPage extends PageQueryPage {
// $toObj may very easily be false if the $result list is cached
if ( !is_object( $toObj ) ) {
- return '<s>' . $skin->link( $fromObj ) . '</s>';
+ return '<del>' . $skin->link( $fromObj ) . '</del>';
}
$from = $skin->linkKnown(
diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php
index eb49fdbc..c2dd40cd 100644
--- a/includes/specials/SpecialCategories.php
+++ b/includes/specials/SpecialCategories.php
@@ -1,29 +1,57 @@
<?php
/**
+ * Implements Special:Categories
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
-function wfSpecialCategories( $par=null ) {
- global $wgOut, $wgRequest;
+/**
+ * @ingroup SpecialPage
+ */
+class SpecialCategories extends SpecialPage {
+
+ function __construct() {
+ parent::__construct( 'Categories' );
+ }
+
+ function execute( $par ) {
+ global $wgOut, $wgRequest;
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $wgOut->allowClickjacking();
- if( $par == '' ) {
- $from = $wgRequest->getText( 'from' );
- } else {
- $from = $par;
+ $from = $wgRequest->getText( 'from', $par );
+
+ $cap = new CategoryPager( $from );
+ $cap->doQuery();
+
+ $wgOut->addHTML(
+ Html::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) .
+ wfMsgExt( 'categoriespagetext', array( 'parse' ), $cap->getNumRows() ) .
+ $cap->getStartForm( $from ) .
+ $cap->getNavigationBar() .
+ '<ul>' . $cap->getBody() . '</ul>' .
+ $cap->getNavigationBar() .
+ Html::closeElement( 'div' )
+ );
}
- $wgOut->allowClickjacking();
- $cap = new CategoryPager( $from );
- $cap->doQuery();
- $wgOut->addHTML(
- XML::openElement( 'div', array('class' => 'mw-spcontent') ) .
- wfMsgExt( 'categoriespagetext', array( 'parse' ), $cap->getNumRows() ) .
- $cap->getStartForm( $from ) .
- $cap->getNavigationBar() .
- '<ul>' . $cap->getBody() . '</ul>' .
- $cap->getNavigationBar() .
- XML::closeElement( 'div' )
- );
}
/**
@@ -77,7 +105,7 @@ class CategoryPager extends AlphabeticPager {
$this->mResult->rewind();
- while ( $row = $this->mResult->fetchObject() ) {
+ foreach ( $this->mResult as $row ) {
$batch->addObj( Title::makeTitleSafe( NS_CATEGORY, $row->cat_title ) );
}
$batch->execute();
@@ -100,7 +128,7 @@ class CategoryPager extends AlphabeticPager {
return
Xml::tags( 'form', array( 'method' => 'get', 'action' => $wgScript ),
- Xml::hidden( 'title', $t->getPrefixedText() ) .
+ Html::hidden( 'title', $t->getPrefixedText() ) .
Xml::fieldset( wfMsg( 'categories' ),
Xml::inputLabel( wfMsg( 'categoriesfrom' ),
'from', 'from', 20, $from ) .
diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php
new file mode 100644
index 00000000..4650fc94
--- /dev/null
+++ b/includes/specials/SpecialComparePages.php
@@ -0,0 +1,170 @@
+<?php
+/**
+ * Implements Special:ComparePages
+ *
+ * Copyright © 2010 Derk-Jan Hartman <hartman@videolan.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 SpecialPage
+ */
+
+/**
+ * Implements Special:ComparePages
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialComparePages extends SpecialPage {
+
+ // Stored objects
+ protected $opts, $skin;
+
+ // Some internal settings
+ protected $showNavigation = false;
+
+ public function __construct() {
+ parent::__construct( 'ComparePages' );
+ }
+
+ protected function setup( $par ) {
+ global $wgRequest, $wgUser;
+
+ // Options
+ $opts = new FormOptions();
+ $this->opts = $opts; // bind
+ $opts->add( 'page1', '' );
+ $opts->add( 'page2', '' );
+ $opts->add( 'rev1', '' );
+ $opts->add( 'rev2', '' );
+ $opts->add( 'action', '' );
+
+ // Set values
+ $opts->fetchValuesFromRequest( $wgRequest );
+
+ $title1 = Title::newFromText( $opts->getValue( 'page1' ) );
+ $title2 = Title::newFromText( $opts->getValue( 'page2' ) );
+
+ if( $title1 && $title1->exists() && $opts->getValue( 'rev1' ) == '' ) {
+ $pda = new Article( $title1 );
+ $pdi = $pda->getID();
+ $pdLastRevision = Revision::loadFromPageId( wfGetDB( DB_SLAVE ), $pdi );
+ $opts->setValue( 'rev1', $pdLastRevision->getId() );
+ } elseif ( $opts->getValue( 'rev1' ) != '' ) {
+ $pdrev = Revision::newFromId( $opts->getValue( 'rev1' ) );
+ if( $pdrev ) $opts->setValue( 'page1', $pdrev->getTitle()->getPrefixedText() );
+ }
+ if( $title2 && $title2->exists() && $opts->getValue( 'rev2' ) == '' ) {
+ $pda = new Article( $title2 );
+ $pdi = $pda->getID();
+ $pdLastRevision = Revision::loadFromPageId( wfGetDB( DB_SLAVE ), $pdi );
+ $opts->setValue('rev2', $pdLastRevision->getId() );
+ } elseif ( $opts->getValue( 'rev2' ) != '' ) {
+ $pdrev = Revision::newFromId( $opts->getValue( 'rev2' ) );
+ if( $pdrev ) $opts->setValue( 'page2', $pdrev->getTitle()->getPrefixedText() );
+ }
+
+ // Store some objects
+ $this->skin = $wgUser->getSkin();
+ }
+
+ /**
+ * Show a form for filtering namespace and username
+ *
+ * @param $par String
+ * @return String
+ */
+ public function execute( $par ) {
+ $this->setHeaders();
+ $this->outputHeader();
+
+ $this->setup( $par );
+
+ // Settings
+ $this->form();
+
+ if( $this->opts->getValue( 'rev1' ) && $this->opts->getValue( 'rev2' ) ) {
+ $de = new DifferenceEngine( null,
+ $this->opts->getValue( 'rev1' ),
+ $this->opts->getValue( 'rev2' ),
+ null, // rcid
+ ( $this->opts->getValue( 'action' ) == 'purge' ),
+ false );
+ $de->showDiffPage( true );
+ }
+ }
+
+ protected function form() {
+ global $wgOut, $wgScript;
+
+ // Consume values
+ $page1 = $this->opts->consumeValue( 'page1' );
+ $page2 = $this->opts->consumeValue( 'page2' );
+ $rev1 = $this->opts->consumeValue( 'rev1' );
+ $rev2 = $this->opts->consumeValue( 'rev2' );
+
+ // Store query values in hidden fields so that form submission doesn't lose them
+ $hidden = array();
+ foreach ( $this->opts->getUnconsumedValues() as $key => $value ) {
+ $hidden[] = Html::hidden( $key, $value );
+ }
+ $hidden = implode( "\n", $hidden );
+
+ $form = Html::openElement( 'form', array( 'action' => $wgScript ) ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ Xml::fieldset( wfMsg( 'compare-selector' ) ) .
+ Html::openElement( 'table', array( 'id' => 'mw-diff-table', 'style' => 'width:100%' ) ) .
+ "<tr>
+ <td class='mw-label' style='width:10%'>" .
+ Html::element( 'label', array( 'for' => 'page1' ), wfMsg( 'compare-page1' ) ) .
+ "</td>
+ <td class='mw-input' style='width:40%'>" .
+ Html::input( 'page1', $page1, 'text', array( 'size' => 40, 'id' => 'page1' ) ) .
+ "</td>
+ <td class='mw-label' style='width:10%'>" .
+ Html::element( 'label', array( 'for' => 'page2' ), wfMsg( 'compare-page2' ) ) .
+ "</td>
+ <td class='mw-input' style='width:40%'>" .
+ Html::input( 'page2', $page2, 'text', array( 'size' => 40, 'id' => 'page2' ) ) .
+ "</td>
+ </tr>" .
+ "<tr>
+ <td class='mw-label'>" .
+ Html::element( 'label', array( 'for' => 'rev1' ), wfMsg( 'compare-rev1' ) ) .
+ "</td>
+ <td class='mw-input'>" .
+ Html::input( 'rev1', $rev1, 'text', array( 'size' => 8, 'id' => 'rev1' ) ) .
+ "</td>
+ <td class='mw-label'>" .
+ Html::element( 'label', array( 'for' => 'rev2' ), wfMsg( 'compare-rev2' ) ) .
+ "</td>
+ <td class='mw-input'>" .
+ Html::input( 'rev2', $rev2, 'text', array( 'size' => 8, 'id' => 'rev2' ) ) .
+ "</td>
+ </tr>" .
+ "<tr> <td></td>
+ <td class='mw-submit' colspan='3'>" .
+ Xml::submitButton( wfMsg( 'compare-submit' ) ) .
+ "</td>
+ </tr>" .
+ Html::closeElement( 'table' ) .
+ Html::closeElement( 'fieldset' ) .
+ $hidden .
+ Html::closeElement( 'form' );
+
+ $wgOut->addHTML( $form );
+ }
+}
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index 372a574c..e556a60b 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -1,4 +1,25 @@
<?php
+/**
+ * Implements Special:Confirmemail and Special:Invalidateemail
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 allows users to request email confirmation message, and handles
@@ -25,6 +46,12 @@ class EmailConfirmation extends UnlistedSpecialPage {
function execute( $code ) {
global $wgUser, $wgOut;
$this->setHeaders();
+
+ if ( wfReadOnly() ) {
+ $wgOut->readOnlyPage();
+ return;
+ }
+
if( empty( $code ) ) {
if( $wgUser->isLoggedIn() ) {
if( User::isValidEmailAddr( $wgUser->getEmail() ) ) {
@@ -54,11 +81,11 @@ class EmailConfirmation extends UnlistedSpecialPage {
function showRequestForm() {
global $wgOut, $wgUser, $wgLang, $wgRequest;
if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getText( 'token' ) ) ) {
- $ok = $wgUser->sendConfirmationMail();
- if ( WikiError::isError( $ok ) ) {
- $wgOut->addWikiMsg( 'confirmemail_sendfailed', $ok->toString() );
- } else {
+ $status = $wgUser->sendConfirmationMail();
+ if ( $status->isGood() ) {
$wgOut->addWikiMsg( 'confirmemail_sent' );
+ } else {
+ $wgOut->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
}
} else {
if( $wgUser->isEmailConfirmed() ) {
@@ -71,11 +98,11 @@ class EmailConfirmation extends UnlistedSpecialPage {
$wgOut->addWikiMsg( 'emailauthenticated', $time, $d, $t );
}
if( $wgUser->isEmailConfirmationPending() ) {
- $wgOut->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1</div>", 'confirmemail_pending' );
+ $wgOut->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1\n</div>", 'confirmemail_pending' );
}
$wgOut->addWikiMsg( 'confirmemail_text' );
$form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl() ) );
- $form .= Xml::hidden( 'token', $wgUser->editToken() );
+ $form .= Html::hidden( 'token', $wgUser->editToken() );
$form .= Xml::submitButton( wfMsg( 'confirmemail_send' ) );
$form .= Xml::closeElement( 'form' );
$wgOut->addHTML( $form );
@@ -121,6 +148,13 @@ class EmailInvalidation extends UnlistedSpecialPage {
function execute( $code ) {
$this->setHeaders();
+
+ if ( wfReadOnly() ) {
+ global $wgOut;
+ $wgOut->readOnlyPage();
+ return;
+ }
+
$this->attemptInvalidate( $code );
}
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index b5d6107a..cee01a7f 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -1,10 +1,32 @@
<?php
/**
- * Special:Contributions, show user contributions in a paged list
+ * Implements Special:Contributions
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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:Contributions, show user contributions in a paged list
+ *
+ * @ingroup SpecialPage
+ */
+
class SpecialContributions extends SpecialPage {
public function __construct() {
@@ -12,7 +34,7 @@ class SpecialContributions extends SpecialPage {
}
public function execute( $par ) {
- global $wgUser, $wgOut, $wgLang, $wgRequest;
+ global $wgUser, $wgOut, $wgRequest;
$this->setHeaders();
$this->outputHeader();
@@ -34,6 +56,8 @@ class SpecialContributions extends SpecialPage {
$this->opts['contribs'] = 'newbie';
}
+ $this->opts['deletedOnly'] = $wgRequest->getBool( 'deletedOnly' );
+
if( !strlen( $target ) ) {
$wgOut->addHTML( $this->getForm() );
return;
@@ -41,6 +65,7 @@ class SpecialContributions extends SpecialPage {
$this->opts['limit'] = $wgRequest->getInt( 'limit', $wgUser->getOption('rclimit') );
$this->opts['target'] = $target;
+ $this->opts['topOnly'] = $wgRequest->getBool( 'topOnly' );
$nt = Title::makeTitleSafe( NS_USER, $target );
if( !$nt ) {
@@ -64,10 +89,10 @@ class SpecialContributions extends SpecialPage {
$this->opts['namespace'] = '';
}
- $this->opts['tagfilter'] = (string) $wgRequest->getVal( 'tagfilter' );
-
+ $this->opts['tagFilter'] = (string) $wgRequest->getVal( 'tagFilter' );
+
// Allows reverts to have the bot flag in recent changes. It is just here to
- // be passed in the form at the top of the page
+ // be passed in the form at the top of the page
if( $wgUser->isAllowed( 'markbotedits' ) && $wgRequest->getBool( 'bot' ) ) {
$this->opts['bot'] = '1';
}
@@ -81,7 +106,7 @@ class SpecialContributions extends SpecialPage {
$this->opts['year'] = $wgRequest->getIntOrNull( 'year' );
$this->opts['month'] = $wgRequest->getIntOrNull( 'month' );
}
-
+
// Add RSS/atom links
$this->setSyndicated();
$feedType = $wgRequest->getVal( 'feed' );
@@ -93,7 +118,14 @@ class SpecialContributions extends SpecialPage {
$wgOut->addHTML( $this->getForm() );
- $pager = new ContribsPager( $target, $this->opts['namespace'], $this->opts['year'], $this->opts['month'] );
+ $pager = new ContribsPager( array(
+ 'target' => $target,
+ 'namespace' => $this->opts['namespace'],
+ 'year' => $this->opts['year'],
+ 'month' => $this->opts['month'],
+ 'deletedOnly' => $this->opts['deletedOnly'],
+ 'topOnly' => $this->opts['topOnly'],
+ ) );
if( !$pager->getNumRows() ) {
$wgOut->addWikiMsg( 'nocontribs', $target );
} else {
@@ -132,7 +164,7 @@ class SpecialContributions extends SpecialPage {
}
}
}
-
+
protected function setSyndicated() {
global $wgOut;
$wgOut->setSyndicated( true );
@@ -141,8 +173,8 @@ class SpecialContributions extends SpecialPage {
/**
* Generates the subheading with links
- * @param Title $nt @see Title object for the target
- * @param integer $id User ID for the target
+ * @param $nt Title object for the target
+ * @param $id Integer: User ID for the target
* @return String: appropriately-escaped HTML to be output literally
* @todo Fixme: almost the same as getSubTitle in SpecialDeletedContributions.php. Could be combined.
*/
@@ -169,12 +201,12 @@ class SpecialContributions extends SpecialPage {
wfMsgHtml( 'change-blocklink' )
);
$tools[] = $sk->linkKnown( # Unblock link
- SpecialPage::getTitleFor( 'BlockList' ),
+ SpecialPage::getTitleFor( 'Ipblocklist' ),
wfMsgHtml( 'unblocklink' ),
array(),
array(
'action' => 'unblock',
- 'ip' => $nt->getDBkey()
+ 'ip' => $nt->getDBkey()
)
);
}
@@ -196,6 +228,14 @@ class SpecialContributions extends SpecialPage {
)
);
}
+ # Uploads
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Listfiles' ),
+ wfMsgHtml( 'sp-contributions-uploads' ),
+ array(),
+ array( 'user' => $nt->getText() )
+ );
+
# Other logs link
$tools[] = $sk->linkKnown(
SpecialPage::getTitleFor( 'Log' ),
@@ -236,7 +276,9 @@ class SpecialContributions extends SpecialPage {
'lim' => 1,
'showIfEmpty' => false,
'msgKey' => array(
- 'sp-contributions-blocked-notice',
+ $userObj->isAnon() ?
+ 'sp-contributions-blocked-notice-anon' :
+ 'sp-contributions-blocked-notice',
$nt->getText() # Support GENDER in 'sp-contributions-blocked-notice'
),
'offset' => '' # don't use $wgRequest parameter offset
@@ -258,99 +300,103 @@ class SpecialContributions extends SpecialPage {
/**
* Generates the namespace selector form with hidden attributes.
- * @param $this->opts Array: the options to be included.
+ * @return String: HTML fragment
*/
protected function getForm() {
global $wgScript;
-
+
$this->opts['title'] = $this->getTitle()->getPrefixedText();
if( !isset( $this->opts['target'] ) ) {
$this->opts['target'] = '';
} else {
$this->opts['target'] = str_replace( '_' , ' ' , $this->opts['target'] );
}
-
+
if( !isset( $this->opts['namespace'] ) ) {
$this->opts['namespace'] = '';
}
-
+
if( !isset( $this->opts['contribs'] ) ) {
$this->opts['contribs'] = 'user';
}
-
+
if( !isset( $this->opts['year'] ) ) {
$this->opts['year'] = '';
}
-
+
if( !isset( $this->opts['month'] ) ) {
$this->opts['month'] = '';
}
-
+
if( $this->opts['contribs'] == 'newbie' ) {
$this->opts['target'] = '';
}
- if( !isset( $this->opts['tagfilter'] ) ) {
- $this->opts['tagfilter'] = '';
+ if( !isset( $this->opts['tagFilter'] ) ) {
+ $this->opts['tagFilter'] = '';
}
-
- $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- # Add hidden params for tracking
+
+ if( !isset( $this->opts['topOnly'] ) ) {
+ $this->opts['topOnly'] = false;
+ }
+
+ $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'class' => 'mw-contributions-form' ) );
+
+ # Add hidden params for tracking except for parameters in $skipParameters
+ $skipParameters = array( 'namespace', 'deletedOnly', 'target', 'contribs', 'year', 'month', 'topOnly' );
foreach ( $this->opts as $name => $value ) {
- if( in_array( $name, array( 'namespace', 'target', 'contribs', 'year', 'month' ) ) ) {
+ if( in_array( $name, $skipParameters ) ) {
continue;
}
- $f .= "\t" . Xml::hidden( $name, $value ) . "\n";
+ $f .= "\t" . Html::hidden( $name, $value ) . "\n";
}
- $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] );
-
- $f .= '<fieldset>' .
- Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) .
- Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parsemag' ) ),
- 'contribs', 'newbie' , 'newbie', $this->opts['contribs'] == 'newbie' ? true : false ) . '<br />' .
- Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parsemag' ) ),
- 'contribs' , 'user', 'user', $this->opts['contribs'] == 'user' ? true : false ) . ' ' .
+ $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagFilter'] );
+
+ $f .= Xml::fieldset( wfMsg( 'sp-contributions-search' ) ) .
+ Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parsemag' ) ),
+ 'contribs', 'newbie' , 'newbie', $this->opts['contribs'] == 'newbie' ) . '<br />' .
+ Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parsemag' ) ),
+ 'contribs' , 'user', 'user', $this->opts['contribs'] == 'user' ) . ' ' .
Html::input( 'target', $this->opts['target'], 'text', array(
'size' => '20',
'required' => ''
) + ( $this->opts['target'] ? array() : array( 'autofocus' ) ) ) . ' '.
- '<span style="white-space: nowrap">' .
- Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' .
- Xml::namespaceSelector( $this->opts['namespace'], '' ) .
- '</span>' .
- ( $tagFilter ? Xml::tags( 'p', null, implode( '&nbsp;', $tagFilter ) ) : '' ) .
- Xml::openElement( 'p' ) .
- '<span style="white-space: nowrap">' .
- Xml::dateMenu( $this->opts['year'], $this->opts['month'] ) .
- '</span>' . ' ' .
- Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) .
- Xml::closeElement( 'p' );
-
+ Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
+ Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' .
+ Xml::namespaceSelector( $this->opts['namespace'], '' )
+ ) .
+ Xml::checkLabel( wfMsg( 'history-show-deleted' ),
+ 'deletedOnly', 'mw-show-deleted-only', $this->opts['deletedOnly'] ) . '<br />' .
+ Xml::tags( 'p', null, Xml::checkLabel( wfMsg( 'sp-contributions-toponly' ),
+ 'topOnly', 'mw-show-top-only', $this->opts['topOnly'] ) ) .
+ ( $tagFilter ? Xml::tags( 'p', null, implode( '&#160;', $tagFilter ) ) : '' ) .
+ Html::rawElement( 'p', array( 'style' => 'white-space: nowrap' ),
+ Xml::dateMenu( $this->opts['year'], $this->opts['month'] ) . ' ' .
+ Xml::submitButton( wfMsg( 'sp-contributions-submit' ) )
+ ) . ' ';
$explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' );
- if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) )
+ if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) {
$f .= "<p id='mw-sp-contributions-explain'>{$explain}</p>";
-
- $f .= '</fieldset>' .
+ }
+ $f .= Xml::closeElement('fieldset' ) .
Xml::closeElement( 'form' );
return $f;
}
-
+
/**
* Output a subscription feed listing recent edits to this page.
- * @param string $type
+ * @param $type String
*/
protected function feed( $type ) {
- global $wgRequest, $wgFeed, $wgFeedClasses, $wgFeedLimit;
+ global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgOut;
if( !$wgFeed ) {
- global $wgOut;
$wgOut->addWikiMsg( 'feed-unavailable' );
return;
}
if( !isset( $wgFeedClasses[$type] ) ) {
- global $wgOut;
$wgOut->addWikiMsg( 'feed-invalid' );
return;
}
@@ -360,19 +406,26 @@ class SpecialContributions extends SpecialPage {
wfMsgExt( 'tagline', 'parsemag' ),
$this->getTitle()->getFullUrl() . "/" . urlencode($this->opts['target'])
);
-
+
// Already valid title
$nt = Title::makeTitleSafe( NS_USER, $this->opts['target'] );
$target = $this->opts['target'] == 'newbies' ? 'newbies' : $nt->getText();
-
- $pager = new ContribsPager( $target, $this->opts['namespace'],
- $this->opts['year'], $this->opts['month'], $this->opts['tagfilter'] );
+
+ $pager = new ContribsPager( array(
+ 'target' => $target,
+ 'namespace' => $this->opts['namespace'],
+ 'year' => $this->opts['year'],
+ 'month' => $this->opts['month'],
+ 'tagFilter' => $this->opts['tagFilter'],
+ 'deletedOnly' => $this->opts['deletedOnly'],
+ 'topOnly' => $this->opts['topOnly'],
+ ) );
$pager->mLimit = min( $this->opts['limit'], $wgFeedLimit );
$feed->outHeader();
if( $pager->getNumRows() > 0 ) {
- while( $row = $pager->mResult->fetchObject() ) {
+ foreach ( $pager->mResult as $row ) {
$feed->outItem( $this->feedItem( $row ) );
}
}
@@ -380,10 +433,10 @@ class SpecialContributions extends SpecialPage {
}
protected function feedTitle() {
- global $wgContLanguageCode, $wgSitename;
+ global $wgLanguageCode, $wgSitename;
$page = SpecialPage::getPage( 'Contributions' );
$desc = $page->getDescription();
- return "$wgSitename - $desc [$wgContLanguageCode]";
+ return "$wgSitename - $desc [$wgLanguageCode]";
}
protected function feedItem( $row ) {
@@ -413,7 +466,7 @@ class SpecialContributions extends SpecialPage {
protected function feedItemDesc( $revision ) {
if( $revision ) {
return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) .
- htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
+ htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
"</p>\n<hr />\n<div>" .
nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
}
@@ -431,7 +484,7 @@ class ContribsPager extends ReverseChronologicalPager {
var $namespace = '', $mDb;
var $preventClickjacking = false;
- function __construct( $target, $namespace = false, $year = false, $month = false, $tagFilter = false ) {
+ function __construct( $options ) {
parent::__construct();
$msgs = array( 'uctop', 'diff', 'newarticle', 'rollbacklink', 'diff', 'hist', 'rev-delundel', 'pipe-separator' );
@@ -440,10 +493,15 @@ class ContribsPager extends ReverseChronologicalPager {
$this->messages[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
}
- $this->target = $target;
- $this->namespace = $namespace;
- $this->tagFilter = $tagFilter;
+ $this->target = isset( $options['target'] ) ? $options['target'] : '';
+ $this->namespace = isset( $options['namespace'] ) ? $options['namespace'] : '';
+ $this->tagFilter = isset( $options['tagFilter'] ) ? $options['tagFilter'] : false;
+
+ $this->deletedOnly = !empty( $options['deletedOnly'] );
+ $this->topOnly = !empty( $options['topOnly'] );
+ $year = isset( $options['year'] ) ? $options['year'] : false;
+ $month = isset( $options['month'] ) ? $options['month'] : false;
$this->getDateCond( $year, $month );
$this->mDb = wfGetDB( DB_SLAVE, 'contributions' );
@@ -458,7 +516,7 @@ class ContribsPager extends ReverseChronologicalPager {
function getQueryInfo() {
global $wgUser;
list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond();
-
+
$conds = array_merge( $userCond, $this->getNamespaceCond() );
// Paranoia: avoid brute force searches (bug 17342)
if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
@@ -468,12 +526,12 @@ class ContribsPager extends ReverseChronologicalPager {
' != ' . Revision::SUPPRESSED_USER;
}
$join_cond['page'] = array( 'INNER JOIN', 'page_id=rev_page' );
-
+
$queryInfo = array(
'tables' => $tables,
'fields' => array(
'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'page_is_redirect',
- 'page_len','rev_id', 'rev_page', 'rev_text_id', 'rev_timestamp', 'rev_comment',
+ 'page_len','rev_id', 'rev_page', 'rev_text_id', 'rev_timestamp', 'rev_comment',
'rev_minor_edit', 'rev_user', 'rev_user_text', 'rev_parent_id', 'rev_deleted'
),
'conds' => $conds,
@@ -510,6 +568,12 @@ class ContribsPager extends ReverseChronologicalPager {
$condition['rev_user_text'] = $this->target;
$index = 'usertext_timestamp';
}
+ if( $this->deletedOnly ) {
+ $condition[] = "rev_deleted != '0'";
+ }
+ if( $this->topOnly ) {
+ $condition[] = "rev_id = page_latest";
+ }
return array( $tables, $index, $condition, $join_conds );
}
@@ -552,7 +616,6 @@ class ContribsPager extends ReverseChronologicalPager {
$classes = array();
$page = Title::newFromRow( $row );
- $page->resetArticleId( $row->rev_page ); // use process cache
$link = $sk->link(
$page,
htmlspecialchars( $page->getPrefixedText() ),
@@ -560,7 +623,7 @@ class ContribsPager extends ReverseChronologicalPager {
$page->isRedirect() ? array( 'redirect' => 'no' ) : array()
);
# Mark current revisions
- $difftext = $topmarktext = '';
+ $topmarktext = '';
if( $row->rev_id == $row->page_latest ) {
$topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
# Add rollback link
@@ -594,15 +657,18 @@ class ContribsPager extends ReverseChronologicalPager {
$comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true );
$date = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true );
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- $d = '<span class="history-deleted">' . $date . '</span>';
- } else {
+ if( $rev->userCan( Revision::DELETED_TEXT ) ) {
$d = $sk->linkKnown(
$page,
htmlspecialchars($date),
array(),
array( 'oldid' => intval( $row->rev_id ) )
);
+ } else {
+ $d = htmlspecialchars( $date );
+ }
+ if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $d = '<span class="history-deleted">' . $d . '</span>';
}
if( $this->target == 'newbies' ) {
@@ -645,7 +711,7 @@ class ContribsPager extends ReverseChronologicalPager {
$diffHistLinks = '(' . $difftext . $this->messages['pipe-separator'] . $histlink . ')';
$ret = "{$del}{$d} {$diffHistLinks} {$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}";
-
+
# Denote if username is redacted for this edit
if( $rev->isDeleted( Revision::DELETED_USER ) ) {
$ret .= " <strong>" . wfMsgHtml('rev-deleted-user-contribs') . "</strong>";
@@ -674,6 +740,17 @@ class ContribsPager extends ReverseChronologicalPager {
return $this->mDb;
}
+ /**
+ * Overwrite Pager function and return a helpful comment
+ */
+ function getSqlComment() {
+ if ( $this->namespace || $this->deletedOnly ) {
+ return 'contributions page filtered for namespace or RevisionDeleted edits'; // potentially slow, see CR r58153
+ } else {
+ return 'contributions page unfiltered';
+ }
+ }
+
protected function preventClickjacking() {
$this->preventClickjacking = true;
}
diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php
index a8416c97..dfa053aa 100644
--- a/includes/specials/SpecialDeadendpages.php
+++ b/includes/specials/SpecialDeadendpages.php
@@ -1,10 +1,29 @@
<?php
/**
+ * Implements Special:Deadenpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 pages that contain no link to other pages
+ *
* @ingroup SpecialPage
*/
class DeadendPagesPage extends PageQueryPage {
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index 8884bb22..92e22586 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -1,5 +1,27 @@
<?php
/**
+ * Implements Special:DeletedContributions
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
* Implements Special:DeletedContributions to display archived revisions
* @ingroup SpecialPage
*/
@@ -258,7 +280,7 @@ class DeletedContributionsPage extends SpecialPage {
return;
}
- global $wgOut, $wgLang, $wgRequest;
+ global $wgOut, $wgRequest;
$wgOut->setPageTitle( wfMsgExt( 'deletedcontributions-title', array( 'parsemag' ) ) );
@@ -328,8 +350,8 @@ class DeletedContributionsPage extends SpecialPage {
/**
* Generates the subheading with links
- * @param Title $nt @see Title object for the target
- * @param integer $id User ID for the target
+ * @param $nt Title object for the target
+ * @param $id Integer: User ID for the target
* @return String: appropriately-escaped HTML to be output literally
* @todo Fixme: almost the same as contributionsSub in SpecialContributions.php. Could be combined.
*/
@@ -445,7 +467,7 @@ class DeletedContributionsPage extends SpecialPage {
* @param $options Array: the options to be included.
*/
function getForm( $options ) {
- global $wgScript, $wgRequest;
+ global $wgScript;
$options['title'] = SpecialPage::getTitleFor( 'DeletedContributions' )->getPrefixedText();
if ( !isset( $options['target'] ) ) {
@@ -472,7 +494,7 @@ class DeletedContributionsPage extends SpecialPage {
if ( in_array( $name, array( 'namespace', 'target', 'contribs' ) ) ) {
continue;
}
- $f .= "\t" . Xml::hidden( $name, $value ) . "\n";
+ $f .= "\t" . Html::hidden( $name, $value ) . "\n";
}
$f .= Xml::openElement( 'fieldset' ) .
diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php
index 1941112a..3e706189 100644
--- a/includes/specials/SpecialDisambiguations.php
+++ b/includes/specials/SpecialDisambiguations.php
@@ -1,10 +1,29 @@
<?php
/**
+ * Implements Special:Disambiguations
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 lists pages containing links to disambiguations pages
+ *
* @ingroup SpecialPage
*/
class DisambiguationsPage extends PageQueryPage {
@@ -22,6 +41,8 @@ class DisambiguationsPage extends PageQueryPage {
}
function getSQL() {
+ global $wgContentNamespaces;
+
$dbr = wfGetDB( DB_SLAVE );
$dMsgText = wfMsgForContent('disambiguationspage');
@@ -48,11 +69,9 @@ class DisambiguationsPage extends PageQueryPage {
'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()),
__METHOD__ );
- while ( $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title ));
}
-
- $dbr->freeResult( $res );
}
$set = $linkBatch->constructSet( 'lb.tl', $dbr );
@@ -64,12 +83,18 @@ class DisambiguationsPage extends PageQueryPage {
list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' );
+ if ( $wgContentNamespaces ) {
+ $nsclause = 'IN (' . $dbr->makeList( $wgContentNamespaces ) . ')';
+ } else {
+ $nsclause = '= ' . NS_MAIN;
+ }
+
$sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace,"
." pb.page_title AS title, la.pl_from AS value"
." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa"
." WHERE $set" # disambiguation template(s)
.' AND pa.page_id = la.pl_from'
- .' AND pa.page_namespace = ' . NS_MAIN # Limit to just articles in the main namespace
+ .' AND pa.page_namespace ' . $nsclause
.' AND pb.page_id = lb.tl_from'
.' AND pb.page_namespace = la.pl_namespace'
.' AND pb.page_title = la.pl_title'
diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php
index 893fee9e..c7f63210 100644
--- a/includes/specials/SpecialDoubleRedirects.php
+++ b/includes/specials/SpecialDoubleRedirects.php
@@ -1,5 +1,22 @@
<?php
/**
+ * Implements Special:DoubleRedirects
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
@@ -7,6 +24,7 @@
/**
* A special page listing redirects to redirecting page.
* The software will automatically not follow double redirects, to prevent loops.
+ *
* @ingroup SpecialPage
*/
class DoubleRedirectsPage extends PageQueryPage {
@@ -70,11 +88,10 @@ class DoubleRedirectsPage extends PageQueryPage {
$res = $dbr->query( $sql, $fname );
if ( $res ) {
$result = $dbr->fetchObject( $res );
- $dbr->freeResult( $res );
}
}
if ( !$result ) {
- return '<s>' . $skin->link( $titleA, null, array(), array( 'redirect' => 'no' ) ) . '</s>';
+ return '<del>' . $skin->link( $titleA, null, array(), array( 'redirect' => 'no' ) ) . '</del>';
}
$titleB = Title::makeTitle( $result->nsb, $result->tb );
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index 48088ded..61271227 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -1,329 +1,303 @@
<?php
/**
+ * Implements Special:Emailuser
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
/**
- * Constructor for Special:Emailuser.
+ * A special page that allows users to send e-mails to other users
+ *
+ * @ingroup SpecialPage
*/
-function wfSpecialEmailuser( $par ) {
- global $wgRequest, $wgUser, $wgOut;
-
- if ( !EmailUserForm::userEmailEnabled() ) {
- $wgOut->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
- return;
- }
-
- $action = $wgRequest->getVal( 'action' );
- $target = isset($par) ? $par : $wgRequest->getVal( 'target' );
- $targetUser = EmailUserForm::validateEmailTarget( $target );
+class SpecialEmailUser extends UnlistedSpecialPage {
+ protected $mTarget;
- if ( !( $targetUser instanceof User ) ) {
- $wgOut->showErrorPage( $targetUser.'title', $targetUser.'text' );
- return;
+ public function __construct(){
+ parent::__construct( 'Emailuser' );
}
- $form = new EmailUserForm( $targetUser,
- $wgRequest->getText( 'wpText' ),
- $wgRequest->getText( 'wpSubject' ),
- $wgRequest->getBool( 'wpCCMe' ) );
- if ( $action == 'success' ) {
- $form->showSuccess();
- return;
+ protected function getFormFields(){
+ global $wgUser;
+ return array(
+ 'From' => array(
+ 'type' => 'info',
+ 'raw' => 1,
+ 'default' => $wgUser->getSkin()->link(
+ $wgUser->getUserPage(),
+ htmlspecialchars( $wgUser->getName() )
+ ),
+ 'label-message' => 'emailfrom',
+ 'id' => 'mw-emailuser-sender',
+ ),
+ 'To' => array(
+ 'type' => 'info',
+ 'raw' => 1,
+ 'default' => $wgUser->getSkin()->link(
+ $this->mTargetObj->getUserPage(),
+ htmlspecialchars( $this->mTargetObj->getName() )
+ ),
+ 'label-message' => 'emailto',
+ 'id' => 'mw-emailuser-recipient',
+ ),
+ 'Target' => array(
+ 'type' => 'hidden',
+ 'default' => $this->mTargetObj->getName(),
+ ),
+ 'Subject' => array(
+ 'type' => 'text',
+ 'default' => wfMsgExt( 'defemailsubject', array( 'content', 'parsemag' ) ),
+ 'label-message' => 'emailsubject',
+ 'maxlength' => 200,
+ 'size' => 60,
+ 'required' => 1,
+ ),
+ 'Text' => array(
+ 'type' => 'textarea',
+ 'rows' => 20,
+ 'cols' => 80,
+ 'label-message' => 'emailmessage',
+ 'required' => 1,
+ ),
+ 'CCMe' => array(
+ 'type' => 'check',
+ 'label-message' => 'emailccme',
+ 'default' => $wgUser->getBoolOption( 'ccmeonemails' ),
+ ),
+ );
}
-
- $error = EmailUserForm::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) );
- if ( $error ) {
+
+ public function execute( $par ) {
+ global $wgRequest, $wgOut, $wgUser;
+
+ $this->setHeaders();
+ $this->outputHeader();
+
+ $this->mTarget = is_null( $par )
+ ? $wgRequest->getVal( 'wpTarget', $wgRequest->getVal( 'target', '' ) )
+ : $par;
+
+ $ret = self::getTarget( $this->mTarget );
+ if( $ret instanceof User ){
+ $this->mTargetObj = $ret;
+ } else {
+ $wgOut->showErrorPage( "{$ret}title", "{$ret}text" );
+ return false;
+ }
+
+ $error = self::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) );
switch ( $error ) {
+ case null:
+ # Wahey!
+ break;
+ case 'badaccess':
+ $wgOut->permissionRequired( 'sendemail' );
+ return;
case 'blockedemailuser':
$wgOut->blockedPage();
return;
case 'actionthrottledtext':
$wgOut->rateLimited();
return;
- case 'sessionfailure':
- $form->showForm();
- return;
case 'mailnologin':
- $wgOut->showErrorPage( 'mailnologin', 'mailnologintext' );
+ case 'usermaildisabled':
+ $wgOut->showErrorPage( $error, "{$error}text" );
return;
default:
- // It's a hook error
+ # It's a hook error
list( $title, $msg, $params ) = $error;
$wgOut->showErrorPage( $title, $msg, $params );
return;
-
}
- }
-
- if ( "submit" == $action && $wgRequest->wasPosted() ) {
- $result = $form->doSubmit();
- if ( !is_null( $result ) ) {
- $wgOut->addHTML( wfMsg( "usermailererror" ) .
- ' ' . htmlspecialchars( $result->getMessage() ) );
- } else {
- $titleObj = SpecialPage::getTitleFor( "Emailuser" );
- $encTarget = wfUrlencode( $form->getTarget()->getName() );
- $wgOut->redirect( $titleObj->getFullURL( "target={$encTarget}&action=success" ) );
- }
- } else {
- $form->showForm();
- }
-}
-
-/**
- * Implements the Special:Emailuser web interface, and invokes userMailer for sending the email message.
- * @ingroup SpecialPage
- */
-class EmailUserForm {
-
- var $target;
- var $text, $subject;
- var $cc_me; // Whether user requested to be sent a separate copy of their email.
-
- /**
- * @param User $target
- */
- function EmailUserForm( $target, $text, $subject, $cc_me ) {
- $this->target = $target;
- $this->text = $text;
- $this->subject = $subject;
- $this->cc_me = $cc_me;
- }
-
- function showForm() {
- global $wgOut, $wgUser;
- $skin = $wgUser->getSkin();
-
- $wgOut->setPagetitle( wfMsg( "emailpage" ) );
- $wgOut->addWikiMsg( "emailpagetext" );
-
- if ( $this->subject === "" ) {
- $this->subject = wfMsgExt( 'defemailsubject', array( 'content', 'parsemag' ) );
- }
-
- $titleObj = SpecialPage::getTitleFor( "Emailuser" );
- $action = $titleObj->getLocalURL( "target=" .
- urlencode( $this->target->getName() ) . "&action=submit" );
-
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'emailuser' ) ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsgExt( 'email-legend', 'parsemag' ) ) .
- Xml::openElement( 'table', array( 'class' => 'mw-emailuser-table' ) ) .
- "<tr>
- <td class='mw-label'>" .
- Xml::label( wfMsg( 'emailfrom' ), 'emailfrom' ) .
- "</td>
- <td class='mw-input' id='mw-emailuser-sender'>" .
- $skin->link( $wgUser->getUserPage(), htmlspecialchars( $wgUser->getName() ) ) .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( wfMsg( 'emailto' ), 'emailto' ) .
- "</td>
- <td class='mw-input' id='mw-emailuser-recipient'>" .
- $skin->link( $this->target->getUserPage(), htmlspecialchars( $this->target->getName() ) ) .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( wfMsg( 'emailsubject' ), 'wpSubject' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'wpSubject', 60, $this->subject, array( 'type' => 'text', 'maxlength' => 200 ) ) .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( wfMsg( 'emailmessage' ), 'wpText' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::textarea( 'wpText', $this->text, 80, 20, array( 'id' => 'wpText' ) ) .
- "</td>
- </tr>
- <tr>
- <td></td>
- <td class='mw-input'>" .
- Xml::checkLabel( wfMsg( 'emailccme' ), 'wpCCMe', 'wpCCMe', $wgUser->getBoolOption( 'ccmeonemails' ) ) .
- "</td>
- </tr>
- <tr>
- <td></td>
- <td class='mw-submit'>" .
- Xml::submitButton( wfMsg( 'emailsend' ), array( 'name' => 'wpSend', 'accesskey' => 's' ) ) .
- "</td>
- </tr>" .
- Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
- );
- }
-
- /*
- * Really send a mail. Permissions should have been checked using
- * EmailUserForm::getPermissionsError. It is probably also a good idea to
- * check the edit token and ping limiter in advance.
- */
- function doSubmit() {
- global $wgUser, $wgUserEmailUseReplyTo, $wgSiteName;
-
- $to = new MailAddress( $this->target );
- $from = new MailAddress( $wgUser );
- $subject = $this->subject;
-
- // Add a standard footer and trim up trailing newlines
- $this->text = rtrim($this->text) . "\n\n-- \n" . wfMsgExt( 'emailuserfooter',
- array( 'content', 'parsemag' ), array( $from->name, $to->name ) );
+ $form = new HTMLForm( $this->getFormFields() );
+ $form->addPreText( wfMsgExt( 'emailpagetext', 'parseinline' ) );
+ $form->setSubmitText( wfMsg( 'emailsend' ) );
+ $form->setTitle( $this->getTitle() );
+ $form->setSubmitCallback( array( __CLASS__, 'submit' ) );
+ $form->setWrapperLegend( wfMsgExt( 'email-legend', 'parsemag' ) );
+ $form->loadData();
- if( wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$this->text ) ) ) {
-
- if( $wgUserEmailUseReplyTo ) {
- // Put the generic wiki autogenerated address in the From:
- // header and reserve the user for Reply-To.
- //
- // This is a bit ugly, but will serve to differentiate
- // wiki-borne mails from direct mails and protects against
- // SPF and bounce problems with some mailers (see below).
- global $wgPasswordSender;
- $mailFrom = new MailAddress( $wgPasswordSender );
- $replyTo = $from;
- } else {
- // Put the sending user's e-mail address in the From: header.
- //
- // This is clean-looking and convenient, but has issues.
- // One is that it doesn't as clearly differentiate the wiki mail
- // from "directly" sent mails.
- //
- // Another is that some mailers (like sSMTP) will use the From
- // address as the envelope sender as well. For open sites this
- // can cause mails to be flunked for SPF violations (since the
- // wiki server isn't an authorized sender for various users'
- // domains) as well as creating a privacy issue as bounces
- // containing the recipient's e-mail address may get sent to
- // the sending user.
- $mailFrom = $from;
- $replyTo = null;
- }
-
- $mailResult = UserMailer::send( $to, $mailFrom, $subject, $this->text, $replyTo );
-
- if( WikiError::isError( $mailResult ) ) {
- return $mailResult;
-
- } else {
-
- // if the user requested a copy of this mail, do this now,
- // unless they are emailing themselves, in which case one copy of the message is sufficient.
- if ($this->cc_me && $to != $from) {
- $cc_subject = wfMsg('emailccsubject', $this->target->getName(), $subject);
- if( wfRunHooks( 'EmailUser', array( &$from, &$from, &$cc_subject, &$this->text ) ) ) {
- $ccResult = UserMailer::send( $from, $from, $cc_subject, $this->text );
- if( WikiError::isError( $ccResult ) ) {
- // At this stage, the user's CC mail has failed, but their
- // original mail has succeeded. It's unlikely, but still, what to do?
- // We can either show them an error, or we can say everything was fine,
- // or we can say we sort of failed AND sort of succeeded. Of these options,
- // simply saying there was an error is probably best.
- return $ccResult;
- }
- }
- }
-
- wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $this->text ) );
- return;
- }
+ if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ){
+ return false;
}
- }
-
- function showSuccess( &$user = null ) {
- global $wgOut;
- if ( is_null($user) )
- $user = $this->target;
-
- $wgOut->setPagetitle( wfMsg( "emailsent" ) );
- $wgOut->addWikiMsg( 'emailsenttext' );
-
- $wgOut->returnToMain( false, $user->getUserPage() );
- }
-
- function getTarget() {
- return $this->target;
- }
-
- static function userEmailEnabled() {
- global $wgEnableEmail, $wgEnableUserEmail;
- return $wgEnableEmail && $wgEnableUserEmail;
+ $wgOut->setPagetitle( wfMsg( 'emailpage' ) );
+ $result = $form->show();
+ if( $result === true || ( $result instanceof Status && $result->isGood() ) ){
+ $wgOut->setPagetitle( wfMsg( 'emailsent' ) );
+ $wgOut->addWikiMsg( 'emailsenttext' );
+ $wgOut->returnToMain( false, $this->mTargetObj->getUserPage() );
+ }
}
- static function validateEmailTarget ( $target ) {
- if ( $target == "" ) {
+
+ /**
+ * Validate target User
+ *
+ * @param $target String: target user name
+ * @return User object on success or a string on error
+ */
+ public static function getTarget( $target ) {
+ if ( $target == '' ) {
wfDebug( "Target is empty.\n" );
- return "notarget";
- }
-
- $nt = Title::newFromURL( $target );
- if ( is_null( $nt ) ) {
- wfDebug( "Target is invalid title.\n" );
- return "notarget";
+ return 'notarget';
}
-
- $nu = User::newFromName( $nt->getText() );
+
+ $nu = User::newFromName( $target );
if( !$nu instanceof User || !$nu->getId() ) {
wfDebug( "Target is invalid user.\n" );
- return "notarget";
+ return 'notarget';
} else if ( !$nu->isEmailConfirmed() ) {
wfDebug( "User has no valid email.\n" );
- return "noemail";
+ return 'noemail';
} else if ( !$nu->canReceiveEmail() ) {
wfDebug( "User does not allow user emails.\n" );
- return "nowikiemail";
+ return 'nowikiemail';
}
-
+
return $nu;
}
- static function getPermissionsError ( $user, $editToken ) {
- if( !$user->canSendEmail() ) {
- wfDebug( "User can't send.\n" );
- // FIXME: this is also the error if user is in a group
- // that is not allowed to send e-mail (no right
- // 'sendemail'). Error messages should probably
- // be more fine grained.
- return "mailnologin";
+
+ /**
+ * Check whether a user is allowed to send email
+ *
+ * @param $user User object
+ * @param $editToken String: edit token
+ * @return null on success or string on error
+ */
+ public static function getPermissionsError( $user, $editToken ) {
+ global $wgEnableEmail, $wgEnableUserEmail;
+ if( !$wgEnableEmail || !$wgEnableUserEmail ){
+ return 'usermaildisabled';
+ }
+
+ if( !$user->isAllowed( 'sendemail' ) ) {
+ return 'badaccess';
}
+ if( !$user->isEmailConfirmed() ){
+ return 'mailnologin';
+ }
+
if( $user->isBlockedFromEmailuser() ) {
wfDebug( "User is blocked from sending e-mail.\n" );
return "blockedemailuser";
}
-
+
if( $user->pingLimiter( 'emailuser' ) ) {
- wfDebug( "Ping limiter triggered.\n" );
+ wfDebug( "Ping limiter triggered.\n" );
return 'actionthrottledtext';
}
-
- $hookErr = null;
+
+ $hookErr = false;
+ wfRunHooks( 'UserCanSendEmail', array( &$user, &$hookErr ) );
wfRunHooks( 'EmailUserPermissionsErrors', array( $user, $editToken, &$hookErr ) );
-
- if ($hookErr) {
+ if ( $hookErr ) {
return $hookErr;
}
+
+ return null;
+ }
+
+ /**
+ * Really send a mail. Permissions should have been checked using
+ * getPermissionsError(). It is probably also a good
+ * idea to check the edit token and ping limiter in advance.
+ *
+ * @return Mixed: Status object, or potentially a String on error
+ * or maybe even true on success if anything uses the EmailUser hook.
+ */
+ public static function submit( $data ) {
+ global $wgUser, $wgUserEmailUseReplyTo;
+
+ $target = self::getTarget( $data['Target'] );
+ if( !$target instanceof User ){
+ return wfMsgExt( $target . 'text', 'parse' );
+ }
+ $to = new MailAddress( $target );
+ $from = new MailAddress( $wgUser );
+ $subject = $data['Subject'];
+ $text = $data['Text'];
+
+ // Add a standard footer and trim up trailing newlines
+ $text = rtrim( $text ) . "\n\n-- \n";
+ $text .= wfMsgExt(
+ 'emailuserfooter',
+ array( 'content', 'parsemag' ),
+ array( $from->name, $to->name )
+ );
+
+ $error = '';
+ if( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) {
+ return $error;
+ }
- if( !$user->matchEditToken( $editToken ) ) {
- wfDebug( "Matching edit token failed.\n" );
- return 'sessionfailure';
+ if( $wgUserEmailUseReplyTo ) {
+ // Put the generic wiki autogenerated address in the From:
+ // header and reserve the user for Reply-To.
+ //
+ // This is a bit ugly, but will serve to differentiate
+ // wiki-borne mails from direct mails and protects against
+ // SPF and bounce problems with some mailers (see below).
+ global $wgPasswordSender, $wgPasswordSenderName;
+ $mailFrom = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
+ $replyTo = $from;
+ } else {
+ // Put the sending user's e-mail address in the From: header.
+ //
+ // This is clean-looking and convenient, but has issues.
+ // One is that it doesn't as clearly differentiate the wiki mail
+ // from "directly" sent mails.
+ //
+ // Another is that some mailers (like sSMTP) will use the From
+ // address as the envelope sender as well. For open sites this
+ // can cause mails to be flunked for SPF violations (since the
+ // wiki server isn't an authorized sender for various users'
+ // domains) as well as creating a privacy issue as bounces
+ // containing the recipient's e-mail address may get sent to
+ // the sending user.
+ $mailFrom = $from;
+ $replyTo = null;
+ }
+
+ $status = UserMailer::send( $to, $mailFrom, $subject, $text, $replyTo );
+
+ if( !$status->isGood() ) {
+ return $status;
+ } else {
+ // if the user requested a copy of this mail, do this now,
+ // unless they are emailing themselves, in which case one
+ // copy of the message is sufficient.
+ if ( $data['CCMe'] && $to != $from ) {
+ $cc_subject = wfMsg(
+ 'emailccsubject',
+ $target->getName(),
+ $subject
+ );
+ wfRunHooks( '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 ) );
+ return $status;
}
- }
-
- static function newFromURL( $target, $text, $subject, $cc_me )
- {
- $nt = Title::newFromURL( $target );
- $nu = User::newFromName( $nt->getText() );
- return new EmailUserForm( $nu, $text, $subject, $cc_me );
}
}
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index b9a44d48..eaed2393 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -1,56 +1,64 @@
<?php
-# Copyright (C) 2003-2008 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
/**
+ * Implements Special:Export
+ *
+ * Copyright © 2003-2008 Brion Vibber <brion@pobox.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
+/**
+ * A special page that allows users to export pages in a XML file
+ *
+ * @ingroup SpecialPage
+ */
class SpecialExport extends SpecialPage {
-
+
private $curonly, $doExport, $pageLinkDepth, $templates;
private $images;
-
+
public function __construct() {
parent::__construct( 'Export' );
}
-
+
public function execute( $par ) {
global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors;
global $wgExportAllowHistory, $wgExportMaxHistory, $wgExportMaxLinkDepth;
- global $wgExportFromNamespaces;
-
+ global $wgExportFromNamespaces, $wgUser;
+
$this->setHeaders();
$this->outputHeader();
-
+
// Set some variables
$this->curonly = true;
$this->doExport = false;
$this->templates = $wgRequest->getCheck( 'templates' );
$this->images = $wgRequest->getCheck( 'images' ); // Doesn't do anything yet
$this->pageLinkDepth = $this->validateLinkDepth(
- $wgRequest->getIntOrNull( 'pagelink-depth' ) );
+ $wgRequest->getIntOrNull( 'pagelink-depth' )
+ );
$nsindex = '';
-
+
if ( $wgRequest->getCheck( 'addcat' ) ) {
$page = $wgRequest->getText( 'pages' );
$catname = $wgRequest->getText( 'catname' );
-
+
if ( $catname !== '' && $catname !== null && $catname !== false ) {
$t = Title::makeTitleSafe( NS_MAIN, $catname );
if ( $t ) {
@@ -67,7 +75,7 @@ class SpecialExport extends SpecialPage {
else if( $wgRequest->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
$page = $wgRequest->getText( 'pages' );
$nsindex = $wgRequest->getText( 'nsindex', '' );
-
+
if ( strval( $nsindex ) !== '' ) {
/**
* Same implementation as above, so same @todo
@@ -80,11 +88,13 @@ class SpecialExport extends SpecialPage {
$page = $wgRequest->getText( 'pages' );
$this->curonly = $wgRequest->getCheck( 'curonly' );
$rawOffset = $wgRequest->getVal( 'offset' );
+
if( $rawOffset ) {
$offset = wfTimestamp( TS_MW, $rawOffset );
} else {
$offset = null;
}
+
$limit = $wgRequest->getInt( 'limit' );
$dir = $wgRequest->getVal( 'dir' );
$history = array(
@@ -93,6 +103,7 @@ class SpecialExport extends SpecialPage {
'limit' => $wgExportMaxHistory,
);
$historyCheck = $wgRequest->getCheck( 'history' );
+
if ( $this->curonly ) {
$history = WikiExporter::CURRENT;
} elseif ( !$historyCheck ) {
@@ -106,93 +117,101 @@ class SpecialExport extends SpecialPage {
$history['dir'] = 'desc';
}
}
-
+
if( $page != '' ) $this->doExport = true;
} else {
- // Default to current-only for GET requests
+ // Default to current-only for GET requests.
$page = $wgRequest->getText( 'pages', $par );
$historyCheck = $wgRequest->getCheck( 'history' );
+
if( $historyCheck ) {
$history = WikiExporter::FULL;
} else {
$history = WikiExporter::CURRENT;
}
-
+
if( $page != '' ) $this->doExport = true;
}
-
+
if( !$wgExportAllowHistory ) {
// Override
$history = WikiExporter::CURRENT;
}
-
+
$list_authors = $wgRequest->getCheck( 'listauthors' );
if ( !$this->curonly || !$wgExportAllowListContributors ) $list_authors = false ;
-
+
if ( $this->doExport ) {
$wgOut->disable();
+
// Cancel output buffering and gzipping if set
// This should provide safer streaming for pages with history
wfResetOutputBuffers();
- header( "Content-type: application/xml; charset=utf-8" );
+ $wgRequest->response()->header( "Content-type: application/xml; charset=utf-8" );
+
if( $wgRequest->getCheck( 'wpDownload' ) ) {
// Provide a sane filename suggestion
$filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' );
$wgRequest->response()->header( "Content-disposition: attachment;filename={$filename}" );
}
+
$this->doExport( $page, $history, $list_authors );
+
return;
}
-
+
$wgOut->addWikiMsg( 'exporttext' );
-
+
$form = Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->getTitle()->getLocalUrl( 'action=submit' ) ) );
- $form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . '&nbsp;';
+ $form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . '&#160;';
$form .= Xml::submitButton( wfMsg( 'export-addcat' ), array( 'name' => 'addcat' ) ) . '<br />';
-
+
if ( $wgExportFromNamespaces ) {
- $form .= Xml::namespaceSelector( $nsindex, null, 'nsindex', wfMsg( 'export-addnstext' ) ) . '&nbsp;';
+ $form .= Xml::namespaceSelector( $nsindex, null, 'nsindex', wfMsg( 'export-addnstext' ) ) . '&#160;';
$form .= Xml::submitButton( wfMsg( 'export-addns' ), array( 'name' => 'addns' ) ) . '<br />';
}
-
+
$form .= Xml::element( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ), $page, false );
$form .= '<br />';
-
+
if( $wgExportAllowHistory ) {
$form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '<br />';
} else {
$wgOut->addHTML( wfMsgExt( 'exportnohistory', 'parse' ) );
}
+
$form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '<br />';
+
if( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) {
$form .= Xml::inputLabel( wfMsg( 'export-pagelinks' ), 'pagelink-depth', 'pagelink-depth', 20, 0 ) . '<br />';
}
// Enable this when we can do something useful exporting/importing image information. :)
//$form .= Xml::checkLabel( wfMsg( 'export-images' ), 'images', 'wpExportImages', false ) . '<br />';
$form .= Xml::checkLabel( wfMsg( 'export-download' ), 'wpDownload', 'wpDownload', true ) . '<br />';
-
- $form .= Xml::submitButton( wfMsg( 'export-submit' ), array( 'accesskey' => 's' ) );
+
+ $form .= Xml::submitButton( wfMsg( 'export-submit' ), $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'export' ) );
$form .= Xml::closeElement( 'form' );
+
$wgOut->addHTML( $form );
}
-
- private function userCanOverrideExportDepth() {
- global $wgUser;
+ private function userCanOverrideExportDepth() {
+ global $wgUser;
return $wgUser->isAllowed( 'override-export-depth' );
}
-
+
/**
* Do the actual page exporting
- * @param string $page User input on what page(s) to export
- * @param mixed $history one of the WikiExporter history export constants
+ *
+ * @param $page String: user input on what page(s) to export
+ * @param $history Mixed: one of the WikiExporter history export constants
+ * @param $list_authors Boolean: Whether to add distinct author list (when
+ * not returning full history)
*/
private function doExport( $page, $history, $list_authors ) {
- global $wgExportMaxHistory;
-
$pageSet = array(); // Inverted index of all pages to look up
-
+
// Split up and normalize input
foreach( explode( "\n", $page ) as $pageName ) {
$pageName = trim( $pageName );
@@ -202,32 +221,33 @@ class SpecialExport extends SpecialPage {
$pageSet[$title->getPrefixedText()] = true;
}
}
-
+
// Set of original pages to pass on to further manipulation...
$inputPages = array_keys( $pageSet );
-
+
// Look up any linked pages if asked...
if( $this->templates ) {
$pageSet = $this->getTemplates( $inputPages, $pageSet );
}
-
- if( $linkDepth = $this->pageLinkDepth ) {
+ $linkDepth = $this->pageLinkDepth;
+ if( $linkDepth ) {
$pageSet = $this->getPageLinks( $inputPages, $pageSet, $linkDepth );
}
-
+
/*
// Enable this when we can do something useful exporting/importing image information. :)
if( $this->images ) ) {
$pageSet = $this->getImages( $inputPages, $pageSet );
}
*/
-
+
$pages = array_keys( $pageSet );
// Normalize titles to the same format and remove dupes, see bug 17374
foreach( $pages as $k => $v ) {
$pages[$k] = str_replace( " ", "_", $v );
}
+
$pages = array_unique( $pages );
/* Ok, let's get to it... */
@@ -240,15 +260,17 @@ class SpecialExport extends SpecialPage {
$lb = wfGetLBFactory()->newMainLB();
$db = $lb->getConnection( DB_SLAVE );
$buffer = WikiExporter::STREAM;
-
+
// This might take a while... :D
wfSuppressWarnings();
set_time_limit(0);
wfRestoreWarnings();
}
+
$exporter = new WikiExporter( $db, $history, $buffer );
$exporter->list_authors = $list_authors;
$exporter->openStream();
+
foreach( $pages as $page ) {
/*
if( $wgExportMaxHistory && !$this->curonly ) {
@@ -266,11 +288,12 @@ class SpecialExport extends SpecialPage {
$title = Title::newFromText( $page );
if( is_null( $title ) ) continue; #TODO: perhaps output an <error> tag or something.
if( !$title->userCanRead() ) continue; #TODO: perhaps output an <error> tag or something.
-
+
$exporter->pageByTitle( $title );
}
-
+
$exporter->closeStream();
+
if( $lb ) {
$lb->closeAll();
}
@@ -278,52 +301,59 @@ class SpecialExport extends SpecialPage {
private function getPagesFromCategory( $title ) {
global $wgContLang;
-
+
$name = $title->getDBkey();
-
+
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( array('page', 'categorylinks' ),
- array( 'page_namespace', 'page_title' ),
- array('cl_from=page_id', 'cl_to' => $name ),
- __METHOD__, array('LIMIT' => '5000'));
-
+ $res = $dbr->select(
+ array( 'page', 'categorylinks' ),
+ array( 'page_namespace', 'page_title' ),
+ array( 'cl_from=page_id', 'cl_to' => $name ),
+ __METHOD__,
+ array( 'LIMIT' => '5000' )
+ );
+
$pages = array();
- while ( $row = $dbr->fetchObject( $res ) ) {
+
+ foreach ( $res as $row ) {
$n = $row->page_title;
if ($row->page_namespace) {
$ns = $wgContLang->getNsText( $row->page_namespace );
$n = $ns . ':' . $n;
}
-
+
$pages[] = $n;
}
- $dbr->freeResult($res);
-
return $pages;
}
-
+
private function getPagesFromNamespace( $nsindex ) {
global $wgContLang;
-
+
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'page', array('page_namespace', 'page_title'),
- array('page_namespace' => $nsindex),
- __METHOD__, array('LIMIT' => '5000') );
-
+ $res = $dbr->select(
+ 'page',
+ array( 'page_namespace', 'page_title' ),
+ array( 'page_namespace' => $nsindex ),
+ __METHOD__,
+ array( 'LIMIT' => '5000' )
+ );
+
$pages = array();
- while ( $row = $dbr->fetchObject( $res ) ) {
+
+ foreach ( $res as $row ) {
$n = $row->page_title;
- if ($row->page_namespace) {
+
+ if ( $row->page_namespace ) {
$ns = $wgContLang->getNsText( $row->page_namespace );
$n = $ns . ':' . $n;
}
-
+
$pages[] = $n;
}
- $dbr->freeResult($res);
-
return $pages;
}
+
/**
* Expand a list of pages to include templates used in those pages.
* @param $inputPages array, list of titles to look up
@@ -332,24 +362,28 @@ class SpecialExport extends SpecialPage {
*/
private function getTemplates( $inputPages, $pageSet ) {
return $this->getLinks( $inputPages, $pageSet,
- 'templatelinks',
- array( 'tl_namespace AS namespace', 'tl_title AS title' ),
- array( 'page_id=tl_from' ) );
+ 'templatelinks',
+ array( 'tl_namespace AS namespace', 'tl_title AS title' ),
+ array( 'page_id=tl_from' )
+ );
}
-
+
/**
* Validate link depth setting, if available.
*/
private function validateLinkDepth( $depth ) {
- global $wgExportMaxLinkDepth, $wgExportMaxLinkDepthLimit;
+ global $wgExportMaxLinkDepth;
+
if( $depth < 0 ) {
return 0;
}
+
if ( !$this->userCanOverrideExportDepth() ) {
if( $depth > $wgExportMaxLinkDepth ) {
return $wgExportMaxLinkDepth;
}
}
+
/*
* There's a HARD CODED limit of 5 levels of recursion here to prevent a
* crazy-big export from being done by someone setting the depth
@@ -357,58 +391,73 @@ class SpecialExport extends SpecialPage {
*/
return intval( min( $depth, 5 ) );
}
-
+
/** Expand a list of pages to include pages linked to from that page. */
private function getPageLinks( $inputPages, $pageSet, $depth ) {
- for( $depth=$depth; $depth>0; --$depth ) {
- $pageSet = $this->getLinks( $inputPages, $pageSet, 'pagelinks',
- array( 'pl_namespace AS namespace', 'pl_title AS title' ),
- array( 'page_id=pl_from' ) );
+ for(; $depth > 0; --$depth ) {
+ $pageSet = $this->getLinks(
+ $inputPages, $pageSet, 'pagelinks',
+ array( 'pl_namespace AS namespace', 'pl_title AS title' ),
+ array( 'page_id=pl_from' )
+ );
$inputPages = array_keys( $pageSet );
}
+
return $pageSet;
}
-
+
/**
* Expand a list of pages to include images used in those pages.
+ *
* @param $inputPages array, list of titles to look up
* @param $pageSet array, associative array indexed by titles for output
+ *
* @return array associative array index by titles
*/
private function getImages( $inputPages, $pageSet ) {
- return $this->getLinks( $inputPages, $pageSet,
- 'imagelinks',
- array( NS_FILE . ' AS namespace', 'il_to AS title' ),
- array( 'page_id=il_from' ) );
+ return $this->getLinks(
+ $inputPages,
+ $pageSet,
+ 'imagelinks',
+ array( NS_FILE . ' AS namespace', 'il_to AS title' ),
+ array( 'page_id=il_from' )
+ );
}
-
+
/**
* Expand a list of pages to include items used in those pages.
- * @private
*/
private function getLinks( $inputPages, $pageSet, $table, $fields, $join ) {
$dbr = wfGetDB( DB_SLAVE );
+
foreach( $inputPages as $page ) {
$title = Title::newFromText( $page );
+
if( $title ) {
$pageSet[$title->getPrefixedText()] = true;
/// @todo Fixme: May or may not be more efficient to batch these
/// by namespace when given multiple input pages.
$result = $dbr->select(
- array( 'page', $table ),
- $fields,
- array_merge( $join,
- array(
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() ) ),
- __METHOD__ );
+ array( 'page', $table ),
+ $fields,
+ array_merge(
+ $join,
+ array(
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey()
+ )
+ ),
+ __METHOD__
+ );
+
foreach( $result as $row ) {
$template = Title::makeTitle( $row->namespace, $row->title );
$pageSet[$template->getPrefixedText()] = true;
}
}
}
+
return $pageSet;
}
-}
-
+
+} \ No newline at end of file
diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php
index 65d76a65..c265ed38 100644
--- a/includes/specials/SpecialFewestrevisions.php
+++ b/includes/specials/SpecialFewestrevisions.php
@@ -1,5 +1,22 @@
<?php
/**
+ * Implements Special:Fewestrevisions
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index 0ed7020a..172e92ad 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -1,24 +1,37 @@
<?php
/**
- * A special page to search for files by hash value as defined in the
- * img_sha1 field in the image table
+ * Implements Special:FileDuplicateSearch
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup SpecialPage
- *
* @author Raimond Spekking, based on Special:MIMESearch by Ævar Arnfjörð Bjarmason
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
/**
* Searches the database for files of the requested hash, comparing this with the
* 'img_sha1' field in the image table.
+ *
* @ingroup SpecialPage
*/
class FileDuplicateSearchPage extends QueryPage {
var $hash, $filename;
- function FileDuplicateSearchPage( $hash, $filename ) {
+ function __construct( $hash, $filename ) {
$this->hash = $hash;
$this->filename = $filename;
}
@@ -72,7 +85,7 @@ function wfSpecialFileDuplicateSearch( $par = null ) {
$hash = '';
$filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' );
- $title = Title::newFromText( $filename );
+ $title = Title::makeTitleSafe( NS_FILE, $filename );
if( $title && $title->getText() != '' ) {
$dbr = wfGetDB( DB_SLAVE );
$image = $dbr->tableName( 'image' );
@@ -83,13 +96,12 @@ function wfSpecialFileDuplicateSearch( $par = null ) {
if( $row !== false ) {
$hash = $row[0];
}
- $dbr->freeResult( $res );
}
# Create the input form
$wgOut->addHTML(
Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) .
- Xml::hidden( 'title', SpecialPage::getTitleFor( 'FileDuplicateSearch' )->getPrefixedDbKey() ) .
+ Html::hidden( 'title', SpecialPage::getTitleFor( 'FileDuplicateSearch' )->getPrefixedDbKey() ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) .
Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $filename ) . ' ' .
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index 8bc1c68b..8bb0890c 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -1,53 +1,81 @@
<?php
/**
+ * Implements Special:Filepath
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
-function wfSpecialFilepath( $par ) {
- global $wgRequest, $wgOut;
+/**
+ * A special page that redirects to the URL of a given file
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialFilepath extends SpecialPage {
- $file = isset( $par ) ? $par : $wgRequest->getText( 'file' );
+ function __construct() {
+ parent::__construct( 'Filepath' );
+ }
- $title = Title::makeTitleSafe( NS_FILE, $file );
+ function execute( $par ) {
+ global $wgRequest, $wgOut;
- if ( ! $title instanceof Title || $title->getNamespace() != NS_FILE ) {
- $cform = new FilepathForm( $title );
- $cform->execute();
- } else {
- $file = wfFindFile( $title );
- if ( $file && $file->exists() ) {
- $wgOut->redirect( $file->getURL() );
- } else {
- $wgOut->setStatusCode( 404 );
- $cform = new FilepathForm( $title );
- $cform->execute();
- }
- }
-}
+ $this->setHeaders();
+ $this->outputHeader();
-/**
- * @ingroup SpecialPage
- */
-class FilepathForm {
- var $mTitle;
+ $file = !is_null( $par ) ? $par : $wgRequest->getText( 'file' );
- function FilepathForm( &$title ) {
- $this->mTitle =& $title;
+ $title = Title::makeTitleSafe( NS_FILE, $file );
+
+ if ( ! $title instanceof Title || $title->getNamespace() != NS_FILE ) {
+ $this->showForm( $title );
+ } else {
+ $file = wfFindFile( $title );
+ if ( $file && $file->exists() ) {
+ $url = $file->getURL();
+ $width = $wgRequest->getInt( 'width', -1 );
+ $height = $wgRequest->getInt( 'height', -1 );
+ if ( $width != -1 ) {
+ $mto = $file->transform( array( 'width' => $width, 'height' => $height ) );
+ if ( $mto && !$mto->isError() ) {
+ $url = $mto->getURL();
+ }
+ }
+ $wgOut->redirect( $url );
+ } else {
+ $wgOut->setStatusCode( 404 );
+ $this->showForm( $title );
+ }
+ }
}
- function execute() {
+ function showForm( $title ) {
global $wgOut, $wgScript;
$wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'specialfilepath' ) ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'filepath' ) ) .
- Xml::hidden( 'title', SpecialPage::getTitleFor( 'Filepath' )->getPrefixedText() ) .
- Xml::inputLabel( wfMsg( 'filepath-page' ), 'file', 'file', 25, is_object( $this->mTitle ) ? $this->mTitle->getText() : '' ) . ' ' .
+ Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'specialfilepath' ) ) .
+ Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', null, wfMsg( 'filepath' ) ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Xml::inputLabel( wfMsg( 'filepath-page' ), 'file', 'file', 25, is_object( $title ) ? $title->getText() : '' ) . ' ' .
Xml::submitButton( wfMsg( 'filepath-submit' ) ) . "\n" .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
+ Html::closeElement( 'fieldset' ) .
+ Html::closeElement( 'form' )
);
}
}
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index 248709a8..7d1cf0dd 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -1,7 +1,8 @@
<?php
/**
- * MediaWiki page data importer
- * Copyright (C) 2003,2005 Brion Vibber <brion@pobox.com>
+ * Implements Special:Import
+ *
+ * Copyright © 2003,2005 Brion Vibber <brion@pobox.com>
* http://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +24,11 @@
* @ingroup SpecialPage
*/
+/**
+ * MediaWiki page data importer
+ *
+ * @ingroup SpecialPage
+ */
class SpecialImport extends SpecialPage {
private $interwiki = false;
@@ -51,7 +57,6 @@ class SpecialImport extends SpecialPage {
$this->outputHeader();
if ( wfReadOnly() ) {
- global $wgOut;
$wgOut->readOnlyPage();
return;
}
@@ -97,7 +102,7 @@ class SpecialImport extends SpecialPage {
$this->pageLinkDepth = $wgExportMaxLinkDepth == 0 ? 0 : $wgRequest->getIntOrNull( 'pagelink-depth' );
if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'editToken' ) ) ) {
- $source = new WikiErrorMsg( 'import-token-mismatch' );
+ $source = Status::newFatal( 'import-token-mismatch' );
} elseif ( $sourceName == 'upload' ) {
$isUpload = true;
if( $wgUser->isAllowed( 'importupload' ) ) {
@@ -111,7 +116,7 @@ class SpecialImport extends SpecialPage {
}
$this->interwiki = $wgRequest->getVal( 'interwiki' );
if ( !in_array( $this->interwiki, $wgImportSources ) ) {
- $source = new WikiErrorMsg( "import-invalid-interwiki" );
+ $source = Status::newFatal( "import-invalid-interwiki" );
} else {
$this->history = $wgRequest->getCheck( 'interwikiHistory' );
$this->frompage = $wgRequest->getText( "frompage" );
@@ -124,30 +129,35 @@ class SpecialImport extends SpecialPage {
$this->pageLinkDepth );
}
} else {
- $source = new WikiErrorMsg( "importunknownsource" );
+ $source = Status::newFatal( "importunknownsource" );
}
- if( WikiError::isError( $source ) ) {
- $wgOut->wrapWikiMsg( '<p class="error">$1</p>', array( 'importfailed', $source->getMessage() ) );
+ if( !$source->isGood() ) {
+ $wgOut->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $source->getWikiText() ) );
} else {
$wgOut->addWikiMsg( "importstart" );
- $importer = new WikiImporter( $source );
+ $importer = new WikiImporter( $source->value );
if( !is_null( $this->namespace ) ) {
$importer->setTargetNamespace( $this->namespace );
}
$reporter = new ImportReporter( $importer, $isUpload, $this->interwiki , $this->logcomment);
+ $exception = false;
$reporter->open();
- $result = $importer->doImport();
- $resultCount = $reporter->close();
+ try {
+ $importer->doImport();
+ } catch ( MWException $e ) {
+ $exception = $e;
+ }
+ $result = $reporter->close();
- if( WikiError::isError( $result ) ) {
+ if ( $exception ) {
# No source or XML parse error
- $wgOut->wrapWikiMsg( '<p class="error">$1</p>', array( 'importfailed', $result->getMessage() ) );
- } elseif( WikiError::isError( $resultCount ) ) {
+ $wgOut->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $exception->getMessage() ) );
+ } elseif( !$result->isGood() ) {
# Zero revisions
- $wgOut->wrapWikiMsg( '<p class="error">$1</p>', array( 'importfailed', $resultCount->getMessage() ) );
+ $wgOut->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $result->getWikiText() ) );
} else {
# Success!
$wgOut->addWikiMsg( 'importsuccess' );
@@ -157,7 +167,7 @@ class SpecialImport extends SpecialPage {
}
private function showForm() {
- global $wgUser, $wgOut, $wgRequest, $wgImportSources, $wgExportMaxLinkDepth;
+ global $wgUser, $wgOut, $wgImportSources, $wgExportMaxLinkDepth;
$action = $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) );
@@ -167,8 +177,8 @@ class SpecialImport extends SpecialPage {
Xml::fieldset( wfMsg( 'import-upload' ) ).
Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post',
'action' => $action, 'id' => 'mw-import-upload-form' ) ) .
- Xml::hidden( 'action', 'submit' ) .
- Xml::hidden( 'source', 'upload' ) .
+ Html::hidden( 'action', 'submit' ) .
+ Html::hidden( 'source', 'upload' ) .
Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) .
"<tr>
@@ -195,7 +205,7 @@ class SpecialImport extends SpecialPage {
"</td>
</tr>" .
Xml::closeElement( 'table' ).
- Xml::hidden( 'editToken', $wgUser->editToken() ) .
+ Html::hidden( 'editToken', $wgUser->editToken() ) .
Xml::closeElement( 'form' ) .
Xml::closeElement( 'fieldset' )
);
@@ -223,9 +233,9 @@ class SpecialImport extends SpecialPage {
Xml::fieldset( wfMsg( 'importinterwiki' ) ) .
Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'mw-import-interwiki-form' ) ) .
wfMsgExt( 'import-interwiki-text', array( 'parse' ) ) .
- Xml::hidden( 'action', 'submit' ) .
- Xml::hidden( 'source', 'interwiki' ) .
- Xml::hidden( 'editToken', $wgUser->editToken() ) .
+ Html::hidden( 'action', 'submit' ) .
+ Html::hidden( 'source', 'interwiki' ) .
+ Html::hidden( 'editToken', $wgUser->editToken() ) .
Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) .
"<tr>
<td class='mw-label'>" .
@@ -280,7 +290,7 @@ class SpecialImport extends SpecialPage {
<td>
</td>
<td class='mw-submit'>" .
- Xml::submitButton( wfMsg( 'import-interwiki-submit' ), array( 'accesskey' => 's' ) ) .
+ Xml::submitButton( wfMsg( 'import-interwiki-submit' ), $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'import' ) ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ).
@@ -297,9 +307,15 @@ class SpecialImport extends SpecialPage {
*/
class ImportReporter {
private $reason=false;
+ private $mOriginalLogCallback = null;
+ private $mOriginalPageOutCallback = null;
+ private $mLogItemCount = 0;
function __construct( $importer, $upload, $interwiki , $reason=false ) {
- $importer->setPageOutCallback( array( $this, 'reportPage' ) );
+ $this->mOriginalPageOutCallback =
+ $importer->setPageOutCallback( array( $this, 'reportPage' ) );
+ $this->mOriginalLogCallback =
+ $importer->setLogItemCallback( array( $this, 'reportLogItem' ) );
$this->mPageCount = 0;
$this->mIsUpload = $upload;
$this->mInterwiki = $interwiki;
@@ -310,9 +326,19 @@ class ImportReporter {
global $wgOut;
$wgOut->addHTML( "<ul>\n" );
}
+
+ function reportLogItem( /* ... */ ) {
+ $this->mLogItemCount++;
+ if ( is_callable( $this->mOriginalLogCallback ) ) {
+ call_user_func_array( $this->mOriginalLogCallback, func_get_args() );
+ }
+ }
- function reportPage( $title, $origTitle, $revisionCount, $successCount ) {
+ function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
global $wgOut, $wgUser, $wgLang, $wgContLang;
+
+ $args = func_get_args();
+ call_user_func_array( $this->mOriginalPageOutCallback, $args );
$skin = $wgUser->getSkin();
@@ -362,13 +388,18 @@ class ImportReporter {
}
function close() {
- global $wgOut;
- if( $this->mPageCount == 0 ) {
+ global $wgOut, $wgLang;
+
+ if ( $this->mLogItemCount > 0 ) {
+ $msg = wfMsgExt( 'imported-log-entries', 'parseinline',
+ $wgLang->formatNum( $this->mLogItemCount ) );
+ $wgOut->addHTML( Xml::tags( 'li', null, $msg ) );
+ } elseif( $this->mPageCount == 0 && $this->mLogItemCount == 0 ) {
$wgOut->addHTML( "</ul>\n" );
- return new WikiErrorMsg( "importnopages" );
+ return Status::newFatal( 'importnopages' );
}
$wgOut->addHTML( "</ul>\n" );
- return $this->mPageCount;
+ return Status::newGood( $this->mPageCount );
}
}
diff --git a/includes/specials/SpecialIpblocklist.php b/includes/specials/SpecialIpblocklist.php
index dfdcf1a7..24d7f008 100644
--- a/includes/specials/SpecialIpblocklist.php
+++ b/includes/specials/SpecialIpblocklist.php
@@ -1,103 +1,134 @@
<?php
/**
+ * Implements Special:ipblocklist
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * @param $ip part of title: Special:Ipblocklist/<ip>.
- * @todo document
+ * A special page that lists existing blocks and allows users with the 'block'
+ * permission to remove blocks
+ *
+ * @ingroup SpecialPage
*/
-function wfSpecialIpblocklist( $ip = '' ) {
- global $wgUser, $wgOut, $wgRequest;
- $ip = $wgRequest->getVal( 'ip', $ip );
- $ip = trim( $wgRequest->getVal( 'wpUnblockAddress', $ip ) );
- $id = $wgRequest->getVal( 'id' );
- $reason = $wgRequest->getText( 'wpUnblockReason' );
- $action = $wgRequest->getText( 'action' );
- $successip = $wgRequest->getVal( 'successip' );
-
- $ipu = new IPUnblockForm( $ip, $id, $reason );
-
- if( $action == 'unblock' ) {
- # Check permissions
- if( !$wgUser->isAllowed( 'block' ) ) {
- $wgOut->permissionRequired( 'block' );
- return;
- }
- # Check for database lock
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
- # Show unblock form
- $ipu->showForm( '' );
- } elseif( $action == 'submit' && $wgRequest->wasPosted()
- && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
- # Check permissions
- if( !$wgUser->isAllowed( 'block' ) ) {
- $wgOut->permissionRequired( 'block' );
- return;
- }
- # Check for database lock
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
- # Remove blocks and redirect user to success page
- $ipu->doSubmit();
- } elseif( $action == 'success' ) {
- # Inform the user of a successful unblock
- # (No need to check permissions or locks here,
- # if something was done, then it's too late!)
- if ( substr( $successip, 0, 1) == '#' ) {
- // A block ID was unblocked
- $ipu->showList( $wgOut->parse( wfMsg( 'unblocked-id', $successip ) ) );
- } else {
- // A username/IP was unblocked
- $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) );
- }
- } else {
- # Just show the block list
- $ipu->showList( '' );
+class IPUnblockForm extends SpecialPage {
+ var $ip, $reason, $id;
+ var $hideuserblocks, $hidetempblocks, $hideaddressblocks;
+
+ function __construct() {
+ parent::__construct( 'Ipblocklist' );
}
-}
+ /**
+ * Main execution point
+ *
+ * @param $ip part of title: Special:Ipblocklist/<ip>.
+ */
+ function execute( $ip ) {
+ global $wgUser, $wgOut, $wgRequest;
-/**
- * implements Special:ipblocklist GUI
- * @ingroup SpecialPage
- */
-class IPUnblockForm {
- var $ip, $reason, $id;
+ $this->setHeaders();
+ $this->outputHeader();
- function IPUnblockForm( $ip, $id, $reason ) {
- global $wgRequest;
- $this->ip = strtr( $ip, '_', ' ' );
- $this->id = $id;
- $this->reason = $reason;
+ $ip = $wgRequest->getVal( 'ip', $ip );
+ $this->ip = trim( $wgRequest->getVal( 'wpUnblockAddress', $ip ) );
+ $this->id = $wgRequest->getVal( 'id' );
+ $this->reason = $wgRequest->getText( 'wpUnblockReason' );
$this->hideuserblocks = $wgRequest->getBool( 'hideuserblocks' );
$this->hidetempblocks = $wgRequest->getBool( 'hidetempblocks' );
$this->hideaddressblocks = $wgRequest->getBool( 'hideaddressblocks' );
+
+ $action = $wgRequest->getText( 'action' );
+ $successip = $wgRequest->getVal( 'successip' );
+
+ if( $action == 'unblock' || $action == 'submit' && $wgRequest->wasPosted() ) {
+ # Check permissions
+ if( !$wgUser->isAllowed( 'block' ) ) {
+ $wgOut->permissionRequired( 'block' );
+ return;
+ }
+ # Check for database lock
+ if( wfReadOnly() ) {
+ $wgOut->readOnlyPage();
+ return;
+ }
+
+ # bug 15810: blocked admins should have limited access here
+ if ( $wgUser->isBlocked() ) {
+ if ( $this->id ) {
+ # This doesn't pick up on autoblocks, but admins
+ # should have the ipblock-exempt permission anyway
+ $block = Block::newFromID( $this->id );
+ $user = User::newFromName( $block->mAddress );
+ } else {
+ $user = User::newFromName( $this->ip );
+ }
+ $status = IPBlockForm::checkUnblockSelf( $user );
+ if ( $status !== true ) {
+ throw new ErrorPageError( 'badaccess', $status );
+ }
+ }
+
+ if( $action == 'unblock' ){
+ # Show unblock form
+ $this->showForm( '' );
+ } elseif( $action == 'submit'
+ && $wgRequest->wasPosted()
+ && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) )
+ {
+ # Remove blocks and redirect user to success page
+ $this->doSubmit();
+ }
+
+ } elseif( $action == 'success' ) {
+ # Inform the user of a successful unblock
+ # (No need to check permissions or locks here,
+ # if something was done, then it's too late!)
+ if ( substr( $successip, 0, 1) == '#' ) {
+ // A block ID was unblocked
+ $this->showList( $wgOut->parse( wfMsg( 'unblocked-id', $successip ) ) );
+ } else {
+ // A username/IP was unblocked
+ $this->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) );
+ }
+ } else {
+ # Just show the block list
+ $this->showList( '' );
+ }
}
/**
* Generates the unblock form
+ *
* @param $err string: error message
* @return $out string: HTML form
*/
function showForm( $err ) {
global $wgOut, $wgUser, $wgSysopUserBans;
- $wgOut->setPagetitle( wfMsg( 'unblockip' ) );
$wgOut->addWikiMsg( 'unblockiptext' );
- $titleObj = SpecialPage::getTitleFor( "Ipblocklist" );
- $action = $titleObj->getLocalURL( "action=submit" );
+ $action = $this->getTitle()->getLocalURL( 'action=submit' );
- if ( $err != "" ) {
- $wgOut->setSubtitle( wfMsg( "formerror" ) );
- $wgOut->addWikiText( Xml::tags( 'span', array( 'class' => 'error' ), $err ) . "\n" );
+ if ( $err != '' ) {
+ $wgOut->setSubtitle( wfMsg( 'formerror' ) );
+ $wgOut->addWikiText( Html::rawElement( 'span', array( 'class' => 'error' ), $err ) . "\n" );
}
$addressPart = false;
@@ -106,7 +137,7 @@ class IPUnblockForm {
if ( $block ) {
$encName = htmlspecialchars( $block->getRedactedName() );
$encId = $this->id;
- $addressPart = $encName . Xml::hidden( 'id', $encId );
+ $addressPart = $encName . Html::hidden( 'id', $encId );
$ipa = wfMsgHtml( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' );
}
}
@@ -116,10 +147,10 @@ class IPUnblockForm {
}
$wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'unblockip' ) ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'ipb-unblock' ) ) .
- Xml::openElement( 'table', array( 'id' => 'mw-unblock-table' ) ).
+ Html::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'unblockip' ) ) .
+ Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', null, wfMsg( 'ipb-unblock' ) ) .
+ Html::openElement( 'table', array( 'id' => 'mw-unblock-table' ) ).
"<tr>
<td class='mw-label'>
{$ipa}
@@ -137,15 +168,15 @@ class IPUnblockForm {
"</td>
</tr>
<tr>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-submit'>" .
Xml::submitButton( wfMsg( 'ipusubmit' ), array( 'name' => 'wpBlock', 'tabindex' => '3' ) ) .
"</td>
</tr>" .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
- Xml::closeElement( 'form' ) . "\n"
+ Html::closeElement( 'table' ) .
+ Html::closeElement( 'fieldset' ) .
+ Html::hidden( 'wpEditToken', $wgUser->editToken() ) .
+ Html::closeElement( 'form' ) . "\n"
);
}
@@ -162,34 +193,35 @@ class IPUnblockForm {
* case it contains the range $ip is part of.
* @return array array(message key, parameters) on failure, empty array on success
*/
-
- static function doUnblock(&$id, &$ip, &$reason, &$range = null, $blocker=null) {
+ public static function doUnblock( &$id, &$ip, &$reason, &$range = null, $blocker = null ) {
if ( $id ) {
$block = Block::newFromID( $id );
if ( !$block ) {
- return array('ipb_cant_unblock', htmlspecialchars($id));
+ return array( 'ipb_cant_unblock', htmlspecialchars( $id ) );
}
$ip = $block->getRedactedName();
} else {
- $block = new Block();
$ip = trim( $ip );
if ( substr( $ip, 0, 1 ) == "#" ) {
$id = substr( $ip, 1 );
$block = Block::newFromID( $id );
if( !$block ) {
- return array('ipb_cant_unblock', htmlspecialchars($id));
+ return array( 'ipb_cant_unblock', htmlspecialchars( $id ) );
}
$ip = $block->getRedactedName();
} else {
+ # FIXME: do proper sanitisation/cleanup here
+ $ip = str_replace( '_', ' ', $ip );
+
$block = Block::newFromDB( $ip );
if ( !$block ) {
- return array('ipb_cant_unblock', htmlspecialchars($id));
+ return array( 'ipb_cant_unblock', htmlspecialchars( $id ) );
}
if( $block->mRangeStart != $block->mRangeEnd && !strstr( $ip, "/" ) ) {
/* If the specified IP is a single address, and the block is
* a range block, don't unblock the range. */
$range = $block->mAddress;
- return array('ipb_blocked_as_range', $ip, $range);
+ return array( 'ipb_blocked_as_range', $ip, $range );
}
}
}
@@ -198,13 +230,13 @@ class IPUnblockForm {
# If the name was hidden and the blocking user cannot hide
# names, then don't allow any block removals...
- if( $blocker && $block->mHideName && !$blocker->isAllowed('hideuser') ) {
- return array('ipb_cant_unblock', htmlspecialchars($id));
+ if( $blocker && $block->mHideName && !$blocker->isAllowed( 'hideuser' ) ) {
+ return array( 'ipb_cant_unblock', htmlspecialchars( $id ) );
}
# Delete block
if ( !$block->delete() ) {
- return array('ipb_cant_unblock', htmlspecialchars($id));
+ return array( 'ipb_cant_unblock', htmlspecialchars( $id ) );
}
# Unset _deleted fields as needed
@@ -220,23 +252,23 @@ class IPUnblockForm {
function doSubmit() {
global $wgOut, $wgUser;
- $retval = self::doUnblock($this->id, $this->ip, $this->reason, $range, $wgUser);
- if( !empty($retval) ) {
- $key = array_shift($retval);
- $this->showForm(wfMsgReal($key, $retval));
+
+ $retval = self::doUnblock( $this->id, $this->ip, $this->reason, $range, $wgUser );
+ if( !empty( $retval ) ) {
+ $key = array_shift( $retval );
+ $this->showForm( wfMsgReal( $key, $retval ) );
return;
}
+
# Report to the user
- $titleObj = SpecialPage::getTitleFor( "Ipblocklist" );
- $success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) );
+ $success = $this->getTitle()->getFullURL( 'action=success&successip=' . urlencode( $this->ip ) );
$wgOut->redirect( $success );
}
function showList( $msg ) {
global $wgOut, $wgUser;
- $wgOut->setPagetitle( wfMsg( "ipblocklist" ) );
- if ( $msg != "" ) {
+ if ( $msg != '' ) {
$wgOut->setSubtitle( $msg );
}
@@ -246,7 +278,6 @@ class IPUnblockForm {
}
$conds = array();
- $matches = array();
// Is user allowed to see all the blocks?
if ( !$wgUser->isAllowed( 'hideuser' ) )
$conds['ipb_deleted'] = 0;
@@ -255,25 +286,26 @@ class IPUnblockForm {
} elseif ( substr( $this->ip, 0, 1 ) == '#' ) {
$conds['ipb_id'] = substr( $this->ip, 1 );
// Single IPs
- } elseif ( IP::isIPAddress($this->ip) && strpos($this->ip,'/') === false ) {
- if( $iaddr = IP::toHex($this->ip) ) {
+ } elseif ( IP::isIPAddress( $this->ip ) && strpos( $this->ip, '/' ) === false ) {
+ $iaddr = IP::toHex( $this->ip );
+ if( $iaddr ) {
# Only scan ranges which start in this /16, this improves search speed
# Blocks should not cross a /16 boundary.
$range = substr( $iaddr, 0, 4 );
// Fixme -- encapsulate this sort of query-building.
$dbr = wfGetDB( DB_SLAVE );
- $encIp = $dbr->addQuotes( IP::sanitizeIP($this->ip) );
+ $encIp = $dbr->addQuotes( IP::sanitizeIP( $this->ip ) );
$encAddr = $dbr->addQuotes( $iaddr );
$conds[] = "(ipb_address = $encIp) OR
(ipb_range_start" . $dbr->buildLike( $range, $dbr->anyString() ) . " AND
ipb_range_start <= $encAddr
AND ipb_range_end >= $encAddr)";
} else {
- $conds['ipb_address'] = IP::sanitizeIP($this->ip);
+ $conds['ipb_address'] = IP::sanitizeIP( $this->ip );
}
$conds['ipb_auto'] = 0;
// IP range
- } elseif ( IP::isIPAddress($this->ip) ) {
+ } elseif ( IP::isIPAddress( $this->ip ) ) {
$conds['ipb_address'] = Block::normaliseRange( $this->ip );
$conds['ipb_auto'] = 0;
} else {
@@ -315,7 +347,7 @@ class IPUnblockForm {
if ( $pager->getNumRows() ) {
$wgOut->addHTML(
$pager->getNavigationBar() .
- Xml::tags( 'ul', null, $pager->getBody() ) .
+ Html::rawElement( 'ul', null, $pager->getBody() ) .
$pager->getNavigationBar()
);
} elseif ( $this->ip != '') {
@@ -338,7 +370,7 @@ class IPUnblockForm {
}
function searchForm() {
- global $wgScript, $wgRequest, $wgLang;
+ global $wgScript, $wgLang;
$showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) );
$nondefaults = array();
@@ -365,30 +397,32 @@ class IPUnblockForm {
$hl = $wgLang->pipeList( $links );
return
- Xml::tags( 'form', array( 'action' => $wgScript ),
- Xml::hidden( 'title', SpecialPage::getTitleFor( 'Ipblocklist' )->getPrefixedDbKey() ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) .
+ Html::rawElement( 'form', array( 'action' => $wgScript ),
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
+ Html::openElement( 'fieldset' ) .
+ Html::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) .
Xml::inputLabel( wfMsg( 'ipblocklist-username' ), 'ip', 'ip', /* size */ false, $this->ip ) .
- '&nbsp;' .
+ '&#160;' .
Xml::submitButton( wfMsg( 'ipblocklist-submit' ) ) . '<br />' .
$hl .
- Xml::closeElement( 'fieldset' )
+ Html::closeElement( 'fieldset' )
);
}
/**
* Makes change an option link which carries all the other options
+ *
* @param $title see Title
- * @param $override
- * @param $options
+ * @param $override Array: special query string options, will override the
+ * ones in $options
+ * @param $options Array: query string options
+ * @param $active Boolean: whether to display the link in bold
*/
function makeOptionsLink( $title, $override, $options, $active = false ) {
global $wgUser;
$sk = $wgUser->getSkin();
$params = $override + $options;
- $ipblocklist = SpecialPage::getTitleFor( 'Ipblocklist' );
- return $sk->link( $ipblocklist, htmlspecialchars( $title ),
+ return $sk->link( $this->getTitle(), htmlspecialchars( $title ),
( $active ? array( 'style'=>'font-weight: bold;' ) : array() ), $params, array( 'known' ) );
}
@@ -453,11 +487,10 @@ class IPUnblockForm {
$line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) );
- $unblocklink = '';
$changeblocklink = '';
$toolLinks = '';
if ( $wgUser->isAllowed( 'block' ) ) {
- $unblocklink = $sk->link( SpecialPage::getTitleFor( 'Ipblocklist' ),
+ $unblocklink = $sk->link( $this->getTitle(),
$msg['unblocklink'],
array(),
array( 'action' => 'unblock', 'id' => $block->mId ),
@@ -513,7 +546,7 @@ class IPBlocklistPager extends ReverseChronologicalPager {
# Faster way
# Usernames and titles are in fact related by a simple substitution of space -> underscore
# The last few lines of Title::secureAndSplit() tell the story.
- while ( $row = $this->mResult->fetchObject() ) {
+ foreach ( $this->mResult as $row ) {
$name = str_replace( ' ', '_', $row->ipb_by_text );
$lb->add( NS_USER, $name );
$lb->add( NS_USER_TALK, $name );
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 70b2257a..9dee9d5a 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -1,11 +1,27 @@
<?php
/**
+ * Implements Special:LinkSearch
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
- *
* @author Brion Vibber
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
+
/**
* Special:LinkSearch to search the external-links table.
@@ -13,7 +29,7 @@
function wfSpecialLinkSearch( $par ) {
list( $limit, $offset ) = wfCheckLimits();
- global $wgOut, $wgRequest, $wgUrlProtocols, $wgMiserMode, $wgLang;
+ global $wgOut, $wgUrlProtocols, $wgMiserMode, $wgLang;
$target = $GLOBALS['wgRequest']->getVal( 'target', $par );
$namespace = $GLOBALS['wgRequest']->getIntorNull( 'namespace', null );
@@ -44,19 +60,18 @@ function wfSpecialLinkSearch( $par ) {
$protocol = '';
}
- $wgOut->allowClickjacking();
-
$self = Title::makeTitle( NS_SPECIAL, 'Linksearch' );
-
+
+ $wgOut->allowClickjacking();
$wgOut->addWikiMsg( 'linksearch-text', '<nowiki>' . $wgLang->commaList( $wgUrlProtocols ) . '</nowiki>' );
- $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) .
- Xml::hidden( 'title', $self->getPrefixedDbKey() ) .
+ $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) .
+ Html::hidden( 'title', $self->getPrefixedDbKey() ) .
'<fieldset>' .
Xml::element( 'legend', array(), wfMsg( 'linksearch' ) ) .
Xml::inputLabel( wfMsg( 'linksearch-pat' ), 'target', 'target', 50, $target ) . ' ';
if ( !$wgMiserMode ) {
$s .= Xml::label( wfMsg( 'linksearch-ns' ), 'namespace' ) . ' ' .
- XML::namespaceSelector( $namespace, '' );
+ Xml::namespaceSelector( $namespace, '' );
}
$s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) .
'</fieldset>' .
@@ -73,6 +88,9 @@ function wfSpecialLinkSearch( $par ) {
}
}
+/**
+ * @ingroup SpecialPage
+ */
class LinkSearchPage extends QueryPage {
function setParams( $params ) {
$this->mQuery = $params['query'];
@@ -98,8 +116,9 @@ class LinkSearchPage extends QueryPage {
$field = 'el_index';
$rv = LinkFilter::makeLikeArray( $query , $prot );
if ($rv === false) {
- //makeLike doesn't handle wildcard in IP, so we'll have to munge here.
+ // LinkFilter doesn't handle wildcard in IP, so we'll have to munge here.
if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) {
+ $dbr = wfGetDB( DB_SLAVE );
$rv = array( $prot . rtrim($query, " \t*"), $dbr->anyString() );
$field = 'el_to';
}
@@ -162,7 +181,7 @@ class LinkSearchPage extends QueryPage {
*/
function doQuery( $offset, $limit, $shownavigation=true ) {
global $wgOut;
- list( $this->mMungedQuery, $clause ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
+ list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
if( $this->mMungedQuery === false ) {
$wgOut->addWikiMsg( 'linksearch-error' );
} else {
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index b9332422..350e833b 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -1,16 +1,30 @@
<?php
/**
+ * Implements Special:Listfiles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
-
-/**
- *
- */
-function wfSpecialListfiles() {
+
+function wfSpecialListfiles( $par = null ) {
global $wgOut;
- $pager = new ImageListPager;
+ $pager = new ImageListPager( $par );
$limit = $pager->getForm();
$body = $pager->getBody();
@@ -24,21 +38,32 @@ function wfSpecialListfiles() {
class ImageListPager extends TablePager {
var $mFieldNames = null;
var $mQueryConds = array();
-
- function __construct() {
+ var $mUserName = null;
+
+ function __construct( $par = null ) {
global $wgRequest, $wgMiserMode;
if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) {
$this->mDefaultDirection = true;
} else {
$this->mDefaultDirection = false;
}
+
+ $userName = $wgRequest->getText( 'user', $par );
+ if ( $userName ) {
+ $nt = Title::newFromText( $userName, NS_USER );
+ if ( !is_null( $nt ) ) {
+ $this->mUserName = $nt->getText();
+ $this->mQueryConds['img_user_text'] = $this->mUserName;
+ }
+ }
+
$search = $wgRequest->getText( 'ilsearch' );
if ( $search != '' && !$wgMiserMode ) {
$nt = Title::newFromURL( $search );
- if( $nt ) {
+ if ( $nt ) {
$dbr = wfGetDB( DB_SLAVE );
- $this->mQueryConds = array( 'LOWER(img_name)' . $dbr->buildLike( $dbr->anyString(),
- strtolower( $nt->getDBkey() ), $dbr->anyString() ) );
+ $this->mQueryConds[] = 'LOWER(img_name)' . $dbr->buildLike( $dbr->anyString(),
+ strtolower( $nt->getDBkey() ), $dbr->anyString() );
}
}
@@ -49,6 +74,7 @@ class ImageListPager extends TablePager {
if ( !$this->mFieldNames ) {
global $wgMiserMode;
$this->mFieldNames = array(
+ 'thumb' => wfMsg( 'listfiles_thumb' ),
'img_timestamp' => wfMsg( 'listfiles_date' ),
'img_name' => wfMsg( 'listfiles_name' ),
'img_user_text' => wfMsg( 'listfiles_user' ),
@@ -63,7 +89,11 @@ class ImageListPager extends TablePager {
}
function isFieldSortable( $field ) {
- static $sortable = array( 'img_timestamp', 'img_name', 'img_size' );
+ static $sortable = array( 'img_timestamp', 'img_name' );
+ if ( $field == 'img_size' ) {
+ # No index for both img_size and img_user_text
+ return !isset( $this->mQueryConds['img_user_text'] );
+ }
return in_array( $field, $sortable );
}
@@ -71,6 +101,7 @@ class ImageListPager extends TablePager {
$tables = array( 'image' );
$fields = array_keys( $this->getFieldNames() );
$fields[] = 'img_user';
+ $fields[array_search('thumb', $fields)] = 'img_name as thumb';
$options = $join_conds = array();
# Depends on $wgMiserMode
@@ -78,9 +109,11 @@ class ImageListPager extends TablePager {
$tables[] = 'oldimage';
# Need to rewrite this one
- foreach ( $fields as &$field )
- if ( $field == 'count' )
+ foreach ( $fields as &$field ) {
+ if ( $field == 'count' ) {
$field = 'COUNT(oi_archive_name) as count';
+ }
+ }
unset( $field );
$dbr = wfGetDB( DB_SLAVE );
@@ -110,7 +143,7 @@ class ImageListPager extends TablePager {
if ( $this->mResult->numRows() ) {
$lb = new LinkBatch;
$this->mResult->seek( 0 );
- while ( $row = $this->mResult->fetchObject() ) {
+ foreach ( $this->mResult as $row ) {
if ( $row->img_user ) {
$lb->add( NS_USER, str_replace( ' ', '_', $row->img_user_text ) );
}
@@ -124,14 +157,18 @@ class ImageListPager extends TablePager {
function formatValue( $field, $value ) {
global $wgLang;
switch ( $field ) {
+ case 'thumb':
+ $file = wfLocalFile( $value );
+ $thumb = $file->transform( array( 'width' => 180 ) );
+ return $thumb->toHtml( array( 'desc-link' => true ) );
case 'img_timestamp':
return htmlspecialchars( $wgLang->timeanddate( $value, true ) );
case 'img_name':
static $imgfile = null;
if ( $imgfile === null ) $imgfile = wfMsg( 'imgfile' );
- $name = $this->mCurrentRow->img_name;
- $link = $this->getSkin()->linkKnown( Title::makeTitle( NS_FILE, $name ), $value );
+ $filePage = Title::makeTitle( NS_FILE, $value );
+ $link = $this->getSkin()->linkKnown( $filePage, htmlspecialchars( $filePage->getText() ) );
$image = wfLocalFile( $value );
$url = $image->getURL();
$download = Xml::element('a', array( 'href' => $url ), $imgfile );
@@ -158,21 +195,26 @@ class ImageListPager extends TablePager {
function getForm() {
global $wgRequest, $wgScript, $wgMiserMode;
$search = $wgRequest->getText( 'ilsearch' );
-
- $s = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'listfiles' ) ) .
- Xml::tags( 'label', null, wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) );
-
+ $inputForm = array();
+ $inputForm['table_pager_limit_label'] = $this->getLimitSelect();
if ( !$wgMiserMode ) {
- $s .= "<br />\n" .
- Xml::inputLabel( wfMsg( 'listfiles_search_for' ), 'ilsearch', 'mw-ilsearch', 20, $search );
+ $inputForm['listfiles_search_for'] = Html::input( 'ilsearch', $search, 'text', array(
+ 'size' => '40',
+ 'maxlength' => '255',
+ 'id' => 'mw-ilsearch',
+ ) );
}
- $s .= ' ' .
- Xml::submitButton( wfMsg( 'table_pager_limit_submit' ) ) ."\n" .
- $this->getHiddenFields( array( 'limit', 'ilsearch' ) ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' ) . "\n";
+ $inputForm['username'] = Html::input( 'user', $this->mUserName, 'text', array(
+ 'size' => '40',
+ 'maxlength' => '255',
+ 'id' => 'mw-listfiles-user',
+ ) );
+ $s = Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) ) .
+ Xml::fieldset( wfMsg( 'listfiles' ) ) .
+ Xml::buildForm( $inputForm, 'table_pager_limit_submit' ) .
+ $this->getHiddenFields( array( 'limit', 'ilsearch', 'user' ) ) .
+ Html::closeElement( 'fieldset' ) .
+ Html::closeElement( 'form' ) . "\n";
return $s;
}
@@ -187,4 +229,25 @@ class ImageListPager extends TablePager {
function getSortHeaderClass() {
return 'listfiles_sort ' . parent::getSortHeaderClass();
}
+
+ function getPagingQueries() {
+ $queries = parent::getPagingQueries();
+ if ( !is_null( $this->mUserName ) ) {
+ # Append the username to the query string
+ foreach ( $queries as &$query ) {
+ $query['user'] = $this->mUserName;
+ }
+ }
+ return $queries;
+ }
+
+ function getDefaultQuery() {
+ $queries = parent::getDefaultQuery();
+ if ( !isset( $queries['user'] )
+ && !is_null( $this->mUserName ) )
+ {
+ $queries['user'] = $this->mUserName;
+ }
+ return $queries;
+ }
}
diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php
index 83724a4f..910ffd08 100644
--- a/includes/specials/SpecialListgrouprights.php
+++ b/includes/specials/SpecialListgrouprights.php
@@ -1,4 +1,25 @@
<?php
+/**
+ * Implements Special:Listgrouprights
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
/**
* This special page lists all defined user groups and the associated rights.
@@ -24,10 +45,9 @@ class SpecialListGroupRights extends SpecialPage {
* Show the special page
*/
public function execute( $par ) {
- global $wgOut, $wgImplicitGroups, $wgMessageCache;
+ global $wgOut, $wgImplicitGroups;
global $wgGroupPermissions, $wgRevokePermissions, $wgAddGroups, $wgRemoveGroups;
global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
- $wgMessageCache->loadAllMessages();
$this->setHeaders();
$this->outputHeader();
@@ -40,8 +60,23 @@ class SpecialListGroupRights extends SpecialPage {
'</tr>'
);
- foreach( $wgGroupPermissions as $group => $permissions ) {
- $groupname = ( $group == '*' ) ? 'all' : $group; // Replace * with a more descriptive groupname
+ $allGroups = array_unique( array_merge(
+ array_keys( $wgGroupPermissions ),
+ array_keys( $wgRevokePermissions ),
+ array_keys( $wgAddGroups ),
+ array_keys( $wgRemoveGroups ),
+ array_keys( $wgGroupsAddToSelf ),
+ array_keys( $wgGroupsRemoveFromSelf )
+ ) );
+ asort( $allGroups );
+
+ foreach ( $allGroups as $group ) {
+ $permissions = isset( $wgGroupPermissions[$group] )
+ ? $wgGroupPermissions[$group]
+ : array();
+ $groupname = ( $group == '*' ) // Replace * with a more descriptive groupname
+ ? 'all'
+ : $group;
$msg = wfMsg( 'group-' . $groupname );
if ( wfEmptyMsg( 'group-' . $groupname, $msg ) || $msg == '' ) {
@@ -59,11 +94,11 @@ class SpecialListGroupRights extends SpecialPage {
if( $group == '*' ) {
// Do not make a link for the generic * group
- $grouppage = htmlspecialchars($groupnameLocalized);
+ $grouppage = htmlspecialchars( $groupnameLocalized );
} else {
$grouppage = $this->skin->link(
Title::newFromText( $grouppageLocalized ),
- htmlspecialchars($groupnameLocalized)
+ htmlspecialchars( $groupnameLocalized )
);
}
@@ -95,16 +130,15 @@ class SpecialListGroupRights extends SpecialPage {
$addgroupsSelf = isset( $wgGroupsAddToSelf[$group] ) ? $wgGroupsAddToSelf[$group] : array();
$removegroupsSelf = isset( $wgGroupsRemoveFromSelf[$group] ) ? $wgGroupsRemoveFromSelf[$group] : array();
- $wgOut->addHTML(
- '<tr>
- <td>' .
- $grouppage . $grouplink .
- '</td>
- <td>' .
+ $id = $group == '*' ? false : Sanitizer::escapeId( $group );
+ $wgOut->addHTML( Html::rawElement( 'tr', array( 'id' => $id ),
+ "
+ <td>$grouppage$grouplink</td>
+ <td>" .
self::formatPermissions( $permissions, $revoke, $addgroups, $removegroups, $addgroupsSelf, $removegroupsSelf ) .
'</td>
- </tr>'
- );
+ '
+ ) );
}
$wgOut->addHTML(
Xml::closeElement( 'table' ) . "\n<br /><hr />\n"
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index bf594070..315047da 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -1,11 +1,27 @@
<?php
/**
+ * Implements Special:Listredirects
+ *
+ * Copyright © 2006 Rob Church
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
- *
* @author Rob Church <robchur@gmail.com>
- * @copyright © 2006 Rob Church
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
/**
@@ -49,10 +65,10 @@ class ListredirectsPage extends QueryPage {
$targetLink = $skin->link( $target );
return "$rd_link $arr $targetLink";
} else {
- return "<s>$rd_link</s>";
+ return "<del>$rd_link</del>";
}
} else {
- return "<s>$rd_link</s>";
+ return "<del>$rd_link</del>";
}
}
}
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index bdb59980..abc0363a 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -1,27 +1,26 @@
<?php
-
-# Copyright (C) 2004 Brion Vibber, lcrocker, Tim Starling,
-# Domas Mituzas, Ashar Voultoiz, Jens Frank, Zhengzhu.
-#
-# © 2006 Rob Church <robchur@gmail.com>
-#
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
/**
+ * Implements Special:Listusers
+ *
+ * Copyright © 2004 Brion Vibber, lcrocker, Tim Starling,
+ * Domas Mituzas, Ashar Voultoiz, Jens Frank, Zhengzhu,
+ * 2006 Rob Church <robchur@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
@@ -120,16 +119,18 @@ class UsersPager extends AlphabeticPager {
function formatRow( $row ) {
global $wgLang;
+ if ($row->user_id == 0) #Bug 16487
+ return '';
+
$userPage = Title::makeTitle( NS_USER, $row->user_name );
$name = $this->getSkin()->link( $userPage, htmlspecialchars( $userPage->getText() ) );
- if( $row->numgroups > 1 || ( $this->requestedGroup && $row->numgroups == 1 ) ) {
+ $groups_list = self::getGroups( $row->user_id );
+ if( count( $groups_list ) > 0 ) {
$list = array();
- foreach( self::getGroups( $row->user_id ) as $group )
+ foreach( $groups_list as $group )
$list[] = self::buildGroupLink( $group );
$groups = $wgLang->commaList( $list );
- } elseif( $row->numgroups == 1 ) {
- $groups = self::buildGroupLink( $row->singlegroup );
} else {
$groups = '';
}
@@ -166,7 +167,7 @@ class UsersPager extends AlphabeticPager {
}
$this->mResult->rewind();
$batch = new LinkBatch;
- while ( $row = $this->mResult->fetchObject() ) {
+ foreach ( $this->mResult as $row ) {
$batch->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
}
$batch->execute();
@@ -175,13 +176,13 @@ class UsersPager extends AlphabeticPager {
}
function getPageHeader( ) {
- global $wgScript, $wgRequest;
+ global $wgScript;
$self = $this->getTitle();
# Form tag
$out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' ) ) .
Xml::fieldset( wfMsg( 'listusers' ) ) .
- Xml::hidden( 'title', $self->getPrefixedDbKey() );
+ Html::hidden( 'title', $self->getPrefixedDbKey() );
# Username field
$out .= Xml::label( wfMsg( 'listusersfrom' ), 'offset' ) . ' ' .
@@ -195,14 +196,14 @@ class UsersPager extends AlphabeticPager {
$out .= Xml::option( $groupText, $group, $group == $this->requestedGroup );
$out .= Xml::closeElement( 'select' ) . '<br />';
$out .= Xml::checkLabel( wfMsg('listusers-editsonly'), 'editsOnly', 'editsOnly', $this->editsOnly );
- $out .= '&nbsp;';
+ $out .= '&#160;';
$out .= Xml::checkLabel( wfMsg('listusers-creationsort'), 'creationSort', 'creationSort', $this->creationSort );
$out .= '<br />';
wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) );
# Submit button and form bottom
- $out .= Xml::hidden( 'limit', $this->mLimit );
+ $out .= Html::hidden( 'limit', $this->mLimit );
$out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) );
wfRunHooks( 'SpecialListusersHeader', array( $this, &$out ) );
$out .= Xml::closeElement( 'fieldset' ) .
@@ -269,13 +270,13 @@ class UsersPager extends AlphabeticPager {
* $par string (optional) A group to list users from
*/
function wfSpecialListusers( $par = null ) {
- global $wgRequest, $wgOut;
+ global $wgOut;
$up = new UsersPager($par);
# getBody() first to check, if empty
$usersbody = $up->getBody();
- $s = XML::openElement( 'div', array('class' => 'mw-spcontent') );
+ $s = Xml::openElement( 'div', array('class' => 'mw-spcontent') );
$s .= $up->getPageHeader();
if( $usersbody ) {
$s .= $up->getNavigationBar();
@@ -284,6 +285,6 @@ function wfSpecialListusers( $par = null ) {
} else {
$s .= '<p>' . wfMsgHTML('listusers-noresult') . '</p>';
};
- $s .= XML::closeElement( 'div' );
+ $s .= Xml::closeElement( 'div' );
$wgOut->addHTML( $s );
}
diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php
index 8c701dd6..aad3cea4 100644
--- a/includes/specials/SpecialLockdb.php
+++ b/includes/specials/SpecialLockdb.php
@@ -1,97 +1,108 @@
<?php
/**
+ * Implements Special:Lockdb
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * Constructor
+ * A form to make the database readonly (eg for maintenance purposes).
+ *
+ * @ingroup SpecialPage
*/
-function wfSpecialLockdb() {
- global $wgUser, $wgOut, $wgRequest;
+class SpecialLockdb extends SpecialPage {
+ var $reason = '';
- if( !$wgUser->isAllowed( 'siteadmin' ) ) {
- $wgOut->permissionRequired( 'siteadmin' );
- return;
+ public function __construct() {
+ parent::__construct( 'Lockdb', 'siteadmin' );
}
- # If the lock file isn't writable, we can do sweet bugger all
- global $wgReadOnlyFile;
- if( !is_writable( dirname( $wgReadOnlyFile ) ) ) {
- DBLockForm::notWritable();
- return;
- }
+ public function execute( $par ) {
+ global $wgUser, $wgOut, $wgRequest;
- $action = $wgRequest->getVal( 'action' );
- $f = new DBLockForm();
+ $this->setHeaders();
- if ( 'success' == $action ) {
- $f->showSuccess();
- } else if ( 'submit' == $action && $wgRequest->wasPosted() &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
- $f->doSubmit();
- } else {
- $f->showForm( '' );
- }
-}
+ if( !$wgUser->isAllowed( 'siteadmin' ) ) {
+ $wgOut->permissionRequired( 'siteadmin' );
+ return;
+ }
-/**
- * A form to make the database readonly (eg for maintenance purposes).
- * @ingroup SpecialPage
- */
-class DBLockForm {
- var $reason = '';
+ $this->outputHeader();
- function DBLockForm() {
- global $wgRequest;
- $this->reason = $wgRequest->getText( 'wpLockReason' );
+ # If the lock file isn't writable, we can do sweet bugger all
+ global $wgReadOnlyFile;
+ if( !is_writable( dirname( $wgReadOnlyFile ) ) ) {
+ self::notWritable();
+ return;
+ }
+
+ $action = $wgRequest->getVal( 'action' );
+ $this->reason = $wgRequest->getVal( 'wpLockReason', '' );
+
+ if ( $action == 'success' ) {
+ $this->showSuccess();
+ } else if ( $action == 'submit' && $wgRequest->wasPosted() &&
+ $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
+ $this->doSubmit();
+ } else {
+ $this->showForm();
+ }
}
- function showForm( $err ) {
+ private function showForm( $err = '' ) {
global $wgOut, $wgUser;
- $wgOut->setPagetitle( wfMsg( 'lockdb' ) );
$wgOut->addWikiMsg( 'lockdbtext' );
- if ( $err != "" ) {
+ if ( $err != '' ) {
$wgOut->setSubtitle( wfMsg( 'formerror' ) );
$wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" );
}
- $lc = htmlspecialchars( wfMsg( 'lockconfirm' ) );
- $lb = htmlspecialchars( wfMsg( 'lockbtn' ) );
- $elr = htmlspecialchars( wfMsg( 'enterlockreason' ) );
- $titleObj = SpecialPage::getTitleFor( 'Lockdb' );
- $action = $titleObj->escapeLocalURL( 'action=submit' );
- $reason = htmlspecialchars( $this->reason );
- $token = htmlspecialchars( $wgUser->editToken() );
-
- $wgOut->addHTML( <<<HTML
-<form id="lockdb" method="post" action="{$action}">
-{$elr}:
-<textarea name="wpLockReason" rows="10" cols="60" wrap="virtual">{$reason}</textarea>
-<table border="0">
+
+ $wgOut->addHTML(
+ Html::openElement( 'form', array( 'id' => 'lockdb', 'method' => 'POST',
+ 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) ). "\n" .
+ wfMsgHtml( 'enterlockreason' ) . ":\n" .
+ Html::textarea( 'wpLockReason', $this->reason, array( 'rows' => 4 ) ). "
+<table>
<tr>
- <td align="right">
- <input type="checkbox" name="wpLockConfirm" />
+ " . Html::openElement( 'td', array( 'style' => 'text-align:right' ) ) . "
+ " . Html::input( 'wpLockConfirm', null, 'checkbox' ) . "
</td>
- <td align="left">{$lc}</td>
+ " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) .
+ wfMsgHtml( 'lockconfirm' ) . "</td>
</tr>
<tr>
- <td>&nbsp;</td>
- <td align="left">
- <input type="submit" name="wpLock" value="{$lb}" />
+ <td>&#160;</td>
+ " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) . "
+ " . Html::input( 'wpLock', wfMsg( 'lockbtn' ), 'submit' ) . "
</td>
</tr>
-</table>
-<input type="hidden" name="wpEditToken" value="{$token}" />
-</form>
-HTML
-);
+</table>\n" .
+ Html::hidden( 'wpEditToken', $wgUser->editToken() ) . "\n" .
+ Html::closeElement( 'form' )
+ );
}
- function doSubmit() {
- global $wgOut, $wgUser, $wgLang, $wgRequest;
+ private function doSubmit() {
+ global $wgOut, $wgUser, $wgContLang, $wgRequest;
global $wgReadOnlyFile;
if ( ! $wgRequest->getCheck( 'wpLockConfirm' ) ) {
@@ -109,14 +120,13 @@ HTML
}
fwrite( $fp, $this->reason );
fwrite( $fp, "\n<p>(by " . $wgUser->getName() . " at " .
- $wgLang->timeanddate( wfTimestampNow() ) . ")</p>\n" );
+ $wgContLang->timeanddate( wfTimestampNow() ) . ")</p>\n" );
fclose( $fp );
- $titleObj = SpecialPage::getTitleFor( 'Lockdb' );
- $wgOut->redirect( $titleObj->getFullURL( 'action=success' ) );
+ $wgOut->redirect( $this->getTitle()->getFullURL( 'action=success' ) );
}
- function showSuccess() {
+ private function showSuccess() {
global $wgOut;
$wgOut->setPagetitle( wfMsg( 'lockdb' ) );
diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php
index d1ccc8c4..a2af8de5 100644
--- a/includes/specials/SpecialLog.php
+++ b/includes/specials/SpecialLog.php
@@ -1,86 +1,127 @@
<?php
-# Copyright (C) 2008 Aaron Schulz
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
+ * Implements Special:Log
+ *
+ * Copyright © 2008 Aaron Schulz
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * constructor
+ * A special page that lists log entries
+ *
+ * @ingroup SpecialPage
*/
-function wfSpecialLog( $par = '' ) {
- global $wgRequest, $wgOut, $wgUser, $wgLogTypes;
-
- # Get parameters
- $parms = explode( '/', ($par = ( $par !== null ) ? $par : '' ) );
- $symsForAll = array( '*', 'all' );
- if ( $parms[0] != '' && ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) ) ) {
- $type = $par;
- $user = $wgRequest->getText( 'user' );
- } else if ( count( $parms ) == 2 ) {
- $type = $parms[0];
- $user = $parms[1];
- } else {
- $type = $wgRequest->getVal( 'type' );
- $user = ( $par != '' ) ? $par : $wgRequest->getText( 'user' );
+class SpecialLog extends SpecialPage {
+
+ public function __construct() {
+ parent::__construct( 'Log' );
}
- $title = $wgRequest->getText( 'page' );
- $pattern = $wgRequest->getBool( 'pattern' );
- $y = $wgRequest->getIntOrNull( 'year' );
- $m = $wgRequest->getIntOrNull( 'month' );
- $tagFilter = $wgRequest->getVal( 'tagfilter' );
- # Don't let the user get stuck with a certain date
- $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev';
- if( $skip ) {
- $y = '';
- $m = '';
+
+ public function execute( $par ) {
+ global $wgRequest;
+
+ $this->setHeaders();
+ $this->outputHeader();
+
+ $opts = new FormOptions;
+ $opts->add( 'type', '' );
+ $opts->add( 'user', '' );
+ $opts->add( 'page', '' );
+ $opts->add( 'pattern', false );
+ $opts->add( 'year', null, FormOptions::INTNULL );
+ $opts->add( 'month', null, FormOptions::INTNULL );
+ $opts->add( 'tagfilter', '' );
+ $opts->add( 'offset', '' );
+ $opts->add( 'dir', '' );
+ $opts->add( 'offender', '' );
+
+ // Set values
+ $opts->fetchValuesFromRequest( $wgRequest );
+ if ( $par ) {
+ $this->parseParams( $opts, (string)$par );
+ }
+
+ # Don't let the user get stuck with a certain date
+ if ( $opts->getValue( 'offset' ) || $opts->getValue( 'dir' ) == 'prev' ) {
+ $opts->setValue( 'year', '' );
+ $opts->setValue( 'month', '' );
+ }
+
+ # Handle type-specific inputs
+ $qc = array();
+ if ( $opts->getValue( 'type' ) == 'suppress' ) {
+ $offender = User::newFromName( $opts->getValue( 'offender' ), false );
+ if ( $offender && $offender->getId() > 0 ) {
+ $qc = array( 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() );
+ } elseif ( $offender && IP::isIPAddress( $offender->getName() ) ) {
+ $qc = array( 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() );
+ }
+ }
+
+ $this->show( $opts, $qc );
}
- # Handle type-specific inputs
- $qc = array();
- if( $type == 'suppress' ) {
- $offender = User::newFromName( $wgRequest->getVal('offender'), false );
- if( $offender && $offender->getId() > 0 ) {
- $qc = array( 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() );
- } else if( $offender && IP::isIPAddress( $offender->getName() ) ) {
- $qc = array( 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() );
+
+ private function parseParams( FormOptions $opts, $par ) {
+ global $wgLogTypes;
+
+ # Get parameters
+ $parms = explode( '/', ($par = ( $par !== null ) ? $par : '' ) );
+ $symsForAll = array( '*', 'all' );
+ if ( $parms[0] != '' && ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) ) ) {
+ $opts->setValue( 'type', $par );
+ } elseif ( count( $parms ) == 2 ) {
+ $opts->setValue( 'type', $parms[0] );
+ $opts->setValue( 'user', $parms[1] );
+ } elseif ( $par != '' ) {
+ $opts->setValue( 'user', $par );
}
}
- # Create a LogPager item to get the results and a LogEventsList item to format them...
- $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
- $pager = new LogPager( $loglist, $type, $user, $title, $pattern, $qc, $y, $m, $tagFilter );
- # Set title and add header
- $loglist->showHeader( $pager->getType() );
- # Show form options
- $loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(),
- $pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $tagFilter );
- # Insert list
- $logBody = $pager->getBody();
- if( $logBody ) {
- $wgOut->addHTML(
- $pager->getNavigationBar() .
- $loglist->beginLogEventsList() .
- $logBody .
- $loglist->endLogEventsList() .
- $pager->getNavigationBar()
- );
- } else {
- $wgOut->addWikiMsg( 'logempty' );
+
+ private function show( FormOptions $opts, array $extraConds ) {
+ global $wgOut, $wgUser;
+
+ # Create a LogPager item to get the results and a LogEventsList item to format them...
+ $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
+ $pager = new LogPager( $loglist, $opts->getValue( 'type' ), $opts->getValue( 'user' ),
+ $opts->getValue( 'page' ), $opts->getValue( 'pattern' ), $extraConds, $opts->getValue( 'year' ),
+ $opts->getValue( 'month' ), $opts->getValue( 'tagfilter' ) );
+
+ # Set title and add header
+ $loglist->showHeader( $pager->getType() );
+
+ # Show form options
+ $loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(),
+ $pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $opts->getValue( 'tagfilter' ) );
+
+ # Insert list
+ $logBody = $pager->getBody();
+ if ( $logBody ) {
+ $wgOut->addHTML(
+ $pager->getNavigationBar() .
+ $loglist->beginLogEventsList() .
+ $logBody .
+ $loglist->endLogEventsList() .
+ $pager->getNavigationBar()
+ );
+ } else {
+ $wgOut->addWikiMsg( 'logempty' );
+ }
}
}
diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php
index 90da25fd..0788037f 100644
--- a/includes/specials/SpecialLonelypages.php
+++ b/includes/specials/SpecialLonelypages.php
@@ -1,5 +1,22 @@
<?php
/**
+ * Implements Special:Lonelypaages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
@@ -7,6 +24,7 @@
/**
* A special page looking for articles with no article linking to them,
* thus being lonely.
+ *
* @ingroup SpecialPage
*/
class LonelyPagesPage extends PageQueryPage {
diff --git a/includes/specials/SpecialLongpages.php b/includes/specials/SpecialLongpages.php
index be16a029..cd0f3090 100644
--- a/includes/specials/SpecialLongpages.php
+++ b/includes/specials/SpecialLongpages.php
@@ -1,5 +1,22 @@
<?php
/**
+ * Implements Special:Longpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
index dafe003e..79683a35 100644
--- a/includes/specials/SpecialMIMEsearch.php
+++ b/includes/specials/SpecialMIMEsearch.php
@@ -1,13 +1,25 @@
<?php
/**
- * A special page to search for files by MIME type as defined in the
- * img_major_mime and img_minor_mime fields in the image table
+ * Implements Special:MIMESearch
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup SpecialPage
- *
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
/**
@@ -18,7 +30,7 @@
class MIMEsearchPage extends QueryPage {
var $major, $minor;
- function MIMEsearchPage( $major, $minor ) {
+ function __construct( $major, $minor ) {
$this->major = $major;
$this->minor = $minor;
}
@@ -95,7 +107,7 @@ function wfSpecialMIMEsearch( $par = null ) {
$wgOut->addHTML(
Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) .
Xml::openElement( 'fieldset' ) .
- Xml::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) .
+ Html::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) .
Xml::element( 'legend', null, wfMsg( 'mimesearch' ) ) .
Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . ' ' .
Xml::submitButton( wfMsg( 'ilsubmit' ) ) .
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index 1b4ef30c..43b4ef6a 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -1,34 +1,42 @@
<?php
/**
- * Special page allowing users with the appropriate permissions to
- * merge article histories, with some restrictions
+ * Implements Special:MergeHistory
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * Constructor
- */
-function wfSpecialMergehistory( $par ) {
- global $wgRequest;
-
- $form = new MergehistoryForm( $wgRequest, $par );
- $form->execute();
-}
-
-/**
- * The HTML form for Special:MergeHistory, which allows users with the appropriate
- * permissions to view and restore deleted content.
+ * Special page allowing users with the appropriate permissions to
+ * merge article histories, with some restrictions
+ *
* @ingroup SpecialPage
*/
-class MergehistoryForm {
+class SpecialMergeHistory extends SpecialPage {
var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment;
var $mTargetObj, $mDestObj;
- function MergehistoryForm( $request, $par = "" ) {
- global $wgUser;
+ public function __construct() {
+ parent::__construct( 'MergeHistory', 'mergehistory' );
+ }
+ private function loadRequestParams( $request ) {
+ global $wgUser;
$this->mAction = $request->getVal( 'action' );
$this->mTarget = $request->getVal( 'target' );
$this->mDest = $request->getVal( 'dest' );
@@ -51,7 +59,6 @@ class MergehistoryForm {
$this->mTargetObj = null;
$this->mDestObj = null;
}
-
$this->preCacheMessages();
}
@@ -62,14 +69,27 @@ class MergehistoryForm {
function preCacheMessages() {
// Precache various messages
if( !isset( $this->message ) ) {
- $this->message['last'] = wfMsgExt( 'last', array( 'escape') );
+ $this->message['last'] = wfMsgExt( 'last', array( 'escape' ) );
}
}
- function execute() {
- global $wgOut;
+ function execute( $par ) {
+ global $wgOut, $wgRequest, $wgUser;
+
+ if ( wfReadOnly() ) {
+ $wgOut->readOnlyPage();
+ return;
+ }
+
+ if( !$this->userCanExecute( $wgUser ) ) {
+ $this->displayRestrictionError();
+ return;
+ }
+
+ $this->loadRequestParams( $wgRequest );
- $wgOut->setPagetitle( wfMsgHtml( "mergehistory" ) );
+ $this->setHeaders();
+ $this->outputHeader();
if( $this->mTargetID && $this->mDestID && $this->mAction=="submit" && $this->mMerge ) {
return $this->merge();
@@ -122,10 +142,9 @@ class MergehistoryForm {
'<fieldset>' .
Xml::element( 'legend', array(),
wfMsg( 'mergehistory-box' ) ) .
- Xml::hidden( 'title',
- SpecialPage::getTitleFor( 'Mergehistory' )->getPrefixedDbKey() ) .
- Xml::hidden( 'submitted', '1' ) .
- Xml::hidden( 'mergepoint', $this->mTimestamp ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
+ Html::hidden( 'submitted', '1' ) .
+ Html::hidden( 'mergepoint', $this->mTimestamp ) .
Xml::openElement( 'table' ) .
"<tr>
<td>".Xml::label( wfMsg( 'mergehistory-from' ), 'target' )."</td>
@@ -142,7 +161,7 @@ class MergehistoryForm {
}
private function showHistory() {
- global $wgLang, $wgUser, $wgOut;
+ global $wgUser, $wgOut;
$this->sk = $wgUser->getSkin();
@@ -154,7 +173,7 @@ class MergehistoryForm {
$revisions = new MergeHistoryPager( $this, array(), $this->mTargetObj, $this->mDestObj );
$haveRevisions = $revisions && $revisions->getNumRows() > 0;
- $titleObj = SpecialPage::getTitleFor( "Mergehistory" );
+ $titleObj = $this->getTitle();
$action = $titleObj->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
$top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) );
@@ -177,7 +196,7 @@ class MergehistoryForm {
"</td>
</tr>
<tr>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-submit'>" .
Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) .
"</td>
@@ -206,11 +225,11 @@ class MergehistoryForm {
# When we submit, go by page ID to avoid some nasty but unlikely collisions.
# Such would happen if a page was renamed after the form loaded, but before submit
- $misc = Xml::hidden( 'targetID', $this->mTargetObj->getArticleID() );
- $misc .= Xml::hidden( 'destID', $this->mDestObj->getArticleID() );
- $misc .= Xml::hidden( 'target', $this->mTarget );
- $misc .= Xml::hidden( 'dest', $this->mDest );
- $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() );
+ $misc = Html::hidden( 'targetID', $this->mTargetObj->getArticleID() );
+ $misc .= Html::hidden( 'destID', $this->mDestObj->getArticleID() );
+ $misc .= Html::hidden( 'target', $this->mTarget );
+ $misc .= Html::hidden( 'dest', $this->mDest );
+ $misc .= Html::hidden( 'wpEditToken', $wgUser->editToken() );
$misc .= Xml::closeElement( 'form' );
$wgOut->addHTML( $misc );
@@ -419,7 +438,7 @@ class MergeHistoryPager extends ReverseChronologicalPager {
$batch = new LinkBatch();
# Give some pointers to make (last) links
$this->mForm->prevId = array();
- while( $row = $this->mResult->fetchObject() ) {
+ foreach ( $this->mResult as $row ) {
$batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) );
$batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) );
@@ -440,7 +459,6 @@ class MergeHistoryPager extends ReverseChronologicalPager {
}
function formatRow( $row ) {
- $block = new Block;
return $this->mForm->formatRevisionRow( $row );
}
diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php
index 1ba05626..124f0bd5 100644
--- a/includes/specials/SpecialMostcategories.php
+++ b/includes/specials/SpecialMostcategories.php
@@ -1,15 +1,32 @@
<?php
/**
+ * Implements Special:Mostcategories
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
- *
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
/**
- * implements Special:Mostcategories
+ * A special page that list pages that have highest category count
+ *
* @ingroup SpecialPage
*/
class MostcategoriesPage extends QueryPage {
diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php
index 5cc100ba..411a281b 100644
--- a/includes/specials/SpecialMostimages.php
+++ b/includes/specials/SpecialMostimages.php
@@ -1,15 +1,32 @@
<?php
/**
+ * Implements Special:Mostimages
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
- *
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
/**
- * implements Special:Mostimages
+ * A special page page that list most used images
+ *
* @ingroup SpecialPage
*/
class MostimagesPage extends ImageQueryPage {
@@ -36,7 +53,7 @@ class MostimagesPage extends ImageQueryPage {
function getCellHtml( $row ) {
global $wgLang;
- return wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ),
+ return wfMsgExt( 'nimagelinks', array( 'parsemag', 'escape' ),
$wgLang->formatNum( $row->value ) ) . '<br />';
}
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index f112ae17..c731588a 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -1,20 +1,34 @@
<?php
/**
+ * Implements Special:Mostlinked
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason, 2006 Rob Church
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * @author Rob Church <robchur@gmail.com>
*/
/**
* A special page to show pages ordered by the number of pages linking to them.
- * Implements Special:Mostlinked
*
* @ingroup SpecialPage
- *
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @author Rob Church <robchur@gmail.com>
- * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
- * @copyright © 2006 Rob Church
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
class MostlinkedPage extends QueryPage {
@@ -59,8 +73,9 @@ class MostlinkedPage extends QueryPage {
function preprocessResults( $db, $res ) {
if( $db->numRows( $res ) > 0 ) {
$linkBatch = new LinkBatch();
- while( $row = $db->fetchObject( $res ) )
+ foreach ( $res as $row ) {
$linkBatch->add( $row->namespace, $row->title );
+ }
$db->dataSeek( $res, 0 );
$linkBatch->execute();
}
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index 20a35c97..e1fc1d95 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -1,17 +1,33 @@
<?php
/**
+ * Implements Special:Mostlinkedcategories
+ *
+ * Copyright © 2005, Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
*/
/**
* A querypage to show categories ordered in descending order by the pages in them
*
* @ingroup SpecialPage
- *
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
class MostlinkedCategoriesPage extends QueryPage {
@@ -42,8 +58,9 @@ class MostlinkedCategoriesPage extends QueryPage {
*/
function preprocessResults( $db, $res ) {
$batch = new LinkBatch;
- while ( $row = $db->fetchObject( $res ) )
+ foreach ( $res as $row ) {
$batch->add( $row->namespace, $row->title );
+ }
$batch->execute();
// Back to start for display
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
index 71a6b539..822d6bc9 100644
--- a/includes/specials/SpecialMostlinkedtemplates.php
+++ b/includes/specials/SpecialMostlinkedtemplates.php
@@ -1,7 +1,25 @@
<?php
/**
+ * Implements Special:Mostlinkedtemplates
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
*/
/**
@@ -9,7 +27,6 @@
* transclusion links, i.e. "most used" templates
*
* @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>
*/
class SpecialMostlinkedtemplates extends QueryPage {
@@ -75,7 +92,7 @@ class SpecialMostlinkedtemplates extends QueryPage {
*/
public function preprocessResults( $db, $res ) {
$batch = new LinkBatch();
- while( $row = $db->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$batch->add( $row->namespace, $row->title );
}
$batch->execute();
@@ -110,8 +127,8 @@ class SpecialMostlinkedtemplates extends QueryPage {
private function makeWlhLink( $title, $skin, $result ) {
global $wgLang;
$wlh = SpecialPage::getTitleFor( 'Whatlinkshere' );
- $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->value ) );
+ $label = wfMsgExt( 'ntransclusions', array( 'parsemag', 'escape' ),
+ $wgLang->formatNum( $result->value ) );
return $skin->link( $wlh, $label, array(), array( 'target' => $title->getPrefixedText() ) );
}
}
diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php
index 414e8d97..f9bafabc 100644
--- a/includes/specials/SpecialMostrevisions.php
+++ b/includes/specials/SpecialMostrevisions.php
@@ -1,15 +1,32 @@
<?php
/**
- * A special page to show pages in the
+ * Implements Special:Mostrevisions
*
- * @ingroup SpecialPage
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
/**
+ * A special page to show pages with highest revision count
+ *
* @ingroup SpecialPage
*/
class MostrevisionsPage extends QueryPage {
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index 02197b19..2f156c65 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -1,93 +1,104 @@
<?php
/**
+ * Implements Special:Movepage
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * Constructor
+ * A special page that allows users to change page titles
+ *
+ * @ingroup SpecialPage
*/
-function wfSpecialMovepage( $par = null ) {
- global $wgUser, $wgOut, $wgRequest, $action;
+class MovePageForm extends UnlistedSpecialPage {
+ var $oldTitle, $newTitle; # Objects
+ var $reason; # Text input
+ var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; # Checks
- # Check for database lock
- if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
+ private $watch = false;
+
+ public function __construct() {
+ parent::__construct( 'Movepage' );
}
- $target = isset( $par ) ? $par : $wgRequest->getVal( 'target' );
+ public function execute( $par ) {
+ global $wgUser, $wgOut, $wgRequest;
- // Yes, the use of getVal() and getText() is wanted, see bug 20365
- $oldTitleText = $wgRequest->getVal( 'wpOldTitle', $target );
- $newTitleText = $wgRequest->getText( 'wpNewTitle' );
+ # Check for database lock
+ if ( wfReadOnly() ) {
+ $wgOut->readOnlyPage();
+ return;
+ }
- $oldTitle = Title::newFromText( $oldTitleText );
- $newTitle = Title::newFromText( $newTitleText );
+ $this->setHeaders();
+ $this->outputHeader();
- if( is_null( $oldTitle ) ) {
- $wgOut->showErrorPage( 'notargettitle', 'notargettext' );
- return;
- }
- if( !$oldTitle->exists() ) {
- $wgOut->showErrorPage( 'nopagetitle', 'nopagetext' );
- return;
- }
+ $target = !is_null( $par ) ? $par : $wgRequest->getVal( 'target' );
- # Check rights
- $permErrors = $oldTitle->getUserPermissionsErrors( 'move', $wgUser );
- if( !empty( $permErrors ) ) {
- $wgOut->showPermissionsErrorPage( $permErrors );
- return;
- }
+ // Yes, the use of getVal() and getText() is wanted, see bug 20365
+ $oldTitleText = $wgRequest->getVal( 'wpOldTitle', $target );
+ $newTitleText = $wgRequest->getText( 'wpNewTitle' );
- $form = new MovePageForm( $oldTitle, $newTitle );
+ $this->oldTitle = Title::newFromText( $oldTitleText );
+ $this->newTitle = Title::newFromText( $newTitleText );
- if ( 'submit' == $action && $wgRequest->wasPosted()
- && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
- $form->doSubmit();
- } else {
- $form->showForm( '' );
- }
-}
+ if( is_null( $this->oldTitle ) ) {
+ $wgOut->showErrorPage( 'notargettitle', 'notargettext' );
+ return;
+ }
+ if( !$this->oldTitle->exists() ) {
+ $wgOut->showErrorPage( 'nopagetitle', 'nopagetext' );
+ return;
+ }
-/**
- * HTML form for Special:Movepage
- * @ingroup SpecialPage
- */
-class MovePageForm {
- var $oldTitle, $newTitle; # Objects
- var $reason; # Text input
- var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; # Checks
+ # Check rights
+ $permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $wgUser );
+ if( !empty( $permErrors ) ) {
+ $wgOut->showPermissionsErrorPage( $permErrors );
+ return;
+ }
- private $watch = false;
+ $def = !$wgRequest->wasPosted();
- function __construct( $oldTitle, $newTitle ) {
- global $wgRequest, $wgUser;
- $target = isset($par) ? $par : $wgRequest->getVal( 'target' );
- $this->oldTitle = $oldTitle;
- $this->newTitle = $newTitle;
$this->reason = $wgRequest->getText( 'wpReason' );
- if ( $wgRequest->wasPosted() ) {
- $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', false );
- $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', false );
- $this->leaveRedirect = $wgRequest->getBool( 'wpLeaveRedirect', false );
- } else {
- $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', true );
- $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', true );
- $this->leaveRedirect = $wgRequest->getBool( 'wpLeaveRedirect', true );
- }
+ $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', $def );
+ $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', $def );
+ $this->leaveRedirect = $wgRequest->getBool( 'wpLeaveRedirect', $def );
$this->moveSubpages = $wgRequest->getBool( 'wpMovesubpages', false );
$this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' );
$this->moveOverShared = $wgRequest->getBool( 'wpMoveOverSharedFile', false );
$this->watch = $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn();
+
+ if ( 'submit' == $wgRequest->getVal( 'action' ) && $wgRequest->wasPosted()
+ && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
+ $this->doSubmit();
+ } else {
+ $this->showForm( '' );
+ }
}
/**
* Show the form
- * @param mixed $err Error message. May either be a string message name or
- * array message name and parameters, like the second argument to
- * OutputPage::wrapWikiMsg().
+ *
+ * @param $err Mixed: error message. May either be a string message name or
+ * array message name and parameters, like the second argument to
+ * OutputPage::wrapWikiMsg().
*/
function showForm( $err ) {
global $wgOut, $wgUser, $wgContLang, $wgFixDoubleRedirects;
@@ -134,7 +145,8 @@ class MovePageForm {
if ($this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
$wgOut->wrapWikiMsg( "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>", 'moveuserpage-warning' );
}
- $wgOut->addWikiMsg( 'movepagetext' );
+ $wgOut->addWikiMsg( $wgFixDoubleRedirects ? 'movepagetext' :
+ 'movepagetext-noredirectfixer' );
$movepagebtn = wfMsg( 'movepagebtn' );
$submitVar = 'wpMove';
$confirm = false;
@@ -145,14 +157,14 @@ class MovePageForm {
$submitVar = 'wpMoveOverSharedFile';
$err = '';
}
-
+
$oldTalk = $this->oldTitle->getTalkPage();
$considerTalk = ( !$this->oldTitle->isTalkPage() && $oldTalk->exists() );
$dbr = wfGetDB( DB_SLAVE );
if ( $wgFixDoubleRedirects ) {
- $hasRedirects = $dbr->selectField( 'redirect', '1',
- array(
+ $hasRedirects = $dbr->selectField( 'redirect', '1',
+ array(
'rd_namespace' => $this->oldTitle->getNamespace(),
'rd_title' => $this->oldTitle->getDBkey(),
) , __METHOD__ );
@@ -164,7 +176,6 @@ class MovePageForm {
$wgOut->addWikiMsg( 'movepagetalktext' );
}
- $titleObj = SpecialPage::getTitleFor( 'Movepage' );
$token = htmlspecialchars( $wgUser->editToken() );
if ( !empty($err) ) {
@@ -174,7 +185,7 @@ class MovePageForm {
$errMsg = "<p><strong class=\"error\">$hookErr</strong></p>\n";
$wgOut->addHTML( $errMsg );
} else {
- $wgOut->wrapWikiMsg( '<p><strong class="error">$1</strong></p>', $err );
+ $wgOut->wrapWikiMsg( "<p><strong class=\"error\">\n$1\n</strong></p>", $err );
}
}
@@ -195,7 +206,7 @@ class MovePageForm {
}
$wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) .
+ Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'move-page-legend' ) ) .
Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-movepage-table' ) ) .
@@ -213,7 +224,7 @@ class MovePageForm {
"</td>
<td class='mw-input'>" .
Xml::input( 'wpNewTitle', 40, $wgContLang->recodeForEdit( $newTitle->getPrefixedText() ), array( 'type' => 'text', 'id' => 'wpNewTitle' ) ) .
- Xml::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
+ Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
"</td>
</tr>
<tr>
@@ -243,7 +254,7 @@ class MovePageForm {
<tr>
<td></td>
<td class='mw-input' >" .
- Xml::checkLabel( wfMsg( 'move-leave-redirect' ), 'wpLeaveRedirect',
+ Xml::checkLabel( wfMsg( 'move-leave-redirect' ), 'wpLeaveRedirect',
'wpLeaveRedirect', $this->leaveRedirect ) .
"</td>
</tr>"
@@ -255,7 +266,7 @@ class MovePageForm {
<tr>
<td></td>
<td class='mw-input' >" .
- Xml::checkLabel( wfMsg( 'fix-double-redirects' ), 'wpFixRedirects',
+ Xml::checkLabel( wfMsg( 'fix-double-redirects' ), 'wpFixRedirects',
'wpFixRedirects', $this->fixRedirects ) .
"</td>
</tr>"
@@ -277,7 +288,7 @@ class MovePageForm {
# move and we aren't moving the talk page.
$this->moveSubpages && ($this->oldTitle->hasSubpages() || $this->moveTalk),
array( 'id' => 'wpMovesubpages' )
- ) . '&nbsp;' .
+ ) . '&#160;' .
Xml::tags( 'label', array( 'for' => 'wpMovesubpages' ),
wfMsgExt(
( $this->oldTitle->hasSubpages()
@@ -294,7 +305,7 @@ class MovePageForm {
);
}
- $watchChecked = $wgUser->isLoggedIn() && ($this->watch || $wgUser->getBoolOption( 'watchmoves' )
+ $watchChecked = $wgUser->isLoggedIn() && ($this->watch || $wgUser->getBoolOption( 'watchmoves' )
|| $this->oldTitle->userIsWatching());
# Don't allow watching if user is not logged in
if( $wgUser->isLoggedIn() ) {
@@ -307,16 +318,16 @@ class MovePageForm {
</tr>");
}
- $wgOut->addHTML( "
+ $wgOut->addHTML( "
{$confirm}
<tr>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-submit'>" .
Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ) .
- Xml::hidden( 'wpEditToken', $token ) .
+ Html::hidden( 'wpEditToken', $token ) .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' ) .
"\n"
@@ -328,7 +339,7 @@ class MovePageForm {
}
function doSubmit() {
- global $wgOut, $wgUser, $wgRequest, $wgMaximumMovedPages, $wgLang;
+ global $wgOut, $wgUser, $wgMaximumMovedPages, $wgLang;
global $wgFixDoubleRedirects;
if ( $wgUser->pingLimiter( 'move' ) ) {
@@ -346,7 +357,7 @@ class MovePageForm {
# Disallow deletions of big articles
$bigHistory = $article->isBigDeletion();
if( $bigHistory && !$nt->userCan( 'bigdelete' ) ) {
- global $wgLang, $wgDeleteRevisionsLimit;
+ global $wgDeleteRevisionsLimit;
$this->showForm( array('delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
return;
}
@@ -368,16 +379,16 @@ class MovePageForm {
}
# Show a warning if the target file exists on a shared repo
- if ( $nt->getNamespace() == NS_FILE
+ if ( $nt->getNamespace() == NS_FILE
&& !( $this->moveOverShared && $wgUser->isAllowed( 'reupload-shared' ) )
- && !RepoGroup::singleton()->getLocalRepo()->findFile( $nt )
+ && !RepoGroup::singleton()->getLocalRepo()->findFile( $nt )
&& wfFindFile( $nt ) )
{
$this->showForm( array('file-exists-sharedrepo') );
return;
-
+
}
-
+
if ( $wgUser->isAllowed( 'suppressredirect' ) ) {
$createRedirect = $this->leaveRedirect;
} else {
@@ -433,7 +444,7 @@ class MovePageForm {
# would mean that you couldn't move them back in one operation, which
# is bad. FIXME: A specific error message should be given in this
# case.
-
+
// FIXME: Use Title::moveSubpages() here
$dbr = wfGetDB( DB_MASTER );
if( $this->moveSubpages && (
@@ -547,8 +558,8 @@ class MovePageForm {
$wgUser->removeWatch( $ot );
$wgUser->removeWatch( $nt );
}
-
- # Re-clear the file redirect cache, which may have been polluted by
+
+ # Re-clear the file redirect cache, which may have been polluted by
# parsing in messages above. See CR r56745.
# FIXME: needs a more robust solution inside FileRepo.
if( $ot->getNamespace() == NS_FILE ) {
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
index a39b56ee..cecd7dfd 100644
--- a/includes/specials/SpecialNewimages.php
+++ b/includes/specials/SpecialNewimages.php
@@ -1,10 +1,29 @@
<?php
/**
+ * Implements Special:Newimages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
- * FIXME: this code is crap, should use Pager and Database::select().
*/
+/**
+ * @todo FIXME: this code is crap, should use Pager and Database::select().
+ */
function wfSpecialNewimages( $par, $specialPage ) {
global $wgUser, $wgOut, $wgLang, $wgRequest, $wgMiserMode;
@@ -49,16 +68,14 @@ function wfSpecialNewimages( $par, $specialPage ) {
} else {
$ts = false;
}
- $dbr->freeResult( $res );
- $sql = '';
# If we were clever, we'd use this to cache.
$latestTimestamp = wfTimestamp( TS_MW, $ts );
# Hardcode this for now.
$limit = 48;
-
- if ( $parval = intval( $par ) ) {
+ $parval = intval( $par );
+ if ( $parval ) {
if ( $parval <= $limit && $parval > 0 ) {
$limit = $parval;
}
@@ -75,14 +92,16 @@ function wfSpecialNewimages( $par, $specialPage ) {
}
$invertSort = false;
- if( $until = $wgRequest->getVal( 'until' ) ) {
+ $until = $wgRequest->getVal( 'until' );
+ if( $until ) {
$where[] = "img_timestamp < '" . $dbr->timestamp( $until ) . "'";
}
- if( $from = $wgRequest->getVal( 'from' ) ) {
+ $from = $wgRequest->getVal( 'from' );
+ if( $from ) {
$where[] = "img_timestamp >= '" . $dbr->timestamp( $from ) . "'";
$invertSort = true;
}
- $sql='SELECT img_size, img_name, img_user, img_user_text,'.
+ $sql = 'SELECT img_size, img_name, img_user, img_user_text,'.
"img_description,img_timestamp FROM $image";
if( $hidebotsql ) {
@@ -100,14 +119,13 @@ function wfSpecialNewimages( $par, $specialPage ) {
* We have to flip things around to get the last N after a certain date
*/
$images = array();
- while ( $s = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $s ) {
if( $invertSort ) {
array_unshift( $images, $s );
} else {
array_push( $images, $s );
}
}
- $dbr->freeResult( $res );
$gallery = new ImageGallery();
$firstTimestamp = null;
@@ -214,9 +232,9 @@ function wfSpecialNewimages( $par, $specialPage ) {
}
$nextLink = wfMsgExt( 'pager-older-n', $opts, $wgLang->formatNum( $limit ) );
- if( $shownImages > $limit && $lastTimestamp ) {
+ if( $invertSort || ( $shownImages > $limit && $lastTimestamp ) ) {
$query = array_merge(
- array( 'until' => $lastTimestamp ),
+ array( 'until' => ( $lastTimestamp ? $lastTimestamp : "" ) ),
$botpar,
$searchpar
);
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index 903ddab0..3235436a 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -1,10 +1,32 @@
<?php
+/**
+ * Implements Special:Newpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
/**
- * implements Special:Newpages
+ * A special page that list newly created pages
+ *
* @ingroup SpecialPage
*/
-class SpecialNewpages extends SpecialPage {
+class SpecialNewpages extends IncludableSpecialPage {
// Stored objects
protected $opts, $skin;
@@ -14,7 +36,6 @@ class SpecialNewpages extends SpecialPage {
public function __construct() {
parent::__construct( 'Newpages' );
- $this->includable( true );
}
protected function setup( $par ) {
@@ -85,11 +106,11 @@ class SpecialNewpages extends SpecialPage {
/**
* Show a form for filtering namespace and username
*
- * @param string $par
- * @return string
+ * @param $par String
+ * @return String
*/
public function execute( $par ) {
- global $wgLang, $wgOut;
+ global $wgOut;
$this->setHeaders();
$this->outputHeader();
@@ -136,6 +157,7 @@ class SpecialNewpages extends SpecialPage {
);
// Disable some if needed
+ # FIXME: throws E_NOTICEs if not set; and doesn't obey hooks etc
if ( $wgGroupPermissions['*']['createpage'] !== true )
unset($filters['hideliu']);
@@ -174,7 +196,7 @@ class SpecialNewpages extends SpecialPage {
// Store query values in hidden fields so that form submission doesn't lose them
$hidden = array();
foreach ( $this->opts->getUnconsumedValues() as $key => $value ) {
- $hidden[] = Xml::hidden( $key, $value );
+ $hidden[] = Html::hidden( $key, $value );
}
$hidden = implode( "\n", $hidden );
@@ -183,7 +205,7 @@ class SpecialNewpages extends SpecialPage {
list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter;
$form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) .
- Xml::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
Xml::fieldset( wfMsg( 'newpages' ) ) .
Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) .
"<tr>
@@ -239,9 +261,8 @@ class SpecialNewpages extends SpecialPage {
/**
* Format a row, providing the timestamp, links to the page/history, size, user links, and a comment
*
- * @param $skin Skin to use
* @param $result Result row
- * @return string
+ * @return String
*/
public function formatRow( $result ) {
global $wgLang, $wgContLang;
@@ -251,7 +272,9 @@ class SpecialNewpages extends SpecialPage {
$dm = $wgContLang->getDirMark();
$title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title );
- $time = htmlspecialchars( $wgLang->timeAndDate( $result->rc_timestamp, true ) );
+ $time = Html::element( 'span', array( 'class' => 'mw-newpages-time' ),
+ $wgLang->timeAndDate( $result->rc_timestamp, true )
+ );
$query = array( 'redirect' => 'no' );
@@ -261,38 +284,53 @@ class SpecialNewpages extends SpecialPage {
$plink = $this->skin->linkKnown(
$title,
null,
- array(),
- $query
+ array( 'class' => 'mw-newpages-pagename' ),
+ $query,
+ array( 'known' ) // Set explicitly to avoid the default of 'known','noclasses'. This breaks the colouration for stubs
);
- $hist = $this->skin->linkKnown(
+ $histLink = $this->skin->linkKnown(
$title,
wfMsgHtml( 'hist' ),
array(),
array( 'action' => 'history' )
);
- $length = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->length ) );
+ $hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ), wfMsg( 'parentheses', $histLink ) );
+
+ $length = Html::rawElement( 'span', array( 'class' => 'mw-newpages-length' ),
+ '[' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->length ) ) .
+ ']'
+ );
$ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' .
$this->skin->userToolLinks( $result->rc_user, $result->rc_user_text );
$comment = $this->skin->commentBlock( $result->rc_comment );
- if ( $this->patrollable( $result ) )
+ if ( $this->patrollable( $result ) ) {
$classes[] = 'not-patrolled';
+ }
- # Tags, if any.
- list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' );
- $classes = array_merge( $classes, $newClasses );
+ # Add a class for zero byte pages
+ if ( $result->length == 0 ) {
+ $classes[] = 'mw-newpages-zero-byte-page';
+ }
+
+ # Tags, if any. check for including due to bug 23293
+ if ( !$this->including() ) {
+ list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' );
+ $classes = array_merge( $classes, $newClasses );
+ } else {
+ $tagDisplay = '';
+ }
$css = count($classes) ? ' class="'.implode( " ", $classes).'"' : '';
- return "<li{$css}>{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n";
+ return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n";
}
/**
* Should a specific result row provide "patrollable" links?
*
* @param $result Result row
- * @return bool
+ * @return Boolean
*/
protected function patrollable( $result ) {
global $wgUser;
@@ -301,19 +339,18 @@ class SpecialNewpages extends SpecialPage {
/**
* Output a subscription feed listing recent edits to this page.
- * @param string $type
+ *
+ * @param $type String
*/
protected function feed( $type ) {
- global $wgFeed, $wgFeedClasses, $wgFeedLimit;
+ global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgOut;
if ( !$wgFeed ) {
- global $wgOut;
$wgOut->addWikiMsg( 'feed-unavailable' );
return;
}
if( !isset( $wgFeedClasses[$type] ) ) {
- global $wgOut;
$wgOut->addWikiMsg( 'feed-invalid' );
return;
}
@@ -329,7 +366,7 @@ class SpecialNewpages extends SpecialPage {
$feed->outHeader();
if( $pager->getNumRows() > 0 ) {
- while( $row = $pager->mResult->fetchObject() ) {
+ foreach ( $pager->mResult as $row ) {
$feed->outItem( $this->feedItem( $row ) );
}
}
@@ -337,10 +374,10 @@ class SpecialNewpages extends SpecialPage {
}
protected function feedTitle() {
- global $wgContLanguageCode, $wgSitename;
+ global $wgLanguageCode, $wgSitename;
$page = SpecialPage::getPage( 'Newpages' );
$desc = $page->getDescription();
- return "$wgSitename - $desc [$wgContLanguageCode]";
+ return "$wgSitename - $desc [$wgLanguageCode]";
}
protected function feedItem( $row ) {
@@ -434,6 +471,9 @@ class NewPagesPager extends ReverseChronologicalPager {
if ( $this->opts->getValue( 'hideredirs' ) ) {
$conds['page_is_redirect'] = 0;
}
+
+ // Allow changes to the New Pages query
+ wfRunHooks('SpecialNewpagesConditions', array(&$this, $this->opts, &$conds));
$info = array(
'tables' => array( 'recentchanges', 'page' ),
@@ -471,7 +511,7 @@ class NewPagesPager extends ReverseChronologicalPager {
function getStartBody() {
# Do a batch existence check on pages
$linkBatch = new LinkBatch();
- while( $row = $this->mResult->fetchObject() ) {
+ foreach ( $this->mResult as $row ) {
$linkBatch->add( NS_USER, $row->rc_user_text );
$linkBatch->add( NS_USER_TALK, $row->rc_user_text );
$linkBatch->add( $row->rc_namespace, $row->rc_title );
diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php
index 88b90bc3..375cefdf 100644
--- a/includes/specials/SpecialPopularpages.php
+++ b/includes/specials/SpecialPopularpages.php
@@ -1,11 +1,29 @@
<?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
*/
/**
- * implements Special:Popularpages
+ * A special page that list most viewed pages
+ *
* @ingroup SpecialPage
*/
class PopularPagesPage extends QueryPage {
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index 4c8bbb09..e63aeee6 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -1,5 +1,31 @@
<?php
-
+/**
+ * Implements Special: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
+ * @ingroup SpecialPage
+ */
+
+/**
+ * A special page that allows users to change their preferences
+ *
+ * @ingroup SpecialPage
+ */
class SpecialPreferences extends SpecialPage {
function __construct() {
parent::__construct( 'Preferences' );
@@ -25,18 +51,19 @@ class SpecialPreferences extends SpecialPage {
$this->showResetForm();
return;
}
-
- $wgOut->addScriptFile( 'prefs.js' );
+
+ $wgOut->addModules( 'mediawiki.legacy.prefs' );
+ $wgOut->addModules( 'mediawiki.special.preferences' );
if ( $wgRequest->getCheck( 'success' ) ) {
$wgOut->wrapWikiMsg(
- '<div class="successbox"><strong>$1</strong></div><div id="mw-pref-clear"></div>',
+ "<div class=\"successbox\"><strong>\n$1\n</strong></div><div id=\"mw-pref-clear\"></div>",
'savedprefs'
);
}
if ( $wgRequest->getCheck( 'eauth' ) ) {
- $wgOut->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1</div>",
+ $wgOut->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>",
'eauthentsent', $wgUser->getName() );
}
diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php
index 8b5f0c93..09e7734c 100644
--- a/includes/specials/SpecialPrefixindex.php
+++ b/includes/specials/SpecialPrefixindex.php
@@ -1,7 +1,29 @@
<?php
+/**
+ * Implements Special:Prefixindex
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
/**
- * implements Special:Prefixindex
+ * Implements Special:Prefixindex
+ *
* @ingroup SpecialPage
*/
class SpecialPrefixindex extends SpecialAllpages {
@@ -22,7 +44,7 @@ class SpecialPrefixindex extends SpecialAllpages {
$this->outputHeader();
# GET values
- $from = $wgRequest->getVal( 'from' );
+ $from = $wgRequest->getVal( 'from', '' );
$prefix = $wgRequest->getVal( 'prefix', '' );
$namespace = $wgRequest->getInt( 'namespace' );
$namespaces = $wgContLang->getNamespaces();
@@ -32,12 +54,17 @@ class SpecialPrefixindex extends SpecialAllpages {
: wfMsg( 'prefixindex' )
);
+ $showme = '';
if( isset( $par ) ){
- $this->showPrefixChunk( $namespace, $par, $from );
- } elseif( isset( $prefix ) ){
- $this->showPrefixChunk( $namespace, $prefix, $from );
- } elseif( isset( $from ) ){
- $this->showPrefixChunk( $namespace, $from, $from );
+ $showme = $par;
+ } elseif( $prefix != '' ){
+ $showme = $prefix;
+ } elseif( $from != '' ){
+ // For back-compat with Special:Allpages
+ $showme = $from;
+ }
+ if ($showme != '' || $namespace) {
+ $this->showPrefixChunk( $namespace, $showme, $from );
} else {
$wgOut->addHTML( $this->namespacePrefixForm( $namespace, null ) );
}
@@ -45,8 +72,8 @@ class SpecialPrefixindex extends SpecialAllpages {
/**
* HTML for the top form
- * @param integer $namespace A namespace constant (default NS_MAIN).
- * @param string $from dbKey we are starting listing at.
+ * @param $namespace Integer: a namespace constant (default NS_MAIN).
+ * @param $from String: dbKey we are starting listing at.
*/
function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ) {
global $wgScript;
@@ -54,7 +81,7 @@ class SpecialPrefixindex extends SpecialAllpages {
$out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
$out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- $out .= Xml::hidden( 'title', $t->getPrefixedText() );
+ $out .= Html::hidden( 'title', $t->getPrefixedText() );
$out .= Xml::openElement( 'fieldset' );
$out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) );
$out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
@@ -83,8 +110,9 @@ class SpecialPrefixindex extends SpecialAllpages {
}
/**
- * @param integer $namespace (Default NS_MAIN)
- * @param string $from list all pages from this name (default FALSE)
+ * @param $namespace Integer, default NS_MAIN
+ * @param $prefix String
+ * @param $from String: list all pages from this name (default FALSE)
*/
function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) {
global $wgOut, $wgUser, $wgContLang, $wgLang;
@@ -105,7 +133,7 @@ class SpecialPrefixindex extends SpecialAllpages {
$namespace = NS_MAIN;
} else {
list( $namespace, $prefixKey, $prefix ) = $prefixList;
- list( /* $fromNs */, $fromKey, $from ) = $fromList;
+ list( /* $fromNS */, $fromKey, ) = $fromList;
### FIXME: should complain if $fromNs != $namespace
diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php
index 8229770c..c676aa00 100644
--- a/includes/specials/SpecialProtectedpages.php
+++ b/includes/specials/SpecialProtectedpages.php
@@ -1,24 +1,45 @@
<?php
/**
+ * Implements Special:Protectedpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * @todo document
+ * A special page that lists protected pages
+ *
* @ingroup SpecialPage
*/
-class ProtectedPagesForm {
+class SpecialProtectedpages extends SpecialPage {
protected $IdLevel = 'level';
protected $IdType = 'type';
- public function showList( $msg = '' ) {
+ public function __construct() {
+ parent::__construct( 'Protectedpages' );
+ }
+
+ public function execute( $par ) {
global $wgOut, $wgRequest;
- if( $msg != "" ) {
- $wgOut->setSubtitle( $msg );
- }
+ $this->setHeaders();
+ $this->outputHeader();
// Purge expired entries on one in every 10 queries
if( !mt_rand( 0, 10 ) ) {
@@ -77,7 +98,6 @@ class ProtectedPagesForm {
$description_items[] = wfMsg( 'protect-summary-cascade' );
}
- $expiry_description = '';
$stxt = '';
if( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) {
@@ -123,14 +143,14 @@ class ProtectedPagesForm {
}
/**
- * @param $namespace int
- * @param $type string
- * @param $level string
- * @param $minsize int
- * @param $indefOnly bool
- * @param $cascadeOnly bool
- * @return string Input form
- * @private
+ * @param $namespace Integer
+ * @param $type String: restriction type
+ * @param $level String: restriction level
+ * @param $sizetype String: "min" or "max"
+ * @param $size Integer
+ * @param $indefOnly Boolean: only indefinie protection
+ * @param $cascadeOnly Boolean: only cascading protection
+ * @return String: input form
*/
protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly, $cascadeOnly ) {
global $wgScript;
@@ -138,17 +158,17 @@ class ProtectedPagesForm {
return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), wfMsg( 'protectedpages' ) ) .
- Xml::hidden( 'title', $title->getPrefixedDBkey() ) . "\n" .
- $this->getNamespaceMenu( $namespace ) . "&nbsp;\n" .
- $this->getTypeMenu( $type ) . "&nbsp;\n" .
- $this->getLevelMenu( $level ) . "&nbsp;\n" .
+ Html::hidden( 'title', $title->getPrefixedDBkey() ) . "\n" .
+ $this->getNamespaceMenu( $namespace ) . "&#160;\n" .
+ $this->getTypeMenu( $type ) . "&#160;\n" .
+ $this->getLevelMenu( $level ) . "&#160;\n" .
"<br /><span style='white-space: nowrap'>" .
- $this->getExpiryCheck( $indefOnly ) . "&nbsp;\n" .
- $this->getCascadeCheck( $cascadeOnly ) . "&nbsp;\n" .
+ $this->getExpiryCheck( $indefOnly ) . "&#160;\n" .
+ $this->getCascadeCheck( $cascadeOnly ) . "&#160;\n" .
"</span><br /><span style='white-space: nowrap'>" .
- $this->getSizeLimit( $sizetype, $size ) . "&nbsp;\n" .
+ $this->getSizeLimit( $sizetype, $size ) . "&#160;\n" .
"</span>" .
- "&nbsp;" . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
+ "&#160;" . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' );
}
@@ -157,12 +177,12 @@ class ProtectedPagesForm {
* Prepare the namespace filter drop-down; standard namespace
* selector, sans the MediaWiki namespace
*
- * @param mixed $namespace Pre-select namespace
- * @return string
+ * @param $namespace Mixed: pre-select namespace
+ * @return String
*/
protected function getNamespaceMenu( $namespace = null ) {
return "<span style='white-space: nowrap'>" .
- Xml::label( wfMsg( 'namespace' ), 'namespace' ) . '&nbsp;'
+ Xml::label( wfMsg( 'namespace' ), 'namespace' ) . '&#160;'
. Xml::namespaceSelector( $namespace, '' ) . "</span>";
}
@@ -190,11 +210,11 @@ class ProtectedPagesForm {
return
Xml::radioLabel( wfMsg('minimum-size'), 'sizetype', 'min', 'wpmin', !$max ) .
- '&nbsp;' .
+ '&#160;' .
Xml::radioLabel( wfMsg('maximum-size'), 'sizetype', 'max', 'wpmax', $max ) .
- '&nbsp;' .
+ '&#160;' .
Xml::input( 'size', 9, $size, array( 'id' => 'wpsize' ) ) .
- '&nbsp;' .
+ '&#160;' .
Xml::label( wfMsg('pagesize'), 'wpsize' );
}
@@ -204,13 +224,11 @@ class ProtectedPagesForm {
* @return string Formatted HTML
*/
protected function getTypeMenu( $pr_type ) {
- global $wgRestrictionTypes;
-
$m = array(); // Temporary array
$options = array();
// First pass to load the log names
- foreach( $wgRestrictionTypes as $type ) {
+ foreach( Title::getFilteredRestrictionTypes( true ) as $type ) {
$text = wfMsg("restriction-$type");
$m[$text] = $type;
}
@@ -222,7 +240,7 @@ class ProtectedPagesForm {
}
return "<span style='white-space: nowrap'>" .
- Xml::label( wfMsg('restriction-type') , $this->IdType ) . '&nbsp;' .
+ Xml::label( wfMsg('restriction-type') , $this->IdType ) . '&#160;' .
Xml::tags( 'select',
array( 'id' => $this->IdType, 'name' => $this->IdType ),
implode( "\n", $options ) ) . "</span>";
@@ -288,7 +306,7 @@ class ProtectedPagesPager extends AlphabeticPager {
function getStartBody() {
# Do a link batch query
$lb = new LinkBatch;
- while( $row = $this->mResult->fetchObject() ) {
+ foreach ( $this->mResult as $row ) {
$lb->add( $row->page_namespace, $row->page_title );
}
$lb->execute();
@@ -334,11 +352,3 @@ class ProtectedPagesPager extends AlphabeticPager {
return 'pr_id';
}
}
-
-/**
- * Constructor
- */
-function wfSpecialProtectedpages() {
- $ppForm = new ProtectedPagesForm();
- $ppForm->showList();
-}
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index d65b3f79..5b18d87f 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -1,24 +1,45 @@
<?php
/**
+ * Implements Special:Protectedtitles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * @todo document
+ * A special page that list protected titles from creation
+ *
* @ingroup SpecialPage
*/
-class ProtectedTitlesForm {
+class SpecialProtectedtitles extends SpecialPage {
protected $IdLevel = 'level';
protected $IdType = 'type';
- function showList( $msg = '' ) {
+ public function __construct() {
+ parent::__construct( 'Protectedtitles' );
+ }
+
+ function execute( $par ) {
global $wgOut, $wgRequest;
- if ( $msg != "" ) {
- $wgOut->setSubtitle( $msg );
- }
+ $this->setHeaders();
+ $this->outputHeader();
// Purge expired entries on one in every 10 queries
if ( !mt_rand( 0, 10 ) ) {
@@ -33,7 +54,7 @@ class ProtectedTitlesForm {
$pager = new ProtectedTitlesPager( $this, array(), $type, $level, $NS, $sizetype, $size );
- $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size ) );
+ $wgOut->addHTML( $this->showOptions( $NS, $type, $level ) );
if ( $pager->getNumRows() ) {
$s = $pager->getNavigationBar();
@@ -51,7 +72,7 @@ class ProtectedTitlesForm {
* Callback function to output a restriction
*/
function formatRow( $row ) {
- global $wgUser, $wgLang, $wgContLang;
+ global $wgUser, $wgLang;
wfProfileIn( __METHOD__ );
@@ -69,7 +90,7 @@ class ProtectedTitlesForm {
$description_items[] = $protType;
- $expiry_description = ''; $stxt = '';
+ $stxt = '';
if ( $row->pt_expiry != 'infinity' && strlen($row->pt_expiry) ) {
$expiry = Block::decodeExpiry( $row->pt_expiry );
@@ -85,13 +106,12 @@ class ProtectedTitlesForm {
}
/**
- * @param $namespace int
+ * @param $namespace Integer:
* @param $type string
* @param $level string
- * @param $minsize int
* @private
*/
- function showOptions( $namespace, $type='edit', $level, $sizetype, $size ) {
+ function showOptions( $namespace, $type='edit', $level ) {
global $wgScript;
$action = htmlspecialchars( $wgScript );
$title = SpecialPage::getTitleFor( 'Protectedtitles' );
@@ -99,10 +119,10 @@ class ProtectedTitlesForm {
return "<form action=\"$action\" method=\"get\">\n" .
'<fieldset>' .
Xml::element( 'legend', array(), wfMsg( 'protectedtitles' ) ) .
- Xml::hidden( 'title', $special ) . "&nbsp;\n" .
- $this->getNamespaceMenu( $namespace ) . "&nbsp;\n" .
- $this->getLevelMenu( $level ) . "&nbsp;\n" .
- "&nbsp;" . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
+ Html::hidden( 'title', $special ) . "&#160;\n" .
+ $this->getNamespaceMenu( $namespace ) . "&#160;\n" .
+ $this->getLevelMenu( $level ) . "&#160;\n" .
+ "&#160;" . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
"</fieldset></form>";
}
@@ -110,12 +130,12 @@ class ProtectedTitlesForm {
* Prepare the namespace filter drop-down; standard namespace
* selector, sans the MediaWiki namespace
*
- * @param mixed $namespace Pre-select namespace
+ * @param $namespace Mixed: pre-select namespace
* @return string
*/
function getNamespaceMenu( $namespace = null ) {
return Xml::label( wfMsg( 'namespace' ), 'namespace' )
- . '&nbsp;'
+ . '&#160;'
. Xml::namespaceSelector( $namespace, '' );
}
@@ -147,7 +167,7 @@ class ProtectedTitlesForm {
}
return
- Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . '&nbsp;' .
+ Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . '&#160;' .
Xml::tags( 'select',
array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ),
implode( "\n", $options ) );
@@ -158,7 +178,7 @@ class ProtectedTitlesForm {
* @todo document
* @ingroup Pager
*/
-class ProtectedtitlesPager extends AlphabeticPager {
+class ProtectedTitlesPager extends AlphabeticPager {
public $mForm, $mConds;
function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0 ) {
@@ -176,7 +196,7 @@ class ProtectedtitlesPager extends AlphabeticPager {
$this->mResult->seek( 0 );
$lb = new LinkBatch;
- while ( $row = $this->mResult->fetchObject() ) {
+ foreach ( $this->mResult as $row ) {
$lb->add( $row->pt_namespace, $row->pt_title );
}
@@ -208,12 +228,3 @@ class ProtectedtitlesPager extends AlphabeticPager {
}
}
-/**
- * Constructor
- */
-function wfSpecialProtectedtitles() {
-
- $ppForm = new ProtectedTitlesForm();
-
- $ppForm->showList();
-}
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index fd3f17f2..6299f384 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -1,11 +1,31 @@
<?php
+/**
+ * Implements Special:Randompage
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>, Ilmari Karonen
+ */
/**
* Special page to direct the user to a random page
*
* @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>, Ilmari Karonen
- * @license GNU General Public Licence 2.0 or later
*/
class RandomPage extends SpecialPage {
private $namespaces; // namespaces to select pages from
@@ -33,7 +53,7 @@ class RandomPage extends SpecialPage {
}
public function execute( $par ) {
- global $wgOut, $wgContLang;
+ global $wgOut, $wgContLang, $wgRequest;
if ($par) {
$this->setNamespace( $wgContLang->getNsIndex( $par ) );
@@ -48,7 +68,9 @@ class RandomPage extends SpecialPage {
return;
}
- $query = $this->isRedirect() ? 'redirect=no' : '';
+ $redirectParam = $this->isRedirect() ? array( 'redirect' => 'no' ) : array();
+ $query = array_merge( $wgRequest->getValues(), $redirectParam );
+ unset( $query['title'] );
$wgOut->redirect( $title->getFullUrl( $query ) );
}
diff --git a/includes/specials/SpecialRandomredirect.php b/includes/specials/SpecialRandomredirect.php
index 28cb2aae..88c81b31 100644
--- a/includes/specials/SpecialRandomredirect.php
+++ b/includes/specials/SpecialRandomredirect.php
@@ -1,11 +1,31 @@
<?php
+/**
+ * Implements Special:Randomredirect
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>, Ilmari Karonen
+ */
/**
* Special page to direct the user to a random redirect page (minus the second redirect)
*
* @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>, Ilmari Karonen
- * @license GNU General Public Licence 2.0 or later
*/
class SpecialRandomredirect extends RandomPage {
function __construct(){
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index 283eeaf4..c012beca 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -1,15 +1,36 @@
<?php
-
/**
* Implements Special:Recentchanges
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
* @ingroup SpecialPage
*/
-class SpecialRecentChanges extends SpecialPage {
+
+/**
+ * A special page that lists last changes made to the wiki
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialRecentChanges extends IncludableSpecialPage {
var $rcOptions, $rcSubpage;
- public function __construct() {
- parent::__construct( 'Recentchanges' );
- $this->includable( true );
+ public function __construct( $name = 'Recentchanges' ) {
+ parent::__construct( $name );
}
/**
@@ -91,7 +112,7 @@ class SpecialRecentChanges extends SpecialPage {
/**
* Main execution point
*
- * @param $subpage string
+ * @param $subpage String
*/
public function execute( $subpage ) {
global $wgRequest, $wgOut;
@@ -142,7 +163,7 @@ class SpecialRecentChanges extends SpecialPage {
/**
* Return an array with a ChangesFeed object and ChannelFeed object
*
- * @return array
+ * @return Array
*/
public function getFeedObject( $feedFormat ){
$changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' );
@@ -186,7 +207,7 @@ class SpecialRecentChanges extends SpecialPage {
* update the timestamp
*
* @param $feedFormat String
- * @return string or false
+ * @return String or false
*/
public function checkLastModified( $feedFormat ) {
global $wgUseRCPatrol, $wgOut;
@@ -275,7 +296,7 @@ class SpecialRecentChanges extends SpecialPage {
/**
* Process the query
*
- * @param $conds array
+ * @param $conds Array
* @param $opts FormOptions
* @return database result or false (for Recentchangeslinked only)
*/
@@ -290,6 +311,7 @@ class SpecialRecentChanges extends SpecialPage {
$dbr = wfGetDB( DB_SLAVE );
$limit = $opts['limit'];
$namespace = $opts['namespace'];
+ $select = '*';
$invert = $opts['invert'];
// JOIN on watchlist for users
@@ -302,14 +324,17 @@ class SpecialRecentChanges extends SpecialPage {
$tables[] = 'page';
$join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id');
}
- // Tag stuff.
- $fields = array();
- // Fields are * in this case, so let the function modify an empty array to keep it happy.
- ChangeTags::modifyDisplayQuery(
- $tables, $fields, $conds, $join_conds, $query_options, $opts['tagfilter']
- );
+ if ( !$this->including() ) {
+ // Tag stuff.
+ // Doesn't work when transcluding. See bug 23293
+ $fields = array();
+ // Fields are * in this case, so let the function modify an empty array to keep it happy.
+ ChangeTags::modifyDisplayQuery(
+ $tables, $fields, $conds, $join_conds, $query_options, $opts['tagfilter']
+ );
+ }
- if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options ) ) )
+ if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$select ) ) )
return false;
// Don't use the new_namespace_time timestamp index if:
@@ -317,8 +342,8 @@ class SpecialRecentChanges extends SpecialPage {
// (b) We want all pages NOT in a certain namespaces (inverted)
// (c) There is a tag to filter on (use tag index instead)
// (d) UNION + sort/limit is not an option for the DBMS
- if( is_null($namespace)
- || $invert
+ if( is_null( $namespace )
+ || ( $invert && !is_null( $namespace ) )
|| $opts['tagfilter'] != ''
|| !$dbr->unionSupportsOrderAndLimit() )
{
@@ -329,7 +354,7 @@ class SpecialRecentChanges extends SpecialPage {
// We have a new_namespace_time index! UNION over new=(0,1) and sort result set!
} else {
// New pages
- $sqlNew = $dbr->selectSQLText( $tables, '*',
+ $sqlNew = $dbr->selectSQLText( $tables, $select,
array( 'rc_new' => 1 ) + $conds,
__METHOD__,
array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
@@ -354,7 +379,7 @@ class SpecialRecentChanges extends SpecialPage {
/**
* Send output to $wgOut, only called if not used feeds
*
- * @param $rows array of database rows
+ * @param $rows Array of database rows
* @param $opts FormOptions
*/
public function webOutput( $rows, $opts ) {
@@ -437,7 +462,8 @@ class SpecialRecentChanges extends SpecialPage {
$defaults = $opts->getAllValues();
$nondefaults = $opts->getChangedValues();
- $opts->consumeValues( array( 'namespace', 'invert', 'tagfilter' ) );
+ $opts->consumeValues( array( 'namespace', 'invert', 'tagfilter',
+ 'categories', 'categories_any' ) );
$panel = array();
$panel[] = $this->optionsPanel( $defaults, $nondefaults );
@@ -467,11 +493,11 @@ class SpecialRecentChanges extends SpecialPage {
$unconsumed = $opts->getUnconsumedValues();
foreach( $unconsumed as $key => $value ) {
- $out .= Xml::hidden( $key, $value );
+ $out .= Html::hidden( $key, $value );
}
$t = $this->getTitle();
- $out .= Xml::hidden( 'title', $t->getPrefixedText() );
+ $out .= Html::hidden( 'title', $t->getPrefixedText() );
$form = Xml::tags( 'form', array( 'action' => $wgScript ), $out );
$panel[] = $form;
$panelString = implode( "\n", $panel );
@@ -480,8 +506,6 @@ class SpecialRecentChanges extends SpecialPage {
Xml::fieldset( wfMsg( 'recentchanges-legend' ), $panelString, array( 'class' => 'rcoptions' ) )
);
- $wgOut->addHTML( ChangesList::flagLegend() );
-
$this->setBottomText( $wgOut, $opts );
}
@@ -489,7 +513,7 @@ class SpecialRecentChanges extends SpecialPage {
* Get options to be displayed in a form
*
* @param $opts FormOptions
- * @return array
+ * @return Array
*/
function getExtraOptions( $opts ){
$extraOpts = array();
@@ -531,7 +555,7 @@ class SpecialRecentChanges extends SpecialPage {
* Creates the choose namespace selection
*
* @param $opts FormOptions
- * @return string
+ * @return String
*/
protected function namespaceFilterForm( FormOptions $opts ) {
$nsSelect = Xml::namespaceSelector( $opts['namespace'], '' );
@@ -544,7 +568,7 @@ class SpecialRecentChanges extends SpecialPage {
* Create a input to filter changes by categories
*
* @param $opts FormOptions
- * @return array
+ * @return Array
*/
protected function categoryFilterForm( FormOptions $opts ) {
list( $label, $input ) = Xml::inputLabelSep( wfMsg('rc_categories'),
@@ -559,13 +583,13 @@ class SpecialRecentChanges extends SpecialPage {
/**
* Filter $rows by categories set in $opts
*
- * @param $rows array of database rows
+ * @param $rows Array of database rows
* @param $opts FormOptions
*/
function filterByCategories( &$rows, FormOptions $opts ) {
- $categories = array_map( 'trim', explode( "|" , $opts['categories'] ) );
+ $categories = array_map( 'trim', explode( '|' , $opts['categories'] ) );
- if( empty($categories) ) {
+ if( !count( $categories ) ) {
return;
}
@@ -573,32 +597,34 @@ class SpecialRecentChanges extends SpecialPage {
$cats = array();
foreach( $categories as $cat ) {
$cat = trim( $cat );
- if( $cat == "" ) continue;
+ if( $cat == '' ) continue;
$cats[] = $cat;
}
# Filter articles
$articles = array();
$a2r = array();
+ $rowsarr = array();
foreach( $rows AS $k => $r ) {
$nt = Title::makeTitle( $r->rc_namespace, $r->rc_title );
$id = $nt->getArticleID();
if( $id == 0 ) continue; # Page might have been deleted...
- if( !in_array($id, $articles) ) {
+ if( !in_array( $id, $articles ) ) {
$articles[] = $id;
}
- if( !isset($a2r[$id]) ) {
+ if( !isset( $a2r[$id] ) ) {
$a2r[$id] = array();
}
$a2r[$id][] = $k;
+ $rowsarr[$k] = $r;
}
# Shortcut?
- if( !count($articles) || !count($cats) )
+ if( !count( $articles ) || !count( $cats ) )
return ;
# Look up
- $c = new Categoryfinder ;
+ $c = new Categoryfinder;
$c->seed( $articles, $cats, $opts['categories_any'] ? "OR" : "AND" ) ;
$match = $c->run();
@@ -607,7 +633,7 @@ class SpecialRecentChanges extends SpecialPage {
foreach( $match AS $id ) {
foreach( $a2r[$id] AS $rev ) {
$k = $rev;
- $newrows[$k] = $rows[$k];
+ $newrows[$k] = $rowsarr[$k];
}
}
$rows = $newrows;
@@ -615,9 +641,11 @@ class SpecialRecentChanges extends SpecialPage {
/**
* Makes change an option link which carries all the other options
- * @param $title see Title
- * @param $override
- * @param $options
+ *
+ * @param $title Title
+ * @param $override Array: options to override
+ * @param $options Array: current options
+ * @param $active Boolean: whether to show the link in bold
*/
function makeOptionsLink( $title, $override, $options, $active = false ) {
global $wgUser;
@@ -633,8 +661,9 @@ class SpecialRecentChanges extends SpecialPage {
/**
* Creates the options panel.
- * @param $defaults array
- * @param $nondefaults array
+ *
+ * @param $defaults Array
+ * @param $nondefaults Array
*/
function optionsPanel( $defaults, $nondefaults ) {
global $wgLang, $wgUser, $wgRCLinkLimits, $wgRCLinkDays;
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
index 3b549843..db0f554d 100644
--- a/includes/specials/SpecialRecentchangeslinked.php
+++ b/includes/specials/SpecialRecentchangeslinked.php
@@ -1,15 +1,36 @@
<?php
+/**
+ * Implements Special:Recentchangeslinked
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
/**
* This is to display changes made to all articles linked in an article.
+ *
* @ingroup SpecialPage
*/
-class SpecialRecentchangeslinked extends SpecialRecentchanges {
+class SpecialRecentchangeslinked extends SpecialRecentChanges {
var $rclTargetTitle;
function __construct(){
- SpecialPage::SpecialPage( 'Recentchangeslinked' );
- $this->includable( true );
+ parent::__construct( 'Recentchangeslinked' );
}
public function getDefaultOptions() {
@@ -52,7 +73,7 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
}
$title = Title::newFromURL( $target );
if( !$title || $title->getInterwiki() != '' ){
- $wgOut->wrapWikiMsg( "<div class=\"errorbox\">\n$1</div><br style=\"clear: both\" />", 'allpagesbadtitle' );
+ $wgOut->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' );
return false;
}
@@ -78,7 +99,8 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
$query_options = array();
// left join with watchlist table to highlight watched rows
- if( $uid = $wgUser->getId() ) {
+ $uid = $wgUser->getId();
+ if( $uid ) {
$tables[] = 'watchlist';
$select[] = 'wl_user';
$join_conds['watchlist'] = array( 'LEFT JOIN', "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace" );
@@ -88,12 +110,13 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
$join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id');
$select[] = 'page_latest';
}
+ if ( !$this->including() ) { // bug 23293
+ ChangeTags::modifyDisplayQuery( $tables, $select, $conds, $join_conds,
+ $query_options, $opts['tagfilter'] );
+ }
- ChangeTags::modifyDisplayQuery( $tables, $select, $conds, $join_conds,
- $query_options, $opts['tagfilter'] );
-
- // XXX: parent class does this, should we too?
- // wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) );
+ if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$select ) ) )
+ return false;
if( $ns == NS_CATEGORY && !$showlinkedto ) {
// special handling for categories
diff --git a/includes/specials/SpecialRemoveRestrictions.php b/includes/specials/SpecialRemoveRestrictions.php
deleted file mode 100644
index a3428a5a..00000000
--- a/includes/specials/SpecialRemoveRestrictions.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-
-function wfSpecialRemoveRestrictions() {
- global $wgOut, $wgRequest, $wgUser, $wgLang;
- $sk = $wgUser->getSkin();
- $title = SpecialPage::getTitleFor( 'RemoveRestrictions' );
- $id = $wgRequest->getVal( 'id' );
- if( !is_numeric( $id ) ) {
- $wgOut->addWikiMsg( 'removerestrictions-noid' );
- return;
- }
-
- UserRestriction::purgeExpired();
- $r = UserRestriction::newFromId( $id, true );
- if( !$r ) {
- $wgOut->addWikiMsg( 'removerestrictions-wrongid' );
- return;
- }
-
- $form = array();
- $form['removerestrictions-user'] = $sk->userLink( $r->getSubjectId(), $r->getSubjectText() ) .
- $sk->userToolLinks( $r->getSubjectId(), $r->getSubjectText() );
- $form['removerestrictions-type'] = UserRestriction::formatType( $r->getType() );
- if( $r->isPage() )
- $form['removerestrictions-page'] = $sk->link( $r->getPage() );
- if( $r->isNamespace() )
- $form['removerestrictions-namespace'] = $wgLang->getDisplayNsText( $r->getNamespace() );
- $form['removerestrictions-reason'] = Xml::input( 'reason' );
-
- $result = null;
- if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) )
- $result = wfSpecialRemoveRestrictionsProcess( $r );
-
- $wgOut->addWikiMsg( 'removerestrictions-intro' );
- $wgOut->addHTML( Xml::fieldset( wfMsgHtml( 'removerestrictions-legend' ) ) );
- if( $result )
- $wgOut->addHTML( '<strong class="success">' . wfMsgExt( 'removerestrictions-success',
- 'parseinline', $r->getSubjectText() ) . '</strong>' );
- $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $title->getLocalUrl( array( 'id' => $id ) ),
- 'method' => 'post' ) ) );
- $wgOut->addHTML( Xml::buildForm( $form, 'removerestrictions-submit' ) );
- $wgOut->addHTML( Xml::hidden( 'id', $r->getId() ) );
- $wgOut->addHTML( Xml::hidden( 'title', $title->getPrefixedDbKey() ) );
- $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) );
- $wgOut->addHTML( "</form></fieldset>" );
-}
-
-function wfSpecialRemoveRestrictionsProcess( $r ) {
- global $wgRequest;
- $reason = $wgRequest->getVal( 'reason' );
- $result = $r->delete();
- $log = new LogPage( 'restrict' );
- $params = array( $r->getType() );
- if( $r->isPage() )
- $params[] = $r->getPage()->getPrefixedDbKey();
- if( $r->isNamespace() )
- $params[] = $r->getNamespace();
- $log->addEntry( 'remove', Title::makeTitle( NS_USER, $r->getSubjectText() ), $reason, $params );
- return $result;
-}
diff --git a/includes/specials/SpecialResetpass.php b/includes/specials/SpecialResetpass.php
index 967d2119..0af6fbf0 100644
--- a/includes/specials/SpecialResetpass.php
+++ b/includes/specials/SpecialResetpass.php
@@ -1,11 +1,29 @@
<?php
/**
+ * Implements Special:Resetpass
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
* Let users recover their password.
+ *
* @ingroup SpecialPage
*/
class SpecialResetpass extends SpecialPage {
@@ -19,6 +37,11 @@ class SpecialResetpass extends SpecialPage {
function execute( $par ) {
global $wgUser, $wgAuth, $wgOut, $wgRequest;
+ if ( wfReadOnly() ) {
+ $wgOut->readOnlyPage();
+ return;
+ }
+
$this->mUserName = $wgRequest->getVal( 'wpName' );
$this->mOldpass = $wgRequest->getVal( 'wpPassword' );
$this->mNewpass = $wgRequest->getVal( 'wpNewPassword' );
@@ -26,6 +49,7 @@ class SpecialResetpass extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
+ $wgOut->disallowUserJs();
if( !$wgAuth->allowPasswordChange() ) {
$this->error( wfMsg( 'resetpass_forbidden' ) );
@@ -84,18 +108,18 @@ class SpecialResetpass extends SpecialPage {
function showForm() {
global $wgOut, $wgUser, $wgRequest;
- $wgOut->disallowUserJs();
-
- $self = SpecialPage::getTitleFor( 'Resetpass' );
+ $self = $this->getTitle();
if ( !$this->mUserName ) {
$this->mUserName = $wgUser->getName();
}
$rememberMe = '';
if ( !$wgUser->isLoggedIn() ) {
+ global $wgCookieExpiration, $wgLang;
$rememberMe = '<tr>' .
'<td></td>' .
'<td class="mw-input">' .
- Xml::checkLabel( wfMsg( 'remembermypassword' ),
+ Xml::checkLabel(
+ wfMsgExt( 'remembermypassword', 'parsemag', $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) ) ),
'wpRemember', 'wpRemember',
$wgRequest->getCheck( 'wpRemember' ) ) .
'</td>' .
@@ -113,9 +137,9 @@ class SpecialResetpass extends SpecialPage {
'method' => 'post',
'action' => $self->getLocalUrl(),
'id' => 'mw-resetpass-form' ) ) . "\n" .
- Xml::hidden( 'token', $wgUser->editToken() ) . "\n" .
- Xml::hidden( 'wpName', $this->mUserName ) . "\n" .
- Xml::hidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . "\n" .
+ Html::hidden( 'token', $wgUser->editToken() ) . "\n" .
+ Html::hidden( 'wpName', $this->mUserName ) . "\n" .
+ Html::hidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . "\n" .
wfMsgExt( 'resetpass_text', array( 'parse' ) ) . "\n" .
Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" .
$this->pretty( array(
@@ -196,7 +220,6 @@ class SpecialResetpass extends SpecialPage {
} catch( PasswordError $e ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) );
throw new PasswordError( $e->getMessage() );
- return;
}
$user->setCookies();
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index b2db869c..f77fc347 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -1,12 +1,32 @@
<?php
/**
- * Special page allowing users with the appropriate permissions to view
- * and hide revisions. Log items can also be hidden.
+ * Implements Special:Revisiondelete
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup SpecialPage
*/
+/**
+ * Special page allowing users with the appropriate permissions to view
+ * and hide revisions. Log items can also be hidden.
+ *
+ * @ingroup SpecialPage
+ */
class SpecialRevisionDelete extends UnlistedSpecialPage {
/** Skin object */
var $skin;
@@ -333,8 +353,6 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$wgOut->addHTML( "<ul>" );
- $where = $revObjs = array();
-
$numRevisions = 0;
// Live revisions...
$list = $this->getList();
@@ -396,10 +414,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
'</td>' .
"</tr>\n" .
Xml::closeElement( 'table' ) .
- Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
- Xml::hidden( 'target', $this->targetObj->getPrefixedText() ) .
- Xml::hidden( 'type', $this->typeName ) .
- Xml::hidden( 'ids', implode( ',', $this->ids ) ) .
+ Html::hidden( 'wpEditToken', $wgUser->editToken() ) .
+ Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
+ Html::hidden( 'type', $this->typeName ) .
+ Html::hidden( 'ids', implode( ',', $this->ids ) ) .
Xml::closeElement( 'fieldset' ) . "\n";
} else {
$out = '';
@@ -533,7 +551,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
protected function success() {
global $wgOut;
$wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
- $wgOut->wrapWikiMsg( '<span class="success">$1</span>', $this->typeInfo['success'] );
+ $wgOut->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeInfo['success'] );
$this->list->reloadFromMaster();
$this->showForm();
}
@@ -598,1250 +616,3 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
}
-/**
- * Temporary b/c interface, collection of static functions.
- * @ingroup SpecialPage
- */
-class RevisionDeleter {
- /**
- * Checks for a change in the bitfield for a certain option and updates the
- * provided array accordingly.
- *
- * @param $desc String: description to add to the array if the option was
- * enabled / disabled.
- * @param $field Integer: the bitmask describing the single option.
- * @param $diff Integer: the xor of the old and new bitfields.
- * @param $new Integer: the new bitfield
- * @param $arr Array: the array to update.
- */
- protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
- if( $diff & $field ) {
- $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
- }
- }
-
- /**
- * Gets an array describing the changes made to the visibilit of the revision.
- * If the resulting array is $arr, then $arr[0] will contain an array of strings
- * describing the items that were hidden, $arr[2] will contain an array of strings
- * describing the items that were unhidden, and $arr[3] will contain an array with
- * a single string, which can be one of "applied restrictions to sysops",
- * "removed restrictions from sysops", or null.
- *
- * @param $n Integer: the new bitfield.
- * @param $o Integer: the old bitfield.
- * @return An array as described above.
- */
- protected static function getChanges( $n, $o ) {
- $diff = $n ^ $o;
- $ret = array( 0 => array(), 1 => array(), 2 => array() );
- // Build bitfield changes in language
- self::checkItem( wfMsgForContent( 'revdelete-content' ),
- Revision::DELETED_TEXT, $diff, $n, $ret );
- self::checkItem( wfMsgForContent( 'revdelete-summary' ),
- Revision::DELETED_COMMENT, $diff, $n, $ret );
- self::checkItem( wfMsgForContent( 'revdelete-uname' ),
- Revision::DELETED_USER, $diff, $n, $ret );
- // Restriction application to sysops
- if( $diff & Revision::DELETED_RESTRICTED ) {
- if( $n & Revision::DELETED_RESTRICTED )
- $ret[2][] = wfMsgForContent( 'revdelete-restricted' );
- else
- $ret[2][] = wfMsgForContent( 'revdelete-unrestricted' );
- }
- return $ret;
- }
-
- /**
- * Gets a log message to describe the given revision visibility change. This
- * message will be of the form "[hid {content, edit summary, username}];
- * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
- *
- * @param $count Integer: The number of effected revisions.
- * @param $nbitfield Integer: The new bitfield for the revision.
- * @param $obitfield Integer: The old bitfield for the revision.
- * @param $isForLog Boolean
- */
- public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false ) {
- global $wgLang;
- $s = '';
- $changes = self::getChanges( $nbitfield, $obitfield );
- if( count( $changes[0] ) ) {
- $s .= wfMsgForContent( 'revdelete-hid', implode( ', ', $changes[0] ) );
- }
- if( count( $changes[1] ) ) {
- if ($s) $s .= '; ';
- $s .= wfMsgForContent( 'revdelete-unhid', implode( ', ', $changes[1] ) );
- }
- if( count( $changes[2] ) ) {
- $s .= $s ? ' (' . $changes[2][0] . ')' : $changes[2][0];
- }
- $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
- return wfMsgExt( $msg, array( 'parsemag', 'content' ), $s, $wgLang->formatNum($count) );
-
- }
-
- // Get DB field name for URL param...
- // Future code for other things may also track
- // other types of revision-specific changes.
- // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
- public static function getRelationType( $typeName ) {
- if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
- $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
- }
- if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
- $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
- $list = new $class( null, null, null );
- return $list->getIdField();
- } else {
- return null;
- }
- }
-}
-
-/**
- * Abstract base class for a list of deletable items
- */
-abstract class RevDel_List {
- var $special, $title, $ids, $res, $current;
- var $type = null; // override this
- var $idField = null; // override this
- var $dateField = false; // override this
- var $authorIdField = false; // override this
- var $authorNameField = false; // override this
-
- /**
- * @param $special The parent SpecialPage
- * @param $title The target title
- * @param $ids Array of IDs
- */
- public function __construct( $special, $title, $ids ) {
- $this->special = $special;
- $this->title = $title;
- $this->ids = $ids;
- }
-
- /**
- * Get the internal type name of this list. Equal to the table name.
- */
- public function getType() {
- return $this->type;
- }
-
- /**
- * Get the DB field name associated with the ID list
- */
- public function getIdField() {
- return $this->idField;
- }
-
- /**
- * Get the DB field name storing timestamps
- */
- public function getTimestampField() {
- return $this->dateField;
- }
-
- /**
- * Get the DB field name storing user ids
- */
- public function getAuthorIdField() {
- return $this->authorIdField;
- }
-
- /**
- * Get the DB field name storing user names
- */
- public function getAuthorNameField() {
- return $this->authorNameField;
- }
- /**
- * Set the visibility for the revisions in this list. Logging and
- * transactions are done here.
- *
- * @param $params Associative array of parameters. Members are:
- * value: The integer value to set the visibility to
- * comment: The log comment.
- * @return Status
- */
- public function setVisibility( $params ) {
- $bitPars = $params['value'];
- $comment = $params['comment'];
-
- $this->res = false;
- $dbw = wfGetDB( DB_MASTER );
- $this->doQuery( $dbw );
- $dbw->begin();
- $status = Status::newGood();
- $missing = array_flip( $this->ids );
- $this->clearFileOps();
- $idsForLog = array();
- $authorIds = $authorIPs = array();
-
- for ( $this->reset(); $this->current(); $this->next() ) {
- $item = $this->current();
- unset( $missing[ $item->getId() ] );
-
- $oldBits = $item->getBits();
- // Build the actual new rev_deleted bitfield
- $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
-
- if ( $oldBits == $newBits ) {
- $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
- $status->failCount++;
- continue;
- } elseif ( $oldBits == 0 && $newBits != 0 ) {
- $opType = 'hide';
- } elseif ( $oldBits != 0 && $newBits == 0 ) {
- $opType = 'show';
- } else {
- $opType = 'modify';
- }
-
- if ( $item->isHideCurrentOp( $newBits ) ) {
- // Cannot hide current version text
- $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
- $status->failCount++;
- continue;
- }
- if ( !$item->canView() ) {
- // Cannot access this revision
- $msg = ($opType == 'show') ?
- 'revdelete-show-no-access' : 'revdelete-modify-no-access';
- $status->error( $msg, $item->formatDate(), $item->formatTime() );
- $status->failCount++;
- continue;
- }
- // Cannot just "hide from Sysops" without hiding any fields
- if( $newBits == Revision::DELETED_RESTRICTED ) {
- $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
- $status->failCount++;
- continue;
- }
-
- // Update the revision
- $ok = $item->setBits( $newBits );
-
- if ( $ok ) {
- $idsForLog[] = $item->getId();
- $status->successCount++;
- if( $item->getAuthorId() > 0 ) {
- $authorIds[] = $item->getAuthorId();
- } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
- $authorIPs[] = $item->getAuthorName();
- }
- } else {
- $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
- $status->failCount++;
- }
- }
-
- // Handle missing revisions
- foreach ( $missing as $id => $unused ) {
- $status->error( 'revdelete-modify-missing', $id );
- $status->failCount++;
- }
-
- if ( $status->successCount == 0 ) {
- $status->ok = false;
- $dbw->rollback();
- return $status;
- }
-
- // Save success count
- $successCount = $status->successCount;
-
- // Move files, if there are any
- $status->merge( $this->doPreCommitUpdates() );
- if ( !$status->isOK() ) {
- // Fatal error, such as no configured archive directory
- $dbw->rollback();
- return $status;
- }
-
- // Log it
- $this->updateLog( array(
- 'title' => $this->title,
- 'count' => $successCount,
- 'newBits' => $newBits,
- 'oldBits' => $oldBits,
- 'comment' => $comment,
- 'ids' => $idsForLog,
- 'authorIds' => $authorIds,
- 'authorIPs' => $authorIPs
- ) );
- $dbw->commit();
-
- // Clear caches
- $status->merge( $this->doPostCommitUpdates() );
- return $status;
- }
-
- /**
- * Reload the list data from the master DB. This can be done after setVisibility()
- * to allow $item->getHTML() to show the new data.
- */
- function reloadFromMaster() {
- $dbw = wfGetDB( DB_MASTER );
- $this->res = $this->doQuery( $dbw );
- }
-
- /**
- * Record a log entry on the action
- * @param $params Associative array of parameters:
- * newBits: The new value of the *_deleted bitfield
- * oldBits: The old value of the *_deleted bitfield.
- * title: The target title
- * ids: The ID list
- * comment: The log comment
- * authorsIds: The array of the user IDs of the offenders
- * authorsIPs: The array of the IP/anon user offenders
- */
- protected function updateLog( $params ) {
- // Get the URL param's corresponding DB field
- $field = RevisionDeleter::getRelationType( $this->getType() );
- if( !$field ) {
- throw new MWException( "Bad log URL param type!" );
- }
- // Put things hidden from sysops in the oversight log
- if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
- $logType = 'suppress';
- } else {
- $logType = 'delete';
- }
- // Add params for effected 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 );
- // 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 );
- }
-
- /**
- * Get the log action for this list type
- */
- public function getLogAction() {
- return 'revision';
- }
-
- /**
- * Get log parameter array.
- * @param $params Associative array of log parameters, same as updateLog()
- * @return array
- */
- public function getLogParams( $params ) {
- return array(
- $this->getType(),
- implode( ',', $params['ids'] ),
- "ofield={$params['oldBits']}",
- "nfield={$params['newBits']}"
- );
- }
-
- /**
- * Initialise the current iteration pointer
- */
- protected function initCurrent() {
- $row = $this->res->current();
- if ( $row ) {
- $this->current = $this->newItem( $row );
- } else {
- $this->current = false;
- }
- }
-
- /**
- * Start iteration. This must be called before current() or next().
- * @return First list item
- */
- public function reset() {
- if ( !$this->res ) {
- $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
- } else {
- $this->res->rewind();
- }
- $this->initCurrent();
- return $this->current;
- }
-
- /**
- * Get the current list item, or false if we are at the end
- */
- public function current() {
- return $this->current;
- }
-
- /**
- * Move the iteration pointer to the next list item, and return it.
- */
- public function next() {
- $this->res->next();
- $this->initCurrent();
- return $this->current;
- }
-
- /**
- * Get the number of items in the list.
- */
- public function length() {
- if( !$this->res ) {
- return 0;
- } else {
- return $this->res->numRows();
- }
- }
-
- /**
- * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
- * STUB
- */
- public function clearFileOps() {
- }
-
- /**
- * A hook for setVisibility(): do batch updates pre-commit.
- * STUB
- * @return Status
- */
- public function doPreCommitUpdates() {
- return Status::newGood();
- }
-
- /**
- * A hook for setVisibility(): do any necessary updates post-commit.
- * STUB
- * @return Status
- */
- public function doPostCommitUpdates() {
- return Status::newGood();
- }
-
- /**
- * Create an item object from a DB result row
- * @param $row stdclass
- */
- abstract public function newItem( $row );
-
- /**
- * Do the DB query to iterate through the objects.
- * @param $db Database object to use for the query
- */
- abstract public function doQuery( $db );
-
- /**
- * Get the integer value of the flag used for suppression
- */
- abstract public function getSuppressBit();
-}
-
-/**
- * Abstract base class for deletable items
- */
-abstract class RevDel_Item {
- /** The parent SpecialPage */
- var $special;
-
- /** The parent RevDel_List */
- var $list;
-
- /** The DB result row */
- var $row;
-
- /**
- * @param $list RevDel_List
- * @param $row DB result row
- */
- public function __construct( $list, $row ) {
- $this->special = $list->special;
- $this->list = $list;
- $this->row = $row;
- }
-
- /**
- * Get the ID, as it would appear in the ids URL parameter
- */
- public function getId() {
- $field = $this->list->getIdField();
- return $this->row->$field;
- }
-
- /**
- * Get the date, formatted with $wgLang
- */
- public function formatDate() {
- global $wgLang;
- return $wgLang->date( $this->getTimestamp() );
- }
-
- /**
- * Get the time, formatted with $wgLang
- */
- public function formatTime() {
- global $wgLang;
- return $wgLang->time( $this->getTimestamp() );
- }
-
- /**
- * Get the timestamp in MW 14-char form
- */
- public function getTimestamp() {
- $field = $this->list->getTimestampField();
- return wfTimestamp( TS_MW, $this->row->$field );
- }
-
- /**
- * Get the author user ID
- */
- public function getAuthorId() {
- $field = $this->list->getAuthorIdField();
- return intval( $this->row->$field );
- }
-
- /**
- * Get the author user name
- */
- public function getAuthorName() {
- $field = $this->list->getAuthorNameField();
- return strval( $this->row->$field );
- }
-
- /**
- * Returns true if the item is "current", and the operation to set the given
- * bits can't be executed for that reason
- * STUB
- */
- public function isHideCurrentOp( $newBits ) {
- return false;
- }
-
- /**
- * Returns true if the current user can view the item
- */
- abstract public function canView();
-
- /**
- * Returns true if the current user can view the item text/file
- */
- abstract public function canViewContent();
-
- /**
- * Get the current deletion bitfield value
- */
- abstract public function getBits();
-
- /**
- * Get the HTML of the list item. Should be include <li></li> tags.
- * This is used to show the list in HTML form, by the special page.
- */
- abstract public function getHTML();
-
- /**
- * Set the visibility of the item. This should do any necessary DB queries.
- *
- * The DB update query should have a condition which forces it to only update
- * if the value in the DB matches the value fetched earlier with the SELECT.
- * If the update fails because it did not match, the function should return
- * false. This prevents concurrency problems.
- *
- * @return boolean success
- */
- abstract public function setBits( $newBits );
-}
-
-/**
- * List for revision table items
- */
-class RevDel_RevisionList extends RevDel_List {
- var $currentRevId;
- var $type = 'revision';
- var $idField = 'rev_id';
- var $dateField = 'rev_timestamp';
- var $authorIdField = 'rev_user';
- var $authorNameField = 'rev_user_text';
-
- public function doQuery( $db ) {
- $ids = array_map( 'intval', $this->ids );
- return $db->select( array('revision','page'), '*',
- array(
- 'rev_page' => $this->title->getArticleID(),
- 'rev_id' => $ids,
- 'rev_page = page_id'
- ),
- __METHOD__,
- array( 'ORDER BY' => 'rev_id DESC' )
- );
- }
-
- public function newItem( $row ) {
- return new RevDel_RevisionItem( $this, $row );
- }
-
- public function getCurrent() {
- if ( is_null( $this->currentRevId ) ) {
- $dbw = wfGetDB( DB_MASTER );
- $this->currentRevId = $dbw->selectField(
- 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
- }
- return $this->currentRevId;
- }
-
- public function getSuppressBit() {
- return Revision::DELETED_RESTRICTED;
- }
-
- public function doPreCommitUpdates() {
- $this->title->invalidateCache();
- return Status::newGood();
- }
-
- public function doPostCommitUpdates() {
- $this->title->purgeSquid();
- // Extensions that require referencing previous revisions may need this
- wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$this->title ) );
- return Status::newGood();
- }
-}
-
-/**
- * Item class for a revision table row
- */
-class RevDel_RevisionItem extends RevDel_Item {
- var $revision;
-
- public function __construct( $list, $row ) {
- parent::__construct( $list, $row );
- $this->revision = new Revision( $row );
- }
-
- public function canView() {
- return $this->revision->userCan( Revision::DELETED_RESTRICTED );
- }
-
- public function canViewContent() {
- return $this->revision->userCan( Revision::DELETED_TEXT );
- }
-
- public function getBits() {
- return $this->revision->mDeleted;
- }
-
- public function setBits( $bits ) {
- $dbw = wfGetDB( DB_MASTER );
- // Update revision table
- $dbw->update( 'revision',
- array( 'rev_deleted' => $bits ),
- array(
- 'rev_id' => $this->revision->getId(),
- 'rev_page' => $this->revision->getPage(),
- 'rev_deleted' => $this->getBits()
- ),
- __METHOD__
- );
- if ( !$dbw->affectedRows() ) {
- // Concurrent fail!
- return false;
- }
- // Update recentchanges table
- $dbw->update( 'recentchanges',
- array(
- 'rc_deleted' => $bits,
- 'rc_patrolled' => 1
- ),
- array(
- 'rc_this_oldid' => $this->revision->getId(), // condition
- // non-unique timestamp index
- 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
- ),
- __METHOD__
- );
- return true;
- }
-
- public function isDeleted() {
- return $this->revision->isDeleted( Revision::DELETED_TEXT );
- }
-
- public function isHideCurrentOp( $newBits ) {
- return ( $newBits & Revision::DELETED_TEXT )
- && $this->list->getCurrent() == $this->getId();
- }
-
- /**
- * Get the HTML link to the revision text.
- * Overridden by RevDel_ArchiveItem.
- */
- protected function getRevisionLink() {
- global $wgLang;
- $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
- if ( $this->isDeleted() && !$this->canViewContent() ) {
- return $date;
- }
- return $this->special->skin->link(
- $this->list->title,
- $date,
- array(),
- array(
- 'oldid' => $this->revision->getId(),
- 'unhide' => 1
- )
- );
- }
-
- /**
- * Get the HTML link to the diff.
- * Overridden by RevDel_ArchiveItem
- */
- protected function getDiffLink() {
- if ( $this->isDeleted() && !$this->canViewContent() ) {
- return wfMsgHtml('diff');
- } else {
- return
- $this->special->skin->link(
- $this->list->title,
- wfMsgHtml('diff'),
- array(),
- array(
- 'diff' => $this->revision->getId(),
- 'oldid' => 'prev',
- 'unhide' => 1
- ),
- array(
- 'known',
- 'noclasses'
- )
- );
- }
- }
-
- public function getHTML() {
- $difflink = $this->getDiffLink();
- $revlink = $this->getRevisionLink();
- $userlink = $this->special->skin->revUserLink( $this->revision );
- $comment = $this->special->skin->revComment( $this->revision );
- if ( $this->isDeleted() ) {
- $revlink = "<span class=\"history-deleted\">$revlink</span>";
- }
- return "<li>($difflink) $revlink $userlink $comment</li>";
- }
-}
-
-/**
- * List for archive table items, i.e. revisions deleted via action=delete
- */
-class RevDel_ArchiveList extends RevDel_RevisionList {
- var $type = 'archive';
- var $idField = 'ar_timestamp';
- var $dateField = 'ar_timestamp';
- var $authorIdField = 'ar_user';
- var $authorNameField = 'ar_user_text';
-
- public function doQuery( $db ) {
- $timestamps = array();
- foreach ( $this->ids as $id ) {
- $timestamps[] = $db->timestamp( $id );
- }
- return $db->select( 'archive', '*',
- array(
- 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- 'ar_timestamp' => $timestamps
- ),
- __METHOD__,
- array( 'ORDER BY' => 'ar_timestamp DESC' )
- );
- }
-
- public function newItem( $row ) {
- return new RevDel_ArchiveItem( $this, $row );
- }
-
- public function doPreCommitUpdates() {
- return Status::newGood();
- }
-
- public function doPostCommitUpdates() {
- return Status::newGood();
- }
-}
-
-/**
- * Item class for a archive table row
- */
-class RevDel_ArchiveItem extends RevDel_RevisionItem {
- public function __construct( $list, $row ) {
- RevDel_Item::__construct( $list, $row );
- $this->revision = Revision::newFromArchiveRow( $row,
- array( 'page' => $this->list->title->getArticleId() ) );
- }
-
- public function getId() {
- # Convert DB timestamp to MW timestamp
- return $this->revision->getTimestamp();
- }
-
- public function setBits( $bits ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'archive',
- array( 'ar_deleted' => $bits ),
- array( 'ar_namespace' => $this->list->title->getNamespace(),
- 'ar_title' => $this->list->title->getDBkey(),
- // use timestamp for index
- 'ar_timestamp' => $this->row->ar_timestamp,
- 'ar_rev_id' => $this->row->ar_rev_id,
- 'ar_deleted' => $this->getBits()
- ),
- __METHOD__ );
- return (bool)$dbw->affectedRows();
- }
-
- protected function getRevisionLink() {
- global $wgLang;
- $undelete = SpecialPage::getTitleFor( 'Undelete' );
- $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
- if ( $this->isDeleted() && !$this->canViewContent() ) {
- return $date;
- }
- return $this->special->skin->link( $undelete, $date, array(),
- array(
- 'target' => $this->list->title->getPrefixedText(),
- 'timestamp' => $this->revision->getTimestamp()
- ) );
- }
-
- protected function getDiffLink() {
- if ( $this->isDeleted() && !$this->canViewContent() ) {
- return wfMsgHtml( 'diff' );
- }
- $undelete = SpecialPage::getTitleFor( 'Undelete' );
- return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
- array(
- 'target' => $this->list->title->getPrefixedText(),
- 'diff' => 'prev',
- 'timestamp' => $this->revision->getTimestamp()
- ) );
- }
-}
-
-/**
- * List for oldimage table items
- */
-class RevDel_FileList extends RevDel_List {
- var $type = 'oldimage';
- var $idField = 'oi_archive_name';
- var $dateField = 'oi_timestamp';
- var $authorIdField = 'oi_user';
- var $authorNameField = 'oi_user_text';
- var $storeBatch, $deleteBatch, $cleanupBatch;
-
- public function doQuery( $db ) {
- $archiveName = array();
- foreach( $this->ids as $timestamp ) {
- $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
- }
- return $db->select( 'oldimage', '*',
- array(
- 'oi_name' => $this->title->getDBkey(),
- 'oi_archive_name' => $archiveNames
- ),
- __METHOD__,
- array( 'ORDER BY' => 'oi_timestamp DESC' )
- );
- }
-
- public function newItem( $row ) {
- return new RevDel_FileItem( $this, $row );
- }
-
- public function clearFileOps() {
- $this->deleteBatch = array();
- $this->storeBatch = array();
- $this->cleanupBatch = array();
- }
-
- public function doPreCommitUpdates() {
- $status = Status::newGood();
- $repo = RepoGroup::singleton()->getLocalRepo();
- if ( $this->storeBatch ) {
- $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
- }
- if ( !$status->isOK() ) {
- return $status;
- }
- if ( $this->deleteBatch ) {
- $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
- }
- if ( !$status->isOK() ) {
- // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
- // modified (but destined for rollback) causes data loss
- return $status;
- }
- if ( $this->cleanupBatch ) {
- $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
- }
- return $status;
- }
-
- public function doPostCommitUpdates() {
- $file = wfLocalFile( $this->title );
- $file->purgeCache();
- $file->purgeDescription();
- return Status::newGood();
- }
-
- public function getSuppressBit() {
- return File::DELETED_RESTRICTED;
- }
-}
-
-/**
- * Item class for an oldimage table row
- */
-class RevDel_FileItem extends RevDel_Item {
- var $file;
-
- public function __construct( $list, $row ) {
- parent::__construct( $list, $row );
- $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
- }
-
- public function getId() {
- $parts = explode( '!', $this->row->oi_archive_name );
- return $parts[0];
- }
-
- public function canView() {
- return $this->file->userCan( File::DELETED_RESTRICTED );
- }
-
- public function canViewContent() {
- return $this->file->userCan( File::DELETED_FILE );
- }
-
- public function getBits() {
- return $this->file->getVisibility();
- }
-
- public function setBits( $bits ) {
- # Queue the file op
- # FIXME: move to LocalFile.php
- if ( $this->isDeleted() ) {
- if ( $bits & File::DELETED_FILE ) {
- # Still deleted
- } else {
- # Newly undeleted
- $key = $this->file->getStorageKey();
- $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
- $this->list->storeBatch[] = array(
- $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
- 'public',
- $this->file->getRel()
- );
- $this->list->cleanupBatch[] = $key;
- }
- } elseif ( $bits & File::DELETED_FILE ) {
- # Newly deleted
- $key = $this->file->getStorageKey();
- $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
- $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
- }
-
- # Do the database operations
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'oldimage',
- array( 'oi_deleted' => $bits ),
- array(
- 'oi_name' => $this->row->oi_name,
- 'oi_timestamp' => $this->row->oi_timestamp,
- 'oi_deleted' => $this->getBits()
- ),
- __METHOD__
- );
- return (bool)$dbw->affectedRows();
- }
-
- public function isDeleted() {
- return $this->file->isDeleted( File::DELETED_FILE );
- }
-
- /**
- * Get the link to the file.
- * Overridden by RevDel_ArchivedFileItem.
- */
- protected function getLink() {
- global $wgLang, $wgUser;
- $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
- if ( $this->isDeleted() ) {
- # Hidden files...
- if ( !$this->canViewContent() ) {
- $link = $date;
- } else {
- $link = $this->special->skin->link(
- $this->special->getTitle(),
- $date, array(),
- array(
- 'target' => $this->list->title->getPrefixedText(),
- 'file' => $this->file->getArchiveName(),
- 'token' => $wgUser->editToken( $this->file->getArchiveName() )
- )
- );
- }
- return '<span class="history-deleted">' . $link . '</span>';
- } else {
- # Regular files...
- $url = $this->file->getUrl();
- return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
- }
- }
- /**
- * Generate a user tool link cluster if the current user is allowed to view it
- * @return string HTML
- */
- protected function getUserTools() {
- if( $this->file->userCan( Revision::DELETED_USER ) ) {
- $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
- $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
- } else {
- $link = wfMsgHtml( 'rev-deleted-user' );
- }
- if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
- return '<span class="history-deleted">' . $link . '</span>';
- }
- return $link;
- }
-
- /**
- * Wrap and format the file's comment block, if the current
- * user is allowed to view it.
- *
- * @return string HTML
- */
- protected function getComment() {
- if( $this->file->userCan( File::DELETED_COMMENT ) ) {
- $block = $this->special->skin->commentBlock( $this->file->description );
- } else {
- $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
- }
- if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
- return "<span class=\"history-deleted\">$block</span>";
- }
- return $block;
- }
-
- public function getHTML() {
- global $wgLang;
- $data =
- wfMsg(
- 'widthheight',
- $wgLang->formatNum( $this->file->getWidth() ),
- $wgLang->formatNum( $this->file->getHeight() )
- ) .
- ' (' .
- wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
- ')';
- $pageLink = $this->getLink();
-
- return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
- $data . ' ' . $this->getComment(). '</li>';
- }
-}
-
-/**
- * List for filearchive table items
- */
-class RevDel_ArchivedFileList extends RevDel_FileList {
- var $type = 'filearchive';
- var $idField = 'fa_id';
- var $dateField = 'fa_timestamp';
- var $authorIdField = 'fa_user';
- var $authorNameField = 'fa_user_text';
-
- public function doQuery( $db ) {
- $ids = array_map( 'intval', $this->ids );
- return $db->select( 'filearchive', '*',
- array(
- 'fa_name' => $this->title->getDBkey(),
- 'fa_id' => $ids
- ),
- __METHOD__,
- array( 'ORDER BY' => 'fa_id DESC' )
- );
- }
-
- public function newItem( $row ) {
- return new RevDel_ArchivedFileItem( $this, $row );
- }
-}
-
-/**
- * Item class for a filearchive table row
- */
-class RevDel_ArchivedFileItem extends RevDel_FileItem {
- public function __construct( $list, $row ) {
- RevDel_Item::__construct( $list, $row );
- $this->file = ArchivedFile::newFromRow( $row );
- }
-
- public function getId() {
- return $this->row->fa_id;
- }
-
- public function setBits( $bits ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'filearchive',
- array( 'fa_deleted' => $bits ),
- array(
- 'fa_id' => $this->row->fa_id,
- 'fa_deleted' => $this->getBits(),
- ),
- __METHOD__
- );
- return (bool)$dbw->affectedRows();
- }
-
- protected function getLink() {
- global $wgLang, $wgUser;
- $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
- $undelete = SpecialPage::getTitleFor( 'Undelete' );
- $key = $this->file->getKey();
- # Hidden files...
- if( !$this->canViewContent() ) {
- $link = $date;
- } else {
- $link = $this->special->skin->link( $undelete, $date, array(),
- array(
- 'target' => $this->list->title->getPrefixedText(),
- 'file' => $key,
- 'token' => $wgUser->editToken( $key )
- )
- );
- }
- if( $this->isDeleted() ) {
- $link = '<span class="history-deleted">' . $link . '</span>';
- }
- return $link;
- }
-}
-
-/**
- * List for logging table items
- */
-class RevDel_LogList extends RevDel_List {
- var $type = 'logging';
- var $idField = 'log_id';
- var $dateField = 'log_timestamp';
- var $authorIdField = 'log_user';
- var $authorNameField = 'log_user_text';
-
- public function doQuery( $db ) {
- global $wgMessageCache;
- $wgMessageCache->loadAllMessages();
- $ids = array_map( 'intval', $this->ids );
- return $db->select( 'logging', '*',
- array( 'log_id' => $ids ),
- __METHOD__,
- array( 'ORDER BY' => 'log_id DESC' )
- );
- }
-
- public function newItem( $row ) {
- return new RevDel_LogItem( $this, $row );
- }
-
- public function getSuppressBit() {
- return Revision::DELETED_RESTRICTED;
- }
-
- public function getLogAction() {
- return 'event';
- }
-
- public function getLogParams( $params ) {
- return array(
- implode( ',', $params['ids'] ),
- "ofield={$params['oldBits']}",
- "nfield={$params['newBits']}"
- );
- }
-}
-
-/**
- * Item class for a logging table row
- */
-class RevDel_LogItem extends RevDel_Item {
- public function canView() {
- return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
- }
-
- public function canViewContent() {
- return true; // none
- }
-
- public function getBits() {
- return $this->row->log_deleted;
- }
-
- public function setBits( $bits ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'recentchanges',
- array(
- 'rc_deleted' => $bits,
- 'rc_patrolled' => 1
- ),
- array(
- 'rc_logid' => $this->row->log_id,
- 'rc_timestamp' => $this->row->log_timestamp // index
- ),
- __METHOD__
- );
- $dbw->update( 'logging',
- array( 'log_deleted' => $bits ),
- array(
- 'log_id' => $this->row->log_id,
- 'log_deleted' => $this->getBits()
- ),
- __METHOD__
- );
- return (bool)$dbw->affectedRows();
- }
-
- public function getHTML() {
- global $wgLang;
-
- $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
- $paramArray = LogPage::extractParams( $this->row->log_params );
- $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
-
- // Log link for this page
- $loglink = $this->special->skin->link(
- SpecialPage::getTitleFor( 'Log' ),
- wfMsgHtml( 'log' ),
- array(),
- array( 'page' => $title->getPrefixedText() )
- );
- // Action text
- if( !$this->canView() ) {
- $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
- } else {
- $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
- $this->special->skin, $paramArray, true, true );
- if( $this->row->log_deleted & LogPage::DELETED_ACTION )
- $action = '<span class="history-deleted">' . $action . '</span>';
- }
- // User links
- $userLink = $this->special->skin->userLink( $this->row->log_user,
- User::WhoIs( $this->row->log_user ) );
- if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
- $userLink = '<span class="history-deleted">' . $userLink . '</span>';
- }
- // Comment
- $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
- if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
- $comment = '<span class="history-deleted">' . $comment . '</span>';
- }
- return "<li>($loglink) $date $userLink $action $comment</li>";
- }
-}
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index 40b28236..fd6e858e 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -1,24 +1,24 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://www.mediawiki.org/
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# http://www.gnu.org/copyleft/gpl.html
-
/**
- * Run text & title search and display the output
+ * Implements Special:Search
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
@@ -29,7 +29,9 @@
* @param $par String: (default '')
*/
function wfSpecialSearch( $par = '' ) {
- global $wgRequest, $wgUser;
+ global $wgRequest, $wgUser, $wgOut;
+ $wgOut->allowClickjacking();
+
// Strip underscores from title parameter; most of the time we'll want
// text form here. But don't strip underscores from actual text params!
$titleParam = str_replace( '_', ' ', $par );
@@ -56,11 +58,10 @@ class SpecialSearch {
* Set up basic search parameters from the request and user settings.
* Typically you'll pass $wgRequest and $wgUser.
*
- * @param WebRequest $request
- * @param User $user
- * @public
+ * @param $request WebRequest
+ * @param $user User
*/
- function __construct( &$request, &$user ) {
+ public function __construct( &$request, &$user ) {
list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
$this->mPrefix = $request->getVal('prefix', '');
# Extract requested namespaces
@@ -68,7 +69,7 @@ class SpecialSearch {
if( empty( $this->namespaces ) ) {
$this->namespaces = SearchEngine::userNamespaces( $user );
}
- $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false;
+ $this->searchRedirects = $request->getCheck( 'redirs' );
$this->searchAdvanced = $request->getVal( 'advanced' );
$this->active = 'advanced';
$this->sk = $user->getSkin();
@@ -78,7 +79,8 @@ class SpecialSearch {
/**
* If an exact title match can be found, jump straight ahead to it.
- * @param string $term
+ *
+ * @param $term String
*/
public function goResult( $term ) {
global $wgOut;
@@ -91,6 +93,12 @@ class SpecialSearch {
}
# If there's an exact or very near match, jump right there.
$t = SearchEngine::getNearMatch( $term );
+
+ if ( !wfRunHooks( 'SpecialSearchGo', array( &$t, &$term ) ) ) {
+ # Hook requested termination
+ return;
+ }
+
if( !is_null( $t ) ) {
$wgOut->redirect( $t->getFullURL() );
return;
@@ -100,6 +108,8 @@ class SpecialSearch {
if( !is_null( $t ) ) {
global $wgGoToEdit;
wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
+ wfDebugLog( 'nogomatch', $t->getText(), false );
+
# If the feature is enabled, go straight to the edit page
if( $wgGoToEdit ) {
$wgOut->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) );
@@ -110,7 +120,7 @@ class SpecialSearch {
}
/**
- * @param string $term
+ * @param $term String
*/
public function showResults( $term ) {
global $wgOut, $wgUser, $wgDisableTextSearch, $wgContLang, $wgScript;
@@ -220,7 +230,6 @@ class SpecialSearch {
$filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':';
if( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
- $wgOut->addHTML( $this->searchFocus() );
$wgOut->addHTML( $this->formHeader($term, 0, 0));
if( $this->searchAdvanced ) {
$wgOut->addHTML( $this->powerSearchBox( $term ) );
@@ -302,13 +311,10 @@ class SpecialSearch {
$textMatches->free();
}
if( $num === 0 ) {
- $wgOut->addWikiMsg( 'search-nonefound', wfEscapeWikiText( $term ) );
+ $wgOut->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", array( 'search-nonefound', wfEscapeWikiText( $term ) ) );
$this->showCreateLink( $t );
}
$wgOut->addHtml( "</div>" );
- if( $num === 0 ) {
- $wgOut->addHTML( $this->searchFocus() );
- }
if( $num || $this->offset ) {
$wgOut->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
@@ -326,10 +332,12 @@ class SpecialSearch {
$messageName = 'searchmenu-exists';
} elseif( $t->userCan( 'create' ) ) {
$messageName = 'searchmenu-new';
+ } else {
+ $messageName = 'searchmenu-new-nocreate';
}
}
if( $messageName ) {
- $wgOut->addWikiMsg( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) );
+ $wgOut->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) ) );
} else {
// preserve the paragraph for margins etc...
$wgOut->addHtml( '<p></p>' );
@@ -342,10 +350,9 @@ class SpecialSearch {
protected function setupPage( $term ) {
global $wgOut;
// Figure out the active search profile header
- $nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
- if( $this->searchAdvanced )
+ if( $this->searchAdvanced ) {
$this->active = 'advanced';
- else {
+ } else {
$profiles = $this->getSearchProfiles();
foreach( $profiles as $key => $data ) {
@@ -363,16 +370,16 @@ class SpecialSearch {
$wgOut->setArticleRelated( false );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
// add javascript specific to special:search
- $wgOut->addScriptFile( 'search.js' );
- $wgOut->allowClickjacking();
+ $wgOut->addModules( 'mediawiki.legacy.search' );
+ $wgOut->addModules( 'mediawiki.special.search' );
}
/**
* Extract "power search" namespace settings from the request object,
* returning a list of index numbers to search.
*
- * @param WebRequest $request
- * @return array
+ * @param $request WebRequest
+ * @return Array
*/
protected function powerSearch( &$request ) {
$arr = array();
@@ -386,7 +393,8 @@ class SpecialSearch {
/**
* Reconstruct the 'power search' options for links
- * @return array
+ *
+ * @return Array
*/
protected function powerSearchOptions() {
$opt = array();
@@ -403,7 +411,7 @@ class SpecialSearch {
/**
* Show whole set of results
*
- * @param SearchResultSet $matches
+ * @param $matches SearchResultSet
*/
protected function showMatches( &$matches ) {
global $wgContLang;
@@ -416,7 +424,6 @@ class SpecialSearch {
if( !is_null($infoLine) ) {
$out .= "\n<!-- {$infoLine} -->\n";
}
- $off = $this->offset + 1;
$out .= "<ul class='mw-search-results'>\n";
while( $result = $matches->next() ) {
$out .= $this->showHit( $result, $terms );
@@ -431,11 +438,12 @@ class SpecialSearch {
/**
* Format a single hit result
- * @param SearchResult $result
- * @param array $terms terms to highlight
+ *
+ * @param $result SearchResult
+ * @param $terms Array: terms to highlight
*/
protected function showHit( $result, $terms ) {
- global $wgContLang, $wgLang, $wgUser;
+ global $wgLang, $wgUser;
wfProfileIn( __METHOD__ );
if( $result->isBrokenTitle() ) {
@@ -539,6 +547,18 @@ class SpecialSearch {
$this->sk->formatSize( $byteSize ),
$wgLang->formatNum( $wordCount )
);
+
+ if( $t->getNamespace() == NS_CATEGORY ) {
+ $cat = Category::newFromTitle( $t );
+ $size = wfMsgExt(
+ 'search-result-category-size',
+ array( 'parsemag', 'escape' ),
+ $wgLang->formatNum( $cat->getPageCount() ),
+ $wgLang->formatNum( $cat->getSubcatCount() ),
+ $wgLang->formatNum( $cat->getFileCount() )
+ );
+ }
+
$date = $wgLang->timeanddate( $timestamp );
// link to related articles if supported
@@ -567,7 +587,7 @@ class SpecialSearch {
if( $img ) {
$thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
if( $thumb ) {
- $desc = $img->getShortDesc();
+ $desc = wfMsg( 'parentheses', $img->getShortDesc() );
wfProfileOut( __METHOD__ );
// Float doesn't seem to interact well with the bullets.
// Table messes up vertical alignment of the bullets.
@@ -591,7 +611,7 @@ class SpecialSearch {
}
wfProfileOut( __METHOD__ );
- return "<li>{$link} {$redirect} {$section} {$extract}\n" .
+ return "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" .
"<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" .
"</li>\n";
@@ -600,7 +620,8 @@ class SpecialSearch {
/**
* Show results from other wikis
*
- * @param SearchResultSet $matches
+ * @param $matches SearchResultSet
+ * @param $query String
*/
protected function showInterwiki( &$matches, $query ) {
global $wgContLang;
@@ -609,7 +630,6 @@ class SpecialSearch {
$out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>".
wfMsg('search-interwiki-caption')."</div>\n";
- $off = $this->offset + 1;
$out .= "<ul class='mw-search-iwresults'>\n";
// work out custom project captions
@@ -638,15 +658,14 @@ class SpecialSearch {
/**
* Show single interwiki link
*
- * @param SearchResult $result
- * @param string $lastInterwiki
- * @param array $terms
- * @param string $query
- * @param array $customCaptions iw prefix -> caption
+ * @param $result SearchResult
+ * @param $lastInterwiki String
+ * @param $terms Array
+ * @param $query String
+ * @param $customCaptions Array: iw prefix -> caption
*/
protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) {
wfProfileIn( __METHOD__ );
- global $wgContLang, $wgLang;
if( $result->isBrokenTitle() ) {
wfProfileOut( __METHOD__ );
@@ -719,12 +738,11 @@ class SpecialSearch {
/**
* Generates the power search box at bottom of [[Special:Search]]
- * @param $term string: search term
- * @return $out string: HTML form
+ *
+ * @param $term String: search term
+ * @return String: HTML form
*/
protected function powerSearchBox( $term ) {
- global $wgScript, $wgContLang;
-
// Groups namespaces into rows according to subject
$rows = array();
foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) {
@@ -809,19 +827,11 @@ class SpecialSearch {
$namespaceTables .
Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
$redirects .
- Xml::hidden( 'title', SpecialPage::getTitleFor( 'Search' )->getPrefixedText() ) .
- Xml::hidden( 'advanced', $this->searchAdvanced ) .
- Xml::hidden( 'fulltext', 'Advanced search' ) .
+ Html::hidden( 'title', SpecialPage::getTitleFor( 'Search' )->getPrefixedText() ) .
+ Html::hidden( 'advanced', $this->searchAdvanced ) .
+ Html::hidden( 'fulltext', 'Advanced search' ) .
Xml::closeElement( 'fieldset' );
}
-
- protected function searchFocus() {
- $id = $this->searchAdvanced ? 'powerSearchText' : 'searchText';
- return Html::inlineScript(
- "hookEvent(\"load\", function() {" .
- "document.getElementById('$id').focus();" .
- "});" );
- }
protected function getSearchProfiles() {
// Builds list of Search Types (profiles)
@@ -864,7 +874,7 @@ class SpecialSearch {
wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) );
- foreach( $profiles as $key => &$data ) {
+ foreach( $profiles as &$data ) {
sort($data['namespaces']);
}
@@ -872,7 +882,7 @@ class SpecialSearch {
}
protected function formHeader( $term, $resultsShown, $totalNum ) {
- global $wgContLang, $wgLang;
+ global $wgLang;
$out = Xml::openElement('div', array( 'class' => 'mw-search-formheader' ) );
@@ -882,7 +892,6 @@ class SpecialSearch {
$bareterm = substr( $term, strpos( $term, ':' ) + 1 );
}
-
$profiles = $this->getSearchProfiles();
// Outputs XML for Search Types
@@ -934,7 +943,7 @@ class SpecialSearch {
// Adds hidden namespace fields
if ( !$this->searchAdvanced ) {
foreach( $this->namespaces as $ns ) {
- $out .= Xml::hidden( "ns{$ns}", '1' );
+ $out .= Html::hidden( "ns{$ns}", '1' );
}
}
@@ -943,7 +952,6 @@ class SpecialSearch {
protected function shortDialog( $term ) {
$searchTitle = SpecialPage::getTitleFor( 'Search' );
- $searchable = SearchEngine::searchableNamespaces();
$out = Html::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n";
// Keep redirect setting
$out .= Html::hidden( "redirs", (int)$this->searchRedirects ) . "\n";
@@ -958,7 +966,16 @@ class SpecialSearch {
return $out . $this->didYouMeanHtml;
}
- /** Make a search link with some target namespaces */
+ /**
+ * Make a search link with some target namespaces
+ *
+ * @param $term String
+ * @param $namespaces Array
+ * @param $label String: link's text
+ * @param $tooltip String: link's tooltip
+ * @param $params Array: query string parameters
+ * @return String: HTML fragment
+ */
protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params=array() ) {
$opt = $params;
foreach( $namespaces as $n ) {
@@ -986,7 +1003,12 @@ class SpecialSearch {
);
}
- /** Check if query starts with image: prefix */
+ /**
+ * Check if query starts with image: prefix
+ *
+ * @param $term String: the string to check
+ * @return Boolean
+ */
protected function startsWithImage( $term ) {
global $wgContLang;
@@ -997,7 +1019,12 @@ class SpecialSearch {
return false;
}
- /** Check if query starts with all: prefix */
+ /**
+ * Check if query starts with all: prefix
+ *
+ * @param $term String: the string to check
+ * @return Boolean
+ */
protected function startsWithAll( $term ) {
$allkeyword = wfMsgForContent('searchall');
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index c41b15c5..989e4c07 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -1,5 +1,22 @@
<?php
/**
+ * Implements Special:Shortpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
@@ -7,6 +24,7 @@
/**
* SpecialShortpages extends QueryPage. It is used to return the shortest
* pages in the database.
+ *
* @ingroup SpecialPage
*/
class ShortPagesPage extends QueryPage {
@@ -54,11 +72,13 @@ class ShortPagesPage extends QueryPage {
# the page must exist for it to have been pulled out of the table
if( $this->isCached() ) {
$batch = new LinkBatch();
- while( $row = $db->fetchObject( $res ) )
+ foreach ( $res as $row ) {
$batch->add( $row->namespace, $row->title );
+ }
$batch->execute();
- if( $db->numRows( $res ) > 0 )
+ if ( $db->numRows( $res ) > 0 ) {
$db->dataSeek( $res, 0 );
+ }
}
}
@@ -83,11 +103,11 @@ class ShortPagesPage extends QueryPage {
$plink = $this->isCached()
? $skin->link( $title )
: $skin->linkKnown( $title );
- $size = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( htmlspecialchars( $result->value ) ) );
+ $size = wfMessage( 'nbytes', $wgLang->formatNum( $result->value ) )->escaped();
return $title->exists()
? "({$hlink}) {$dm}{$plink} {$dm}[{$size}]"
- : "<s>({$hlink}) {$dm}{$plink} {$dm}[{$size}]</s>";
+ : "<del>({$hlink}) {$dm}{$plink} {$dm}[{$size}]</del>";
}
}
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index 8e97f9b7..19bc6b00 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -1,84 +1,138 @@
<?php
/**
+ * Implements Special:Specialpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 lists special pages
*
+ * @ingroup SpecialPage
*/
-function wfSpecialSpecialpages() {
- global $wgOut, $wgUser, $wgMessageCache, $wgSortSpecialPages;
+class SpecialSpecialpages extends UnlistedSpecialPage {
+
+ function __construct() {
+ parent::__construct( 'Specialpages' );
+ }
- $wgMessageCache->loadAllMessages();
+ function execute( $par ) {
+ global $wgOut;
+ $this->setHeaders();
+ $this->outputHeader();
+ $wgOut->allowClickjacking();
- $wgOut->setRobotPolicy( 'noindex,nofollow' ); # Is this really needed?
- $wgOut->allowClickjacking();
- $sk = $wgUser->getSkin();
+ $groups = $this->getPageGroups();
- $pages = SpecialPage::getUsablePages();
+ if ( $groups === false ) {
+ return;
+ }
- if( count( $pages ) == 0 ) {
- # Yeah, that was pointless. Thanks for coming.
- return;
+ $this->outputPageList( $groups );
}
- /** Put them into a sortable array */
- $groups = array();
- foreach ( $pages as $page ) {
- if ( $page->isListed() ) {
- $group = SpecialPage::getGroup( $page );
- if( !isset($groups[$group]) ) {
- $groups[$group] = array();
+ private function getPageGroups() {
+ global $wgSortSpecialPages;
+
+ $pages = SpecialPage::getUsablePages();
+
+ if( !count( $pages ) ) {
+ # Yeah, that was pointless. Thanks for coming.
+ return false;
+ }
+
+ /** Put them into a sortable array */
+ $groups = array();
+ foreach ( $pages as $page ) {
+ if ( $page->isListed() ) {
+ $group = SpecialPage::getGroup( $page );
+ if( !isset( $groups[$group] ) ) {
+ $groups[$group] = array();
+ }
+ $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted() );
}
- $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted() );
}
- }
- /** Sort */
- if ( $wgSortSpecialPages ) {
- foreach( $groups as $group => $sortedPages ) {
- ksort( $groups[$group] );
+ /** Sort */
+ if ( $wgSortSpecialPages ) {
+ foreach( $groups as $group => $sortedPages ) {
+ ksort( $groups[$group] );
+ }
}
- }
- /** Always move "other" to end */
- if( array_key_exists('other',$groups) ) {
- $other = $groups['other'];
- unset( $groups['other'] );
- $groups['other'] = $other;
+ /** Always move "other" to end */
+ if( array_key_exists( 'other', $groups ) ) {
+ $other = $groups['other'];
+ unset( $groups['other'] );
+ $groups['other'] = $other;
+ }
+
+ return $groups;
}
- $includesRestrictedPages = false;
- /** Now output the HTML */
- foreach ( $groups as $group => $sortedPages ) {
- $middle = ceil( count($sortedPages)/2 );
- $total = count($sortedPages);
- $count = 0;
-
- $wgOut->wrapWikiMsg( "<h4 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h4>\n", "specialpages-group-$group" );
- $wgOut->addHTML( "<table style='width: 100%;' class='mw-specialpages-table'><tr>" );
- $wgOut->addHTML( "<td width='30%' valign='top'><ul>\n" );
- foreach( $sortedPages as $desc => $specialpage ) {
- list( $title, $restricted ) = $specialpage;
- $link = $sk->linkKnown( $title , htmlspecialchars( $desc ) );
- if( $restricted ) {
- $includesRestrictedPages = true;
- $wgOut->addHTML( "<li class='mw-specialpages-page mw-specialpagerestricted'><strong>{$link}</strong></li>\n" );
- } else {
- $wgOut->addHTML( "<li>{$link}</li>\n" );
- }
+ private function outputPageList( $groups ) {
+ global $wgUser, $wgOut;
- # Split up the larger groups
- $count++;
- if( $total > 3 && $count == $middle ) {
- $wgOut->addHTML( "</ul></td><td width='10%'></td><td width='30%' valign='top'><ul>" );
+ $sk = $wgUser->getSkin();
+ $includesRestrictedPages = false;
+
+ foreach ( $groups as $group => $sortedPages ) {
+ $middle = ceil( count( $sortedPages )/2 );
+ $total = count( $sortedPages );
+ $count = 0;
+
+ $wgOut->wrapWikiMsg( "<h4 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h4>\n", "specialpages-group-$group" );
+ $wgOut->addHTML(
+ Html::openElement( 'table', array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' ) ) ."\n" .
+ Html::openElement( 'tr' ) . "\n" .
+ Html::openElement( 'td', array( 'style' => 'width:30%;vertical-align:top' ) ) . "\n" .
+ Html::openElement( 'ul' ) . "\n"
+ );
+ foreach( $sortedPages as $desc => $specialpage ) {
+ list( $title, $restricted ) = $specialpage;
+ $link = $sk->linkKnown( $title , htmlspecialchars( $desc ) );
+ if( $restricted ) {
+ $includesRestrictedPages = true;
+ $wgOut->addHTML( Html::rawElement( 'li', array( 'class' => 'mw-specialpages-page mw-specialpagerestricted' ), Html::rawElement( 'strong', array(), $link ) ) . "\n" );
+ } else {
+ $wgOut->addHTML( Html::rawElement( 'li', array(), $link ) . "\n" );
+ }
+
+ # Split up the larger groups
+ $count++;
+ if( $total > 3 && $count == $middle ) {
+ $wgOut->addHTML(
+ Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) .
+ Html::element( 'td', array( 'style' => 'width:10%' ), '' ) .
+ Html::openElement( 'td', array( 'style' => 'width:30%' ) ) . Html::openElement( 'ul' ) . "\n"
+ );
+ }
}
+ $wgOut->addHTML(
+ Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) .
+ Html::element( 'td', array( 'style' => 'width:30%' ), '' ) .
+ Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n"
+ );
}
- $wgOut->addHTML( "</ul></td><td width='30%' valign='top'></td></tr></table>\n" );
- }
- if ( $includesRestrictedPages ) {
- $wgOut->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' );
+ if ( $includesRestrictedPages ) {
+ $wgOut->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' );
+ }
}
}
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index 2e785b8b..b0d0246e 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -1,31 +1,44 @@
<?php
-
/**
- * Special page lists various statistics, including the contents of
- * `site_stats`, plus page view details if enabled
+ * Implements Special:Statistics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * Show the special page
+ * Special page lists various statistics, including the contents of
+ * `site_stats`, plus page view details if enabled
*
- * @param mixed $par (not used)
+ * @ingroup SpecialPage
*/
class SpecialStatistics extends SpecialPage {
private $views, $edits, $good, $images, $total, $users,
- $activeUsers, $admins, $numJobs = 0;
+ $activeUsers, $admins = 0;
public function __construct() {
parent::__construct( 'Statistics' );
}
public function execute( $par ) {
- global $wgOut, $wgRequest, $wgMessageCache, $wgMemc;
+ global $wgOut, $wgMemc;
global $wgDisableCounters, $wgMiserMode;
- $wgMessageCache->loadAllMessages();
$this->setHeaders();
@@ -37,7 +50,6 @@ class SpecialStatistics extends SpecialPage {
$this->users = SiteStats::users();
$this->activeUsers = SiteStats::activeUsers();
$this->admins = SiteStats::numberingroup('sysop');
- $this->numJobs = SiteStats::jobs();
$this->hook = '';
# Staticic - views
@@ -56,11 +68,6 @@ class SpecialStatistics extends SpecialPage {
$wgMemc->set( $key, '1', 24*3600 ); // don't update for 1 day
}
}
-
- # Do raw output
- if( $wgRequest->getVal( 'action' ) == 'raw' ) {
- $this->doRawOutput();
- }
$text = Xml::openElement( 'table', array( 'class' => 'wikitable mw-statistics-table' ) );
@@ -101,15 +108,14 @@ class SpecialStatistics extends SpecialPage {
/**
* Format a row
- * @param string $text description of the row
- * @param float $number a number
- * @param array $trExtraParams
- * @param string $descMsg
- * @param string $descMsgParam
+ * @param $text String: description of the row
+ * @param $number Float: a statistical number
+ * @param $trExtraParams Array: params to table row, see Html::elememt
+ * @param $descMsg String: message key
+ * @param $descMsgParam Array: message params
* @return string table row in HTML format
*/
private function formatRow( $text, $number, $trExtraParams = array(), $descMsg = '', $descMsgParam = '' ) {
- global $wgStylePath;
if( $descMsg ) {
$descriptionText = wfMsgExt( $descMsg, array( 'parseinline' ), $descMsgParam );
if ( !wfEmptyMsg( $descMsg, $descriptionText ) ) {
@@ -118,10 +124,11 @@ class SpecialStatistics extends SpecialPage {
$descriptionText );
}
}
- return Xml::openElement( 'tr', $trExtraParams ) .
- Xml::openElement( 'td' ) . $text . Xml::closeElement( 'td' ) .
- Xml::openElement( 'td', array( 'class' => 'mw-statistics-numbers' ) ) . $number . Xml::closeElement( 'td' ) .
- Xml::closeElement( 'tr' );
+ return
+ Html::rawElement( 'tr', $trExtraParams,
+ Html::rawElement( 'td', array(), $text ) .
+ Html::rawElement( 'td', array( 'class' => 'mw-statistics-numbers' ), $number )
+ );
}
/**
@@ -155,13 +162,11 @@ class SpecialStatistics extends SpecialPage {
array( 'class' => 'mw-statistics-edits' ) ) .
$this->formatRow( wfMsgExt( 'statistics-edits-average', array( 'parseinline' ) ),
$wgLang->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
- array( 'class' => 'mw-statistics-edits-average' ) ) .
- $this->formatRow( wfMsgExt( 'statistics-jobqueue', array( 'parseinline' ) ),
- $wgLang->formatNum( $this->numJobs ),
- array( 'class' => 'mw-statistics-jobqueue' ) );
+ array( 'class' => 'mw-statistics-edits-average' ) );
}
+
private function getUserStats() {
- global $wgLang, $wgUser, $wgRCMaxAge;
+ global $wgLang, $wgUser, $wgActiveUserDays;
$sk = $wgUser->getSkin();
return Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-users', array( 'parseinline' ) ) ) .
@@ -180,8 +185,9 @@ class SpecialStatistics extends SpecialPage {
$wgLang->formatNum( $this->activeUsers ),
array( 'class' => 'mw-statistics-users-active' ),
'statistics-users-active-desc',
- $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600 * 24 ) ) ) );
+ $wgLang->formatNum( $wgActiveUserDays ) );
}
+
private function getGroupStats() {
global $wgGroupPermissions, $wgImplicitGroups, $wgLang, $wgUser;
$sk = $wgUser->getSkin();
@@ -228,6 +234,7 @@ class SpecialStatistics extends SpecialPage {
}
return $text;
}
+
private function getViewsStats() {
global $wgLang;
return Xml::openElement( 'tr' ) .
@@ -235,12 +242,13 @@ class SpecialStatistics extends SpecialPage {
Xml::closeElement( 'tr' ) .
$this->formatRow( wfMsgExt( 'statistics-views-total', array( 'parseinline' ) ),
$wgLang->formatNum( $this->views ),
- array ( 'class' => 'mw-statistics-views-total' ) ) .
+ array ( 'class' => 'mw-statistics-views-total' ), 'statistics-views-total-desc' ) .
$this->formatRow( wfMsgExt( 'statistics-views-peredit', array( 'parseinline' ) ),
$wgLang->formatNum( sprintf( '%.2f', $this->edits ?
$this->views / $this->edits : 0 ) ),
array ( 'class' => 'mw-statistics-views-peredit' ) );
}
+
private function getMostViewedPages() {
global $wgLang, $wgUser;
$text = '';
@@ -267,7 +275,7 @@ class SpecialStatistics extends SpecialPage {
$text .= Xml::openElement( 'tr' );
$text .= Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-mostpopular', array( 'parseinline' ) ) );
$text .= Xml::closeElement( 'tr' );
- while( $row = $res->fetchObject() ) {
+ foreach ( $res as $row ) {
$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
if( $title instanceof Title ) {
$text .= $this->formatRow( $sk->link( $title ),
@@ -279,7 +287,7 @@ class SpecialStatistics extends SpecialPage {
}
return $text;
}
-
+
private function getOtherStats( $stats ) {
global $wgLang;
@@ -299,20 +307,4 @@ class SpecialStatistics extends SpecialPage {
return $return;
}
-
- /**
- * Do the action=raw output for this page. Legacy, but we support
- * it for backwards compatibility
- * http://lists.wikimedia.org/pipermail/wikitech-l/2008-August/039202.html
- */
- private function doRawOutput() {
- global $wgOut;
- $wgOut->disable();
- header( 'Pragma: nocache' );
- echo "total=" . $this->total . ";good=" . $this->good . ";views=" .
- $this->views . ";edits=" . $this->edits . ";users=" . $this->users . ";";
- echo "activeusers=" . $this->activeUsers . ";admins=" . $this->admins .
- ";images=" . $this->images . ";jobs=" . $this->numJobs . "\n";
- return;
- }
-} \ No newline at end of file
+}
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index 57feeae7..c2aecf47 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -1,8 +1,34 @@
<?php
+/**
+ * Implements Special:Tags
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
+ */
if (!defined('MEDIAWIKI'))
die;
+/**
+ * A special page that lists tags for edits
+ *
+ * @ingroup SpecialPage
+ */
class SpecialTags extends SpecialPage {
function __construct() {
@@ -10,16 +36,12 @@ class SpecialTags extends SpecialPage {
}
function execute( $par ) {
- global $wgOut, $wgUser, $wgMessageCache;
+ global $wgOut;
- $wgMessageCache->loadAllMessages();
-
- $sk = $wgUser->getSkin();
$wgOut->setPageTitle( wfMsg( 'tags-title' ) );
- $wgOut->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1</div>", 'tags-intro' );
+ $wgOut->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1\n</div>", 'tags-intro' );
// Write the headers
- $html = '';
$html = Xml::tags( 'tr', null, Xml::tags( 'th', null, wfMsgExt( 'tags-tag', 'parseinline' ) ) .
Xml::tags( 'th', null, wfMsgExt( 'tags-display-header', 'parseinline' ) ) .
Xml::tags( 'th', null, wfMsgExt( 'tags-description-header', 'parseinline' ) ) .
@@ -28,7 +50,7 @@ class SpecialTags extends SpecialPage {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) as hitcount' ), array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) );
- while ( $row = $res->fetchObject() ) {
+ foreach ( $res as $row ) {
$html .= $this->doTagRow( $row->ct_tag, $row->hitcount );
}
@@ -72,4 +94,4 @@ class SpecialTags extends SpecialPage {
return Xml::tags( 'tr', null, $newRow ) . "\n";
}
-} \ No newline at end of file
+}
diff --git a/includes/specials/SpecialUncategorizedcategories.php b/includes/specials/SpecialUncategorizedcategories.php
index f23e89ce..9574af70 100644
--- a/includes/specials/SpecialUncategorizedcategories.php
+++ b/includes/specials/SpecialUncategorizedcategories.php
@@ -1,15 +1,33 @@
<?php
/**
+ * Implements Special:Uncategorizedcategories
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
/**
- * implements Special:Uncategorizedcategories
+ * A special page that lists uncategorized categories
+ *
* @ingroup SpecialPage
*/
class UncategorizedCategoriesPage extends UncategorizedPagesPage {
- function UncategorizedCategoriesPage() {
+ function __construct() {
$this->requestedNamespace = NS_CATEGORY;
}
diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php
index 25310081..c4254039 100644
--- a/includes/specials/SpecialUncategorizedimages.php
+++ b/includes/specials/SpecialUncategorizedimages.php
@@ -1,6 +1,21 @@
<?php
/**
- * Special page lists images which haven't been categorised
+ * Implements Special:Uncategorizedimages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
@@ -8,6 +23,8 @@
*/
/**
+ * Special page lists images which haven't been categorised
+ *
* @ingroup SpecialPage
*/
class UncategorizedImagesPage extends ImageQueryPage {
diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php
index e7f0aaca..c7fef5d2 100644
--- a/includes/specials/SpecialUncategorizedpages.php
+++ b/includes/specials/SpecialUncategorizedpages.php
@@ -1,11 +1,29 @@
<?php
/**
+ * Implements Special:Uncategorizedpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 looking for page without any category.
+ *
* @ingroup SpecialPage
*/
class UncategorizedPagesPage extends PageQueryPage {
diff --git a/includes/specials/SpecialUncategorizedtemplates.php b/includes/specials/SpecialUncategorizedtemplates.php
index 7e6fd24b..aa4e979d 100644
--- a/includes/specials/SpecialUncategorizedtemplates.php
+++ b/includes/specials/SpecialUncategorizedtemplates.php
@@ -1,7 +1,25 @@
<?php
/**
+ * Implements Special:Uncategorizedtemplates
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
*/
/**
@@ -9,7 +27,6 @@
* template namespace
*
* @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>
*/
class UncategorizedTemplatesPage extends UncategorizedPagesPage {
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index 4db4e633..1cf61d26 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -1,25 +1,29 @@
<?php
-
/**
- * Special page allowing users with the appropriate permissions to view
- * and restore deleted content
+ * Implements Special:Undelete
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * Constructor
- */
-function wfSpecialUndelete( $par ) {
- global $wgRequest;
-
- $form = new UndeleteForm( $wgRequest, $par );
- $form->execute();
-}
-
-/**
* Used to show archived pages and eventually restore them.
+ *
* @ingroup SpecialPage
*/
class PageArchive {
@@ -28,7 +32,7 @@ class PageArchive {
function __construct( $title ) {
if( is_null( $title ) ) {
- throw new MWException( 'Archiver() given a null title.');
+ throw new MWException( __METHOD__ . ' given a null title.' );
}
$this->title = $title;
}
@@ -50,6 +54,7 @@ class PageArchive {
* given title prefix.
* Returns result wrapper with (ar_namespace, ar_title, count) fields.
*
+ * @param $prefix String: title prefix
* @return ResultWrapper
*/
public static function listPagesByPrefix( $prefix ) {
@@ -153,7 +158,8 @@ class PageArchive {
* Fetch (and decompress if necessary) the stored text for the deleted
* revision of the page with the given timestamp.
*
- * @return string
+ * @param $timestamp String
+ * @return String
* @deprecated Use getRevision() for more flexible information
*/
function getRevisionText( $timestamp ) {
@@ -164,7 +170,8 @@ class PageArchive {
/**
* Return a Revision object containing data for the deleted revision.
* Note that the result *may* or *may not* have a null page ID.
- * @param string $timestamp
+ *
+ * @param $timestamp String
* @return Revision
*/
function getRevision( $timestamp ) {
@@ -200,7 +207,7 @@ class PageArchive {
* May produce unexpected results in case of history merges or other
* unusual time issues.
*
- * @param string $timestamp
+ * @param $timestamp String
* @return Revision or null
*/
function getPreviousRevision( $timestamp ) {
@@ -248,6 +255,9 @@ class PageArchive {
/**
* Get the text from an archive row containing ar_text, ar_flags and ar_text_id
+ *
+ * @param $row Object: database row
+ * @return Revision
*/
function getTextFromRow( $row ) {
if( is_null( $row->ar_text_id ) ) {
@@ -272,7 +282,7 @@ class PageArchive {
*
* If there are no archived revisions for the page, returns NULL.
*
- * @return string
+ * @return String
*/
function getLastRevisionText() {
$dbr = wfGetDB( DB_SLAVE );
@@ -280,7 +290,7 @@ class PageArchive {
array( 'ar_text', 'ar_flags', 'ar_text_id' ),
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey() ),
- 'PageArchive::getLastRevisionText',
+ __METHOD__,
array( 'ORDER BY' => 'ar_timestamp DESC' ) );
if( $row ) {
return $this->getTextFromRow( $row );
@@ -291,7 +301,8 @@ class PageArchive {
/**
* Quick check if any archived revisions are present for the page.
- * @return bool
+ *
+ * @return Boolean
*/
function isDeleted() {
$dbr = wfGetDB( DB_SLAVE );
@@ -306,10 +317,10 @@ class PageArchive {
* Once restored, the items will be removed from the archive tables.
* The deletion log will be updated with an undeletion notice.
*
- * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
- * @param string $comment
- * @param array $fileVersions
- * @param bool $unsuppress
+ * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param $comment String
+ * @param $fileVersions Array
+ * @param $unsuppress Boolean
*
* @return array(number of file revisions restored, number of image revisions restored, log message)
* on success, false on failure
@@ -369,12 +380,11 @@ class PageArchive {
* to the cur/old tables. If the page currently exists, all revisions will
* be stuffed into old, otherwise the most recent will go into cur.
*
- * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
- * @param string $comment
- * @param array $fileVersions
- * @param bool $unsuppress, remove all ar_deleted/fa_deleted restrictions of seletected revs
+ * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param $comment String
+ * @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs
*
- * @return mixed number of revisions restored or false on failure
+ * @return Mixed: number of revisions restored or false on failure
*/
private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
if ( wfReadOnly() )
@@ -482,7 +492,7 @@ class PageArchive {
$revision = null;
$restored = 0;
- while( $row = $ret->fetchObject() ) {
+ foreach ( $ret as $row ) {
// Check for key dupes due to shitty archive integrity.
if( $row->ar_rev_id ) {
$exists = $dbw->selectField( 'revision', '1', array('rev_id' => $row->ar_rev_id), __METHOD__ );
@@ -490,12 +500,12 @@ class PageArchive {
}
// Insert one revision at a time...maintaining deletion status
// unless we are specifically removing all restrictions...
- $revision = Revision::newFromArchiveRow( $row,
- array(
- 'page' => $pageId,
+ $revision = Revision::newFromArchiveRow( $row,
+ array(
+ 'page' => $pageId,
'deleted' => $unsuppress ? 0 : $row->ar_deleted
) );
-
+
$revision->insertOn( $dbw );
$restored++;
@@ -508,7 +518,7 @@ class PageArchive {
'ar_title' => $this->title->getDBkey(),
$oldones ),
__METHOD__ );
-
+
// Was anything restored at all?
if( $restored == 0 )
return 0;
@@ -535,7 +545,8 @@ class PageArchive {
}
} else {
// Revision couldn't be created. This is very weird
- return self::UNDELETE_UNKNOWNERR;
+ wfDebug( "Undelete: unknown error...\n" );
+ return false;
}
return $restored;
@@ -545,36 +556,45 @@ class PageArchive {
}
/**
- * The HTML form for Special:Undelete, which allows users with the appropriate
- * permissions to view and restore deleted content.
+ * Special page allowing users with the appropriate permissions to view
+ * and restore deleted content.
+ *
* @ingroup SpecialPage
*/
-class UndeleteForm {
+class UndeleteForm extends SpecialPage {
var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mTargetObj;
- var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken;
+ var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken, $mRequest;
- function UndeleteForm( $request, $par = "" ) {
+ function __construct( $request = null ) {
+ parent::__construct( 'Undelete', 'deletedhistory' );
+
+ if ( $request === null ) {
+ global $wgRequest;
+ $this->mRequest = $wgRequest;
+ } else {
+ $this->mRequest = $request;
+ }
+ }
+
+ function loadRequest() {
global $wgUser;
- $this->mAction = $request->getVal( 'action' );
- $this->mTarget = $request->getVal( 'target' );
- $this->mSearchPrefix = $request->getText( 'prefix' );
- $time = $request->getVal( 'timestamp' );
+ $this->mAction = $this->mRequest->getVal( 'action' );
+ $this->mTarget = $this->mRequest->getVal( 'target' );
+ $this->mSearchPrefix = $this->mRequest->getText( 'prefix' );
+ $time = $this->mRequest->getVal( 'timestamp' );
$this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
- $this->mFile = $request->getVal( 'file' );
-
- $posted = $request->wasPosted() &&
- $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) );
- $this->mRestore = $request->getCheck( 'restore' ) && $posted;
- $this->mInvert = $request->getCheck( 'invert' ) && $posted;
- $this->mPreview = $request->getCheck( 'preview' ) && $posted;
- $this->mDiff = $request->getCheck( 'diff' );
- $this->mComment = $request->getText( 'wpComment' );
- $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
- $this->mToken = $request->getVal( 'token' );
-
- if( $par != "" ) {
- $this->mTarget = $par;
- }
+ $this->mFile = $this->mRequest->getVal( 'file' );
+
+ $posted = $this->mRequest->wasPosted() &&
+ $wgUser->matchEditToken( $this->mRequest->getVal( 'wpEditToken' ) );
+ $this->mRestore = $this->mRequest->getCheck( 'restore' ) && $posted;
+ $this->mInvert = $this->mRequest->getCheck( 'invert' ) && $posted;
+ $this->mPreview = $this->mRequest->getCheck( 'preview' ) && $posted;
+ $this->mDiff = $this->mRequest->getCheck( 'diff' );
+ $this->mComment = $this->mRequest->getText( 'wpComment' );
+ $this->mUnsuppress = $this->mRequest->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
+ $this->mToken = $this->mRequest->getVal( 'token' );
+
if ( $wgUser->isAllowed( 'undelete' ) && !$wgUser->isBlocked() ) {
$this->mAllowed = true; // user can restore
$this->mCanView = true; // user can view content
@@ -587,11 +607,7 @@ class UndeleteForm {
$this->mTimestamp = '';
$this->mRestore = false;
}
- if ( $this->mTarget !== "" ) {
- $this->mTargetObj = Title::newFromURL( $this->mTarget );
- } else {
- $this->mTargetObj = null;
- }
+
if( $this->mRestore || $this->mInvert ) {
$timestamps = array();
$this->mFileVersions = array();
@@ -610,14 +626,33 @@ class UndeleteForm {
}
}
- function execute() {
+ function execute( $par ) {
global $wgOut, $wgUser;
+
+ $this->setHeaders();
+ if ( !$this->userCanExecute( $wgUser ) ) {
+ $this->displayRestrictionError();
+ return;
+ }
+ $this->outputHeader();
+
+ $this->loadRequest();
+
if ( $this->mAllowed ) {
$wgOut->setPagetitle( wfMsg( "undeletepage" ) );
} else {
$wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) );
}
+ if( $par != '' ) {
+ $this->mTarget = $par;
+ }
+ if ( $this->mTarget !== '' ) {
+ $this->mTargetObj = Title::newFromURL( $this->mTarget );
+ } else {
+ $this->mTargetObj = null;
+ }
+
if( is_null( $this->mTargetObj ) ) {
# Not all users can just browse every deleted page from the list
if( $wgUser->isAllowed( 'browsearchive' ) ) {
@@ -659,7 +694,7 @@ class UndeleteForm {
if( $this->mRestore && $this->mAction == "submit" ) {
global $wgUploadMaintenance;
if( $wgUploadMaintenance && $this->mTargetObj && $this->mTargetObj->getNamespace() == NS_FILE ) {
- $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n", array( 'filedelete-maintenance' ) );
+ $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'filedelete-maintenance' ) );
return;
}
return $this->undelete();
@@ -679,8 +714,8 @@ class UndeleteForm {
'method' => 'get',
'action' => $wgScript ) ) .
Xml::fieldset( wfMsg( 'undelete-search-box' ) ) .
- Xml::hidden( 'title',
- SpecialPage::getTitleFor( 'Undelete' )->getPrefixedDbKey() ) .
+ Html::hidden( 'title',
+ $this->getTitle()->getPrefixedDbKey() ) .
Xml::inputLabel( wfMsg( 'undelete-search-prefix' ),
'prefix', 'prefix', 20,
$this->mSearchPrefix ) . ' ' .
@@ -692,7 +727,7 @@ class UndeleteForm {
// Generic list of deleted pages
private function showList( $result ) {
- global $wgLang, $wgContLang, $wgUser, $wgOut;
+ global $wgLang, $wgUser, $wgOut;
if( $result->numRows() == 0 ) {
$wgOut->addWikiMsg( 'undelete-no-results' );
@@ -702,9 +737,9 @@ class UndeleteForm {
$wgOut->addWikiMsg( 'undeletepagetext', $wgLang->formatNum( $result->numRows() ) );
$sk = $wgUser->getSkin();
- $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $undelete = $this->getTitle();
$wgOut->addHTML( "<ul>\n" );
- while( $row = $result->fetchObject() ) {
+ foreach ( $result as $row ) {
$title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
$link = $sk->linkKnown(
$undelete,
@@ -725,7 +760,7 @@ class UndeleteForm {
private function showRevision( $timestamp ) {
global $wgLang, $wgUser, $wgOut;
- $self = SpecialPage::getTitleFor( 'Undelete' );
+
$skin = $wgUser->getSkin();
if(!preg_match("/[0-9]{14}/",$timestamp)) return 0;
@@ -740,10 +775,10 @@ class UndeleteForm {
if( $rev->isDeleted(Revision::DELETED_TEXT) ) {
if( !$rev->userCan(Revision::DELETED_TEXT) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' );
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
return;
} else {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' );
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
$wgOut->addHTML( '<br />' );
// and we are allowed to see...
}
@@ -752,7 +787,7 @@ class UndeleteForm {
$wgOut->setPageTitle( wfMsg( 'undeletepage' ) );
$link = $skin->linkKnown(
- SpecialPage::getTitleFor( 'Undelete', $this->mTargetObj->getPrefixedDBkey() ),
+ $this->getTitle( $this->mTargetObj->getPrefixedDBkey() ),
htmlspecialchars( $this->mTargetObj->getPrefixedText() )
);
@@ -823,7 +858,7 @@ class UndeleteForm {
Xml::openElement( 'div' ) .
Xml::openElement( 'form', array(
'method' => 'post',
- 'action' => $self->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
+ 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
Xml::element( 'input', array(
'type' => 'hidden',
'name' => 'target',
@@ -851,14 +886,15 @@ class UndeleteForm {
/**
* Build a diff display between this and the previous either deleted
* or non-deleted edit.
- * @param Revision $previousRev
- * @param Revision $currentRev
- * @return string HTML
+ *
+ * @param $previousRev Revision
+ * @param $currentRev Revision
+ * @return String: HTML
*/
function showDiff( $previousRev, $currentRev ) {
global $wgOut;
- $diffEngine = new DifferenceEngine();
+ $diffEngine = new DifferenceEngine( $previousRev->getTitle() );
$diffEngine->showDiffStyle();
$wgOut->addHTML(
"<div>" .
@@ -888,7 +924,7 @@ class UndeleteForm {
$isDeleted = !( $rev->getId() && $rev->getTitle() );
if( $isDeleted ) {
/// @todo Fixme: $rev->getTitle() is null for deleted revs...?
- $targetPage = SpecialPage::getTitleFor( 'Undelete' );
+ $targetPage = $this->getTitle();
$targetQuery = array(
'target' => $this->mTargetObj->getPrefixedText(),
'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
@@ -905,7 +941,7 @@ class UndeleteForm {
if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
$del .= $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
} else {
- $query = array(
+ $query = array(
'type' => 'archive',
'target' => $this->mTargetObj->getPrefixedDbkey(),
'ids' => $rev->getTimestamp()
@@ -948,10 +984,10 @@ class UndeleteForm {
$this->mTargetObj->getText(),
$wgLang->date( $file->getTimestamp() ),
$wgLang->time( $file->getTimestamp() ) );
- $wgOut->addHTML(
- Xml::openElement( 'form', array(
+ $wgOut->addHTML(
+ Xml::openElement( 'form', array(
'method' => 'POST',
- 'action' => SpecialPage::getTitleFor( 'Undelete' )->getLocalUrl(
+ 'action' => $this->getTitle()->getLocalUrl(
'target=' . urlencode( $this->mTarget ) .
'&file=' . urlencode( $key ) .
'&token=' . urlencode( $wgUser->editToken( $key ) ) )
@@ -979,13 +1015,13 @@ class UndeleteForm {
global $IP;
require_once( "$IP/includes/StreamFile.php" );
- $repo = RepoGroup::singleton()->getLocalRepo();
+ $repo = RepoGroup::singleton()->getLocalRepo();
$path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
wfStreamFile( $path );
}
private function showHistory( ) {
- global $wgLang, $wgUser, $wgOut;
+ global $wgUser, $wgOut;
$sk = $wgUser->getSkin();
if( $this->mAllowed ) {
@@ -994,7 +1030,7 @@ class UndeleteForm {
$wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) );
}
- $wgOut->wrapWikiMsg( "<div class='mw-undelete-pagetitle'>\n$1</div>\n", array ( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() ) );
+ $wgOut->wrapWikiMsg( "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n", array ( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() ) );
$archive = new PageArchive( $this->mTargetObj );
/*
@@ -1023,7 +1059,7 @@ class UndeleteForm {
# Batch existence check on user and talk pages
if( $haveRevisions ) {
$batch = new LinkBatch();
- while( $row = $revisions->fetchObject() ) {
+ foreach ( $revisions as $row ) {
$batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
$batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) );
}
@@ -1032,7 +1068,7 @@ class UndeleteForm {
}
if( $haveFiles ) {
$batch = new LinkBatch();
- while( $row = $files->fetchObject() ) {
+ foreach ( $files as $row ) {
$batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) );
$batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) );
}
@@ -1041,8 +1077,7 @@ class UndeleteForm {
}
if ( $this->mAllowed ) {
- $titleObj = SpecialPage::getTitleFor( "Undelete" );
- $action = $titleObj->getLocalURL( array( 'action' => 'submit' ) );
+ $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
$top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) );
$wgOut->addHTML( $top );
@@ -1063,7 +1098,7 @@ class UndeleteForm {
if( $wgUser->isAllowed( 'suppressrevision' ) ) {
$unsuppressBox =
"<tr>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg('revdelete-unsuppress'), 'wpUnsuppress',
'mw-undelete-unsuppress', $this->mUnsuppress ).
@@ -1089,7 +1124,7 @@ class UndeleteForm {
"</td>
</tr>
<tr>
- <td>&nbsp;</td>
+ <td>&#160;</td>
<td class='mw-submit'>" .
Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' .
Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) . ' ' .
@@ -1108,11 +1143,10 @@ class UndeleteForm {
if( $haveRevisions ) {
# The page's stored (deleted) history:
$wgOut->addHTML("<ul>");
- $target = urlencode( $this->mTarget );
$remaining = $revisions->numRows();
$earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
- while( $row = $revisions->fetchObject() ) {
+ foreach ( $revisions as $row ) {
$remaining--;
$wgOut->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) );
}
@@ -1125,7 +1159,7 @@ class UndeleteForm {
if( $haveFiles ) {
$wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" );
$wgOut->addHTML( "<ul>" );
- while( $row = $files->fetchObject() ) {
+ foreach ( $files as $row ) {
$wgOut->addHTML( $this->formatFileRow( $row, $sk ) );
}
$files->free();
@@ -1134,8 +1168,8 @@ class UndeleteForm {
if ( $this->mAllowed ) {
# Slip in the hidden controls here
- $misc = Xml::hidden( 'target', $this->mTarget );
- $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() );
+ $misc = Html::hidden( 'target', $this->mTarget );
+ $misc .= Html::hidden( 'wpEditToken', $wgUser->editToken() );
$misc .= Xml::closeElement( 'form' );
$wgOut->addHTML( $misc );
}
@@ -1146,7 +1180,7 @@ class UndeleteForm {
private function formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) {
global $wgUser, $wgLang;
- $rev = Revision::newFromArchiveRow( $row,
+ $rev = Revision::newFromArchiveRow( $row,
array( 'page' => $this->mTargetObj->getArticleId() ) );
$stxt = '';
$ts = wfTimestamp( TS_MW, $row->ar_timestamp );
@@ -1166,7 +1200,7 @@ class UndeleteForm {
}
// Build page & diff links...
if( $this->mCanView ) {
- $titleObj = SpecialPage::getTitleFor( "Undelete" );
+ $titleObj = $this->getTitle();
# Last link
if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
$pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
@@ -1228,14 +1262,12 @@ class UndeleteForm {
if( $this->mAllowed && $row->fa_storage_key ) {
$checkBox = Xml::check( "fileid" . $row->fa_id );
$key = urlencode( $row->fa_storage_key );
- $target = urlencode( $this->mTarget );
- $titleObj = SpecialPage::getTitleFor( "Undelete" );
- $pageLink = $this->getFileLink( $file, $titleObj, $ts, $key, $sk );
+ $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key, $sk );
} else {
$checkBox = '';
$pageLink = $wgLang->timeanddate( $ts, true );
}
- $userLink = $this->getFileUser( $file, $sk );
+ $userLink = $this->getFileUser( $file, $sk );
$data =
wfMsg( 'widthheight',
$wgLang->formatNum( $row->fa_width ),
@@ -1294,7 +1326,8 @@ class UndeleteForm {
/**
* Fetch image view link if it's available to all users
- * @return string
+ *
+ * @return String: HTML fragment
*/
function getFileLink( $file, $titleObj, $ts, $key, $sk ) {
global $wgLang, $wgUser;
@@ -1320,7 +1353,8 @@ class UndeleteForm {
/**
* Fetch file's user id if it's available to this user
- * @return string
+ *
+ * @return String: HTML fragment
*/
function getFileUser( $file, $sk ) {
if( !$file->userCan(File::DELETED_USER) ) {
@@ -1336,7 +1370,8 @@ class UndeleteForm {
/**
* Fetch file upload comment if it's available to this user
- * @return string
+ *
+ * @return String: HTML fragment
*/
function getFileComment( $file, $sk ) {
if( !$file->userCan(File::DELETED_COMMENT) ) {
diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php
index fe38a48a..c71b554b 100644
--- a/includes/specials/SpecialUnlockdb.php
+++ b/includes/specials/SpecialUnlockdb.php
@@ -1,39 +1,62 @@
<?php
/**
+ * Implements Special:Unlockdb
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
/**
+ * Implements Special:Unlockdb
*
+ * @ingroup SpecialPage
*/
-function wfSpecialUnlockdb() {
- global $wgUser, $wgOut, $wgRequest;
+class SpecialUnlockdb extends SpecialPage {
- if( !$wgUser->isAllowed( 'siteadmin' ) ) {
- $wgOut->permissionRequired( 'siteadmin' );
- return;
+ public function __construct() {
+ parent::__construct( 'Unlockdb', 'siteadmin' );
}
- $action = $wgRequest->getVal( 'action' );
- $f = new DBUnlockForm();
+ public function execute( $par ) {
+ global $wgUser, $wgOut, $wgRequest;
+
+ $this->setHeaders();
+
+ if( !$wgUser->isAllowed( 'siteadmin' ) ) {
+ $wgOut->permissionRequired( 'siteadmin' );
+ return;
+ }
+
+ $this->outputHeader();
+
+ $action = $wgRequest->getVal( 'action' );
- if ( "success" == $action ) {
- $f->showSuccess();
- } else if ( "submit" == $action && $wgRequest->wasPosted() &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
- $f->doSubmit();
- } else {
- $f->showForm( "" );
+ if ( $action == 'success' ) {
+ $this->showSuccess();
+ } else if ( $action == 'submit' && $wgRequest->wasPosted() &&
+ $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
+ $this->doSubmit();
+ } else {
+ $this->showForm();
+ }
}
-}
-/**
- * @ingroup SpecialPage
- */
-class DBUnlockForm {
- function showForm( $err )
- {
+ private function showForm( $err = '' ) {
global $wgOut, $wgUser;
global $wgReadOnlyFile;
@@ -42,65 +65,57 @@ class DBUnlockForm {
return;
}
- $wgOut->setPagetitle( wfMsg( "unlockdb" ) );
- $wgOut->addWikiMsg( "unlockdbtext" );
+ $wgOut->addWikiMsg( 'unlockdbtext' );
- if ( $err != "" ) {
- $wgOut->setSubtitle( wfMsg( "formerror" ) );
+ if ( $err != '' ) {
+ $wgOut->setSubtitle( wfMsg( 'formerror' ) );
$wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" );
}
- $lc = htmlspecialchars( wfMsg( "unlockconfirm" ) );
- $lb = htmlspecialchars( wfMsg( "unlockbtn" ) );
- $titleObj = SpecialPage::getTitleFor( "Unlockdb" );
- $action = $titleObj->escapeLocalURL( "action=submit" );
- $token = htmlspecialchars( $wgUser->editToken() );
- $wgOut->addHTML( <<<HTML
-
-<form id="unlockdb" method="post" action="{$action}">
-<table border="0">
+ $wgOut->addHTML(
+ Html::openElement( 'form', array( 'id' => 'unlockdb', 'method' => 'POST',
+ 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) ) . "
+<table>
<tr>
- <td align="right">
- <input type="checkbox" name="wpLockConfirm" />
+ " . Html::openElement( 'td', array( 'style' => 'text-align:right' ) ) . "
+ " . Html::input( 'wpLockConfirm', null, 'checkbox' ) . "
</td>
- <td align="left">{$lc}</td>
+ " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) .
+ wfMsgHtml( 'unlockconfirm' ) . "</td>
</tr>
<tr>
- <td>&nbsp;</td>
- <td align="left">
- <input type="submit" name="wpLock" value="{$lb}" />
+ <td>&#160;</td>
+ " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) . "
+ " . Html::input( 'wpLock', wfMsg( 'unlockbtn' ), 'submit' ) . "
</td>
</tr>
-</table>
-<input type="hidden" name="wpEditToken" value="{$token}" />
-</form>
-HTML
-);
+</table>\n" .
+ Html::hidden( 'wpEditToken', $wgUser->editToken() ) . "\n" .
+ Html::closeElement( 'form' )
+ );
}
- function doSubmit() {
+ private function doSubmit() {
global $wgOut, $wgRequest, $wgReadOnlyFile;
$wpLockConfirm = $wgRequest->getCheck( 'wpLockConfirm' );
- if ( ! $wpLockConfirm ) {
- $this->showForm( wfMsg( "locknoconfirm" ) );
+ if ( !$wpLockConfirm ) {
+ $this->showForm( wfMsg( 'locknoconfirm' ) );
return;
}
- if ( @! unlink( $wgReadOnlyFile ) ) {
+ if ( @!unlink( $wgReadOnlyFile ) ) {
$wgOut->showFileDeleteError( $wgReadOnlyFile );
return;
}
- $titleObj = SpecialPage::getTitleFor( "Unlockdb" );
- $success = $titleObj->getFullURL( "action=success" );
- $wgOut->redirect( $success );
+
+ $wgOut->redirect( $this->getTitle()->getFullURL( 'action=success' ) );
}
- function showSuccess() {
+ private function showSuccess() {
global $wgOut;
- $wgOut->setPagetitle( wfMsg( "unlockdb" ) );
- $wgOut->setSubtitle( wfMsg( "unlockdbsuccesssub" ) );
- $wgOut->addWikiMsg( "unlockdbsuccesstext" );
+ $wgOut->setSubtitle( wfMsg( 'unlockdbsuccesssub' ) );
+ $wgOut->addWikiMsg( 'unlockdbsuccesstext' );
}
}
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
index fe7d7a17..a20efe09 100644
--- a/includes/specials/SpecialUnusedcategories.php
+++ b/includes/specials/SpecialUnusedcategories.php
@@ -1,5 +1,22 @@
<?php
/**
+ * Implements Special:Unusedcategories
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php
index 9d9868f6..091ec3a3 100644
--- a/includes/specials/SpecialUnusedimages.php
+++ b/includes/specials/SpecialUnusedimages.php
@@ -1,11 +1,29 @@
<?php
/**
+ * Implements Special:Unusedimages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
/**
- * implements Special:Unusedimages
+ * A special page that lists unused images
+ *
* @ingroup SpecialPage
*/
class UnusedimagesPage extends ImageQueryPage {
@@ -22,22 +40,11 @@ class UnusedimagesPage extends ImageQueryPage {
function isSyndicated() { return false; }
function getSQL() {
- global $wgCountCategorizedImagesAsUsed, $wgDBtype;
+ global $wgCountCategorizedImagesAsUsed;
+
$dbr = wfGetDB( DB_SLAVE );
- switch ($wgDBtype) {
- case 'mysql':
- $epoch = 'UNIX_TIMESTAMP(img_timestamp)';
- break;
- case 'oracle':
- $epoch = '((trunc(img_timestamp) - to_date(\'19700101\',\'YYYYMMDD\')) * 86400)';
- break;
- case 'sqlite':
- $epoch = 'img_timestamp';
- break;
- default:
- $epoch = 'EXTRACT(epoch FROM img_timestamp)';
- }
+ $epoch = $dbr->unixTimestamp( 'img_timestamp' );
if ( $wgCountCategorizedImagesAsUsed ) {
list( $page, $image, $imagelinks, $categorylinks ) = $dbr->tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' );
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
index 6ddbab32..68bf95a2 100644
--- a/includes/specials/SpecialUnusedtemplates.php
+++ b/includes/specials/SpecialUnusedtemplates.php
@@ -1,14 +1,32 @@
<?php
/**
+ * Implements Special:Unusedtemplates
+ *
+ * Copyright © 2006 Rob Church
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
*/
/**
- * implements Special:Unusedtemplates
- * @author Rob Church <robchur@gmail.com>
- * @copyright © 2006 Rob Church
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * A special page that lists unused templates
+ *
* @ingroup SpecialPage
*/
class UnusedtemplatesPage extends QueryPage {
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
index 483afdaa..ecd62cb7 100644
--- a/includes/specials/SpecialUnwatchedpages.php
+++ b/includes/specials/SpecialUnwatchedpages.php
@@ -1,17 +1,33 @@
<?php
/**
+ * Implements Special:Unwatchedpages
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
*/
/**
* A special page that displays a list of pages that are not on anyones watchlist.
- * Implements Special:Unwatchedpages
*
* @ingroup SpecialPage
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
class UnwatchedpagesPage extends QueryPage {
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 68ee8efc..893e4be2 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -1,18 +1,38 @@
<?php
/**
+ * Implements Special:Upload
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
* @ingroup Upload
- *
+ */
+
+/**
* Form for handling uploads and special page.
*
+ * @ingroup SpecialPage
+ * @ingroup Upload
*/
-
class SpecialUpload extends SpecialPage {
/**
* Constructor : initialise object
* Get data POSTed through the form and assign them to the object
- * @param WebRequest $request Data posted.
+ * @param $request WebRequest : data posted.
*/
public function __construct( $request = null ) {
global $wgRequest;
@@ -50,12 +70,11 @@ class SpecialUpload extends SpecialPage {
/** Text injection points for hooks not using HTMLForm **/
public $uploadFormTextTop;
public $uploadFormTextAfterSummary;
-
/**
* Initialize instance variables from request and create an Upload handler
*
- * @param WebRequest $request The request to extract variables from
+ * @param $request WebRequest: the request to extract variables from
*/
protected function loadRequest( $request ) {
global $wgUser;
@@ -63,14 +82,15 @@ class SpecialUpload extends SpecialPage {
$this->mRequest = $request;
$this->mSourceType = $request->getVal( 'wpSourceType', 'file' );
$this->mUpload = UploadBase::createFromRequest( $request );
- $this->mUploadClicked = $request->wasPosted()
- && ( $request->getCheck( 'wpUpload' )
+ $this->mUploadClicked = $request->wasPosted()
+ && ( $request->getCheck( 'wpUpload' )
|| $request->getCheck( 'wpUploadIgnoreWarning' ) );
// Guess the desired name from the filename if not provided
$this->mDesiredDestName = $request->getText( 'wpDestFile' );
- if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null )
+ if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) {
$this->mDesiredDestName = $request->getFileName( 'wpUploadFile' );
+ }
$this->mComment = $request->getText( 'wpUploadDescription' );
$this->mLicense = $request->getText( 'wpLicense' );
@@ -97,7 +117,7 @@ class SpecialUpload extends SpecialPage {
} else {
$this->mTokenOk = $wgUser->matchEditToken( $token );
}
-
+
$this->uploadFormTextTop = '';
$this->uploadFormTextAfterSummary = '';
}
@@ -107,8 +127,8 @@ class SpecialUpload extends SpecialPage {
* Handle permission checking elsewhere in order to be able to show
* custom error messages.
*
- * @param User $user
- * @return bool
+ * @param $user User object
+ * @return Boolean
*/
public function userCanExecute( $user ) {
return UploadBase::isEnabled() && parent::userCanExecute( $user );
@@ -118,7 +138,7 @@ class SpecialUpload extends SpecialPage {
* Special page entry point
*/
public function execute( $par ) {
- global $wgUser, $wgOut, $wgRequest;
+ global $wgUser, $wgOut;
$this->setHeaders();
$this->outputHeader();
@@ -131,13 +151,14 @@ class SpecialUpload extends SpecialPage {
# Check permissions
global $wgGroupPermissions;
- if( !$wgUser->isAllowed( 'upload' ) ) {
+ $permissionRequired = UploadBase::isAllowed( $wgUser );
+ if( $permissionRequired !== true ) {
if( !$wgUser->isLoggedIn() && ( $wgGroupPermissions['user']['upload']
|| $wgGroupPermissions['autoconfirmed']['upload'] ) ) {
// Custom message if logged-in users without any special rights can upload
$wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
} else {
- $wgOut->permissionRequired( 'upload' );
+ $wgOut->permissionRequired( $permissionRequired );
}
return;
}
@@ -156,69 +177,76 @@ class SpecialUpload extends SpecialPage {
# Unsave the temporary file in case this was a cancelled upload
if ( $this->mCancelUpload ) {
- if ( !$this->unsaveUploadedFile() )
+ if ( !$this->unsaveUploadedFile() ) {
# Something went wrong, so unsaveUploadedFile showed a warning
return;
+ }
}
# Process upload or show a form
- if ( $this->mTokenOk && !$this->mCancelUpload
- && ( $this->mUpload && $this->mUploadClicked ) ) {
+ if (
+ $this->mTokenOk && !$this->mCancelUpload &&
+ ( $this->mUpload && $this->mUploadClicked )
+ )
+ {
$this->processUpload();
} else {
# Backwards compatibility hook
- if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) )
- {
+ if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) {
wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
return;
}
+
$this->showUploadForm( $this->getUploadForm() );
}
# Cleanup
- if ( $this->mUpload )
+ if ( $this->mUpload ) {
$this->mUpload->cleanupTempFile();
+ }
}
/**
- * Show the main upload form
+ * Show the main upload form
*
- * @param mixed $form An HTMLForm instance or HTML string to show
+ * @param $form Mixed: an HTMLForm instance or HTML string to show
*/
protected function showUploadForm( $form ) {
# Add links if file was previously deleted
if ( !$this->mDesiredDestName ) {
$this->showViewDeletedLinks();
}
-
+
if ( $form instanceof HTMLForm ) {
$form->show();
} else {
global $wgOut;
$wgOut->addHTML( $form );
}
-
+
}
/**
* Get an UploadForm instance with title and text properly set.
*
- * @param string $message HTML string to add to the form
- * @param string $sessionKey Session key in case this is a stashed upload
+ * @param $message String: HTML string to add to the form
+ * @param $sessionKey String: session key in case this is a stashed upload
+ * @param $hideIgnoreWarning Boolean: whether to hide "ignore warning" check box
* @return UploadForm
*/
protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
global $wgOut;
-
+
# Initialize form
$form = new UploadForm( array(
- 'watch' => $this->getWatchCheck(),
- 'forreupload' => $this->mForReUpload,
+ 'watch' => $this->getWatchCheck(),
+ 'forreupload' => $this->mForReUpload,
'sessionkey' => $sessionKey,
'hideignorewarning' => $hideIgnoreWarning,
'destwarningack' => (bool)$this->mDestWarningAck,
-
+
+ 'description' => $this->mComment,
'texttop' => $this->uploadFormTextTop,
'textaftersummary' => $this->uploadFormTextAfterSummary,
'destfile' => $this->mDesiredDestName,
@@ -226,26 +254,44 @@ class SpecialUpload extends SpecialPage {
$form->setTitle( $this->getTitle() );
# Check the token, but only if necessary
- if( !$this->mTokenOk && !$this->mCancelUpload
- && ( $this->mUpload && $this->mUploadClicked ) ) {
+ if(
+ !$this->mTokenOk && !$this->mCancelUpload &&
+ ( $this->mUpload && $this->mUploadClicked )
+ )
+ {
$form->addPreText( wfMsgExt( 'session_fail_preview', 'parseinline' ) );
}
+ # Give a notice if the user is uploading a file that has been deleted or moved
+ # Note that this is independent from the message 'filewasdeleted' that requires JS
+ $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
+ $delNotice = ''; // empty by default
+ if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) {
+ LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ),
+ $desiredTitleObj->getPrefixedText(),
+ '', array( 'lim' => 10,
+ 'conds' => array( "log_action != 'revision'" ),
+ 'showIfEmpty' => false,
+ 'msgKey' => array( 'upload-recreate-warning' ) )
+ );
+ }
+ $form->addPreText( $delNotice );
+
# Add text to form
$form->addPreText( '<div id="uploadtext">' .
wfMsgExt( 'uploadtext', 'parse', array( $this->mDesiredDestName ) ) .
'</div>' );
# Add upload error message
$form->addPreText( $message );
-
+
# Add footer to form
$uploadFooter = wfMsgNoTrans( 'uploadfooter' );
if ( $uploadFooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadFooter ) ) {
$form->addPostText( '<div id="mw-upload-footer-message">'
. $wgOut->parse( $uploadFooter ) . "</div>\n" );
}
-
- return $form;
+
+ return $form;
}
@@ -287,13 +333,13 @@ class SpecialUpload extends SpecialPage {
* essentially means that UploadBase::VERIFICATION_ERROR and
* UploadBase::EMPTY_FILE should not be passed here.
*
- * @param string $message HTML message to be passed to mainUploadForm
+ * @param $message String: HTML message to be passed to mainUploadForm
*/
protected function showRecoverableUploadError( $message ) {
$sessionKey = $this->mUpload->stashSession();
$message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
'<div class="error">' . $message . "</div>\n";
-
+
$form = $this->getUploadForm( $message, $sessionKey );
$form->setSubmitText( wfMsg( 'upload-tryagain' ) );
$this->showUploadForm( $form );
@@ -302,13 +348,11 @@ class SpecialUpload extends SpecialPage {
* Stashes the upload, shows the main form, but adds an "continue anyway button".
* Also checks whether there are actually warnings to display.
*
- * @param array $warnings
- * @return boolean true if warnings were displayed, false if there are no
+ * @param $warnings Array
+ * @return boolean true if warnings were displayed, false if there are no
* warnings and the should continue processing like there was no warning
*/
protected function showUploadWarning( $warnings ) {
- global $wgUser;
-
# If there are no warnings, or warnings we can ignore, return early.
# mDestWarningAck is set when some javascript has shown the warning
# to the user. mForReUpload is set when the user clicks the "upload a
@@ -322,28 +366,26 @@ class SpecialUpload extends SpecialPage {
$sessionKey = $this->mUpload->stashSession();
- $sk = $wgUser->getSkin();
-
$warningHtml = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n"
. '<ul class="warning">';
foreach( $warnings as $warning => $args ) {
- $msg = '';
- if( $warning == 'exists' ) {
- $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
- } elseif( $warning == 'duplicate' ) {
- $msg = self::getDupeWarning( $args );
- } elseif( $warning == 'duplicate-archive' ) {
- $msg = "\t<li>" . wfMsgExt( 'file-deleted-duplicate', 'parseinline',
- array( Title::makeTitle( NS_FILE, $args )->getPrefixedText() ) )
- . "</li>\n";
- } else {
- if ( $args === true )
- $args = array();
- elseif ( !is_array( $args ) )
- $args = array( $args );
- $msg = "\t<li>" . wfMsgExt( $warning, 'parseinline', $args ) . "</li>\n";
+ if( $warning == 'exists' ) {
+ $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
+ } elseif( $warning == 'duplicate' ) {
+ $msg = self::getDupeWarning( $args );
+ } elseif( $warning == 'duplicate-archive' ) {
+ $msg = "\t<li>" . wfMsgExt( 'file-deleted-duplicate', 'parseinline',
+ array( Title::makeTitle( NS_FILE, $args )->getPrefixedText() ) )
+ . "</li>\n";
+ } else {
+ if ( $args === true ) {
+ $args = array();
+ } elseif ( !is_array( $args ) ) {
+ $args = array( $args );
}
- $warningHtml .= $msg;
+ $msg = "\t<li>" . wfMsgExt( $warning, 'parseinline', $args ) . "</li>\n";
+ }
+ $warningHtml .= $msg;
}
$warningHtml .= "</ul>\n";
$warningHtml .= wfMsgExt( 'uploadwarning-text', 'parse' );
@@ -354,7 +396,7 @@ class SpecialUpload extends SpecialPage {
$form->addButton( 'wpCancelUpload', wfMsg( 'reuploaddesc' ) );
$this->showUploadForm( $form );
-
+
# Indicate that we showed a form
return true;
}
@@ -362,7 +404,7 @@ class SpecialUpload extends SpecialPage {
/**
* Show the upload form with error message, but do not stash the file.
*
- * @param string $message
+ * @param $message HTML string
*/
protected function showUploadError( $message ) {
$message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
@@ -377,25 +419,21 @@ class SpecialUpload extends SpecialPage {
protected function processUpload() {
global $wgUser, $wgOut;
- // Verify permissions
- $permErrors = $this->mUpload->verifyPermissions( $wgUser );
- if( $permErrors !== true ) {
- $wgOut->showPermissionsErrorPage( $permErrors );
- return;
- }
-
// Fetch the file if required
$status = $this->mUpload->fetchFile();
if( !$status->isOK() ) {
- $this->showUploadForm( $this->getUploadForm( $wgOut->parse( $status->getWikiText() ) ) );
+ $this->showUploadError( $wgOut->parse( $status->getWikiText() ) );
return;
}
- // Deprecated backwards compatibility hook
- if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) )
- {
+ if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) {
wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
- return array( 'status' => UploadBase::BEFORE_PROCESSING );
+ // This code path is deprecated. If you want to break upload processing
+ // do so by hooking into the appropriate hooks in UploadBase::verifyUpload
+ // and UploadBase::verifyFile.
+ // If you use this hook to break uploading, the user will be returned
+ // an empty form with no error message whatsoever.
+ return;
}
@@ -405,6 +443,15 @@ class SpecialUpload extends SpecialPage {
$this->processVerificationError( $details );
return;
}
+
+ // Verify permissions for this title
+ $permErrors = $this->mUpload->verifyPermissions( $wgUser );
+ if( $permErrors !== true ) {
+ $code = array_shift( $permErrors[0] );
+ $this->showRecoverableUploadError( wfMsgExt( $code,
+ 'parseinline', $permErrors[0] ) );
+ return;
+ }
$this->mLocalFile = $this->mUpload->getLocalFile();
@@ -433,28 +480,41 @@ class SpecialUpload extends SpecialPage {
$this->mUploadSuccessful = true;
wfRunHooks( 'SpecialUploadComplete', array( &$this ) );
$wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() );
-
}
/**
* Get the initial image page text based on a comment and optional file status information
*/
public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) {
- global $wgUseCopyrightUpload;
+ global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
+ $wgForceUIMsgAsContentMsg = (array) $wgForceUIMsgAsContentMsg;
+
+ /* 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, $wgForceUIMsgAsContentMsg ) ) {
+ $msg[$msgName] = "{{int:$msgName}}";
+ } else {
+ $msg[$msgName] = wfMsgForContent( $msgName );
+ }
+ }
+
if ( $wgUseCopyrightUpload ) {
$licensetxt = '';
if ( $license != '' ) {
- $licensetxt = '== ' . wfMsgForContent( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n";
+ $licensetxt = '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n";
}
- $pageText = '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n" .
- '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
- "$licensetxt" .
- '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
+ $pageText = '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n" .
+ '== ' . $msg[ 'filestatus' ] . " ==\n" . $copyStatus . "\n" .
+ "$licensetxt" .
+ '== ' . $msg[ 'filesource' ] . " ==\n" . $source;
} else {
if ( $license != '' ) {
- $filedesc = $comment == '' ? '' : '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n";
- $pageText = $filedesc .
- '== ' . wfMsgForContent ( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n";
+ $filedesc = $comment == '' ? '' : '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n";
+ $pageText = $filedesc .
+ '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n";
} else {
$pageText = $comment;
}
@@ -495,7 +555,7 @@ class SpecialUpload extends SpecialPage {
/**
* Provides output to the user for a result of UploadBase::verifyUpload
*
- * @param array $details Result of UploadBase::verifyUpload
+ * @param $details Array: result of UploadBase::verifyUpload
*/
protected function processVerificationError( $details ) {
global $wgFileExtensions, $wgLang;
@@ -510,10 +570,6 @@ class SpecialUpload extends SpecialPage {
$this->showRecoverableUploadError( wfMsgExt( 'illegalfilename',
'parseinline', $details['filtered'] ) );
break;
- case UploadBase::OVERWRITE_EXISTING_FILE:
- $this->showRecoverableUploadError( wfMsgExt( $details['overwrite'],
- 'parseinline' ) );
- break;
case UploadBase::FILETYPE_MISSING:
$this->showRecoverableUploadError( wfMsgExt( 'filetype-missing',
'parseinline' ) );
@@ -521,7 +577,10 @@ class SpecialUpload extends SpecialPage {
/** Statuses that require reuploading **/
case UploadBase::EMPTY_FILE:
- $this->showUploadForm( $this->getUploadForm( wfMsgHtml( 'emptyfile' ) ) );
+ $this->showUploadError( wfMsgHtml( 'emptyfile' ) );
+ break;
+ case UploadBase::FILE_TOO_LARGE:
+ $this->showUploadError( wfMsgHtml( 'largefileserver' ) );
break;
case UploadBase::FILETYPE_BADTYPE:
$finalExt = $details['finalExt'];
@@ -560,15 +619,16 @@ class SpecialUpload extends SpecialPage {
/**
* Remove a temporarily kept file stashed by saveTempUploadedFile().
- * @access private
- * @return success
+ *
+ * @return Boolean: success
*/
protected function unsaveUploadedFile() {
global $wgOut;
- if ( !( $this->mUpload instanceof UploadFromStash ) )
+ if ( !( $this->mUpload instanceof UploadFromStash ) ) {
return true;
+ }
$success = $this->mUpload->unsaveUploadedFile();
- if ( ! $success ) {
+ if ( !$success ) {
$wgOut->showFileDeleteError( $this->mUpload->getTempPath() );
return false;
} else {
@@ -582,14 +642,15 @@ class SpecialUpload extends SpecialPage {
* Formats a result of UploadBase::getExistsWarning as HTML
* This check is static and can be done pre-upload via AJAX
*
- * @param array $exists The result of UploadBase::getExistsWarning
- * @return string Empty string if there is no warning or an HTML fragment
+ * @param $exists Array: the result of UploadBase::getExistsWarning
+ * @return String: empty string if there is no warning or an HTML fragment
*/
public static function getExistsWarning( $exists ) {
- global $wgUser, $wgContLang;
+ global $wgUser;
- if ( !$exists )
+ if ( !$exists ) {
return '';
+ }
$file = $exists['file'];
$filename = $file->getTitle()->getPrefixedText();
@@ -638,8 +699,8 @@ class SpecialUpload extends SpecialPage {
/**
* Get a list of warnings
*
- * @param string local filename, e.g. 'file exists', 'non-descriptive filename'
- * @return array list of warning messages
+ * @param $filename String: local filename, e.g. 'file exists', 'non-descriptive filename'
+ * @return Array: list of warning messages
*/
public static function ajaxGetExistsWarning( $filename ) {
$file = wfFindFile( $filename );
@@ -648,7 +709,7 @@ class SpecialUpload extends SpecialPage {
// if there isn't an exact match...
$file = wfLocalFile( $filename );
}
- $s = '&nbsp;';
+ $s = '&#160;';
if ( $file ) {
$exists = UploadBase::getExistsWarning( $file );
$warning = self::getExistsWarning( $exists );
@@ -665,15 +726,15 @@ class SpecialUpload extends SpecialPage {
public static function getDupeWarning( $dupes ) {
if( $dupes ) {
global $wgOut;
- $msg = "<gallery>";
+ $msg = '<gallery>';
foreach( $dupes as $file ) {
$title = $file->getTitle();
$msg .= $title->getPrefixedText() .
- "|" . $title->getText() . "\n";
+ '|' . $title->getText() . "\n";
}
- $msg .= "</gallery>";
- return "<li>" .
- wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) .
+ $msg .= '</gallery>';
+ return '<li>' .
+ wfMsgExt( 'file-exists-duplicate', array( 'parse' ), count( $dupes ) ) .
$wgOut->parse( $msg ) .
"</li>\n";
} else {
@@ -694,25 +755,30 @@ class UploadForm extends HTMLForm {
protected $mDestWarningAck;
protected $mDestFile;
+ protected $mComment;
protected $mTextTop;
protected $mTextAfterSummary;
-
+
protected $mSourceIds;
public function __construct( $options = array() ) {
- global $wgLang;
-
$this->mWatch = !empty( $options['watch'] );
$this->mForReUpload = !empty( $options['forreupload'] );
- $this->mSessionKey = isset( $options['sessionkey'] )
+ $this->mSessionKey = isset( $options['sessionkey'] )
? $options['sessionkey'] : '';
$this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
$this->mDestWarningAck = !empty( $options['destwarningack'] );
-
- $this->mTextTop = $options['texttop'];
- $this->mTextAfterSummary = $options['textaftersummary'];
$this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : '';
+ $this->mComment = isset( $options['description'] ) ?
+ $options['description'] : '';
+
+ $this->mTextTop = isset( $options['texttop'] )
+ ? $options['texttop'] : '';
+
+ $this->mTextAfterSummary = isset( $options['textaftersummary'] )
+ ? $options['textaftersummary'] : '';
+
$sourceDescriptor = $this->getSourceSection();
$descriptor = $sourceDescriptor
+ $this->getDescriptionSection()
@@ -724,34 +790,37 @@ class UploadForm extends HTMLForm {
# Set some form properties
$this->setSubmitText( wfMsg( 'uploadbtn' ) );
$this->setSubmitName( 'wpUpload' );
+ # Used message keys: 'accesskey-upload', 'tooltip-upload'
$this->setSubmitTooltip( 'upload' );
$this->setId( 'mw-upload-form' );
# Build a list of IDs for javascript insertion
$this->mSourceIds = array();
- foreach ( $sourceDescriptor as $key => $field ) {
- if ( !empty( $field['id'] ) )
+ foreach ( $sourceDescriptor as $field ) {
+ if ( !empty( $field['id'] ) ) {
$this->mSourceIds[] = $field['id'];
+ }
}
}
/**
- * Get the descriptor of the fieldset that contains the file source
+ * Get the descriptor of the fieldset that contains the file source
* selection. The section is 'source'
- *
- * @return array Descriptor array
+ *
+ * @return Array: descriptor array
*/
protected function getSourceSection() {
global $wgLang, $wgUser, $wgRequest;
+ global $wgMaxUploadSize;
if ( $this->mSessionKey ) {
return array(
- 'wpSessionKey' => array(
+ 'SessionKey' => array(
'type' => 'hidden',
'default' => $this->mSessionKey,
),
- 'wpSourceType' => array(
+ 'SourceType' => array(
'type' => 'hidden',
'default' => 'Stash',
),
@@ -771,25 +840,28 @@ class UploadForm extends HTMLForm {
'raw' => true,
);
}
-
+
$descriptor['UploadFile'] = array(
- 'class' => 'UploadSourceField',
- 'section' => 'source',
- 'type' => 'file',
- 'id' => 'wpUploadFile',
- 'label-message' => 'sourcefilename',
- 'upload-type' => 'File',
- 'radio' => &$radio,
- 'help' => wfMsgExt( 'upload-maxfilesize',
- array( 'parseinline', 'escapenoentities' ),
- $wgLang->formatSize(
- wfShorthandToInteger( ini_get( 'upload_max_filesize' ) )
- )
- ) . ' ' . wfMsgHtml( 'upload_source_file' ),
- 'checked' => $selectedSourceType == 'file',
+ 'class' => 'UploadSourceField',
+ 'section' => 'source',
+ 'type' => 'file',
+ 'id' => 'wpUploadFile',
+ 'label-message' => 'sourcefilename',
+ 'upload-type' => 'File',
+ 'radio' => &$radio,
+ 'help' => wfMsgExt( 'upload-maxfilesize',
+ array( 'parseinline', 'escapenoentities' ),
+ $wgLang->formatSize(
+ wfShorthandToInteger( min(
+ wfShorthandToInteger(
+ ini_get( 'upload_max_filesize' )
+ ), $wgMaxUploadSize
+ ) )
+ )
+ ) . ' ' . wfMsgHtml( 'upload_source_file' ),
+ 'checked' => $selectedSourceType == 'file',
);
if ( $canUploadByUrl ) {
- global $wgMaxUploadSize;
$descriptor['UploadFileURL'] = array(
'class' => 'UploadSourceField',
'section' => 'source',
@@ -815,11 +887,10 @@ class UploadForm extends HTMLForm {
return $descriptor;
}
-
/**
* Get the messages indicating which extensions are preferred and prohibitted.
- *
- * @return string HTML string containing the message
+ *
+ * @return String: HTML string containing the message
*/
protected function getExtensionsMessage() {
# Print a list of allowed file extensions, if so configured. We ignore
@@ -827,7 +898,6 @@ class UploadForm extends HTMLForm {
global $wgLang, $wgCheckFileExtensions, $wgStrictFileExtensions,
$wgFileExtensions, $wgFileBlacklist;
- $allowedExtensions = '';
if( $wgCheckFileExtensions ) {
if( $wgStrictFileExtensions ) {
# Everything not permitted is banned
@@ -855,16 +925,11 @@ class UploadForm extends HTMLForm {
/**
* Get the descriptor of the fieldset that contains the file description
* input. The section is 'description'
- *
- * @return array Descriptor array
+ *
+ * @return Array: descriptor array
*/
protected function getDescriptionSection() {
- global $wgUser, $wgOut;
-
- $cols = intval( $wgUser->getOption( 'cols' ) );
- if( $wgUser->getOption( 'editwidth' ) ) {
- $wgOut->addInlineStyle( '#mw-htmlform-description { width: 100%; }' );
- }
+ global $wgUser;
$descriptor = array(
'DestFile' => array(
@@ -884,7 +949,8 @@ class UploadForm extends HTMLForm {
'label-message' => $this->mForReUpload
? 'filereuploadsummary'
: 'fileuploadsummary',
- 'cols' => $cols,
+ 'default' => $this->mComment,
+ 'cols' => intval( $wgUser->getOption( 'cols' ) ),
'rows' => 8,
)
);
@@ -896,22 +962,25 @@ class UploadForm extends HTMLForm {
'raw' => true,
);
}
-
+
$descriptor += array(
'EditTools' => array(
'type' => 'edittools',
'section' => 'description',
- ),
- 'License' => array(
+ )
+ );
+
+ if ( $this->mForReUpload ) {
+ $descriptor['DestFile']['readonly'] = true;
+ } else {
+ $descriptor['License'] = array(
'type' => 'select',
'class' => 'Licenses',
'section' => 'description',
'id' => 'wpLicense',
'label-message' => 'license',
- ),
- );
- if ( $this->mForReUpload )
- $descriptor['DestFile']['readonly'] = true;
+ );
+ }
global $wgUseCopyrightUpload;
if ( $wgUseCopyrightUpload ) {
@@ -933,15 +1002,15 @@ class UploadForm extends HTMLForm {
}
/**
- * Get the descriptor of the fieldset that contains the upload options,
+ * Get the descriptor of the fieldset that contains the upload options,
* such as "watch this file". The section is 'options'
- *
- * @return array Descriptor array
+ *
+ * @return Array: descriptor array
*/
protected function getOptionsSection() {
- global $wgUser, $wgOut;
+ global $wgUser;
- if( $wgUser->isLoggedIn() ) {
+ if ( $wgUser->isLoggedIn() ) {
$descriptor = array(
'Watchthis' => array(
'type' => 'check',
@@ -952,7 +1021,7 @@ class UploadForm extends HTMLForm {
)
);
}
- if( !$this->mHideIgnoreWarning ) {
+ if ( !$this->mHideIgnoreWarning ) {
$descriptor['IgnoreWarning'] = array(
'type' => 'check',
'id' => 'wpIgnoreWarning',
@@ -961,14 +1030,14 @@ class UploadForm extends HTMLForm {
);
}
- $descriptor['wpDestFileWarningAck'] = array(
+ $descriptor['DestFileWarningAck'] = array(
'type' => 'hidden',
'id' => 'wpDestFileWarningAck',
'default' => $this->mDestWarningAck ? '1' : '',
);
if ( $this->mForReUpload ) {
- $descriptor['wpForReUpload'] = array(
+ $descriptor['ForReUpload'] = array(
'type' => 'hidden',
'id' => 'wpForReUpload',
'default' => '1',
@@ -976,7 +1045,6 @@ class UploadForm extends HTMLForm {
}
return $descriptor;
-
}
/**
@@ -989,12 +1057,9 @@ class UploadForm extends HTMLForm {
/**
* Add upload JS to $wgOut
- *
- * @param bool $autofill Whether or not to autofill the destination
- * filename text box
*/
- protected function addUploadJS( ) {
- global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI;
+ protected function addUploadJS() {
+ global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI, $wgStrictFileExtensions;
global $wgOut;
$useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
@@ -1008,18 +1073,19 @@ class UploadForm extends HTMLForm {
// the wpDestFile textbox
$this->mDestFile === '',
'wgUploadSourceIds' => $this->mSourceIds,
+ 'wgStrictFileExtensions' => $wgStrictFileExtensions,
+ 'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
);
$wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
-
+
// For <charinsert> support
- $wgOut->addScriptFile( 'edit.js' );
- $wgOut->addScriptFile( 'upload.js' );
+ $wgOut->addModules( array( 'mediawiki.legacy.edit', 'mediawiki.legacy.upload' ) );
}
/**
* Empty function; submission is handled elsewhere.
- *
+ *
* @return bool false
*/
function trySubmit() {
@@ -1032,9 +1098,9 @@ class UploadForm extends HTMLForm {
* A form field that contains a radio box in the label
*/
class UploadSourceField extends HTMLTextField {
- function getLabelHtml() {
+ function getLabelHtml( $cellAttributes = array() ) {
$id = "wpSourceType{$this->mParams['upload-type']}";
- $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
+ $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
if ( !empty( $this->mParams['radio'] ) ) {
$attribs = array(
@@ -1043,13 +1109,15 @@ class UploadSourceField extends HTMLTextField {
'id' => $id,
'value' => $this->mParams['upload-type'],
);
- if ( !empty( $this->mParams['checked'] ) )
+ if ( !empty( $this->mParams['checked'] ) ) {
$attribs['checked'] = 'checked';
+ }
$label .= Html::element( 'input', $attribs );
}
- return Html::rawElement( 'td', array( 'class' => 'mw-label' ), $label );
+ return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, $label );
}
+
function getSize() {
return isset( $this->mParams['size'] )
? $this->mParams['size']
diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php
new file mode 100644
index 00000000..48a41a5e
--- /dev/null
+++ b/includes/specials/SpecialUploadStash.php
@@ -0,0 +1,394 @@
+<?php
+/**
+ * Implements Special:UploadStash
+ *
+ * Web access for files temporarily stored by UploadStash.
+ *
+ * For example -- files that were uploaded with the UploadWizard extension are stored temporarily
+ * before committing them to the db. But we want to see their thumbnails and get other information
+ * about them.
+ *
+ * Since this is based on the user's session, in effect this creates a private temporary file area.
+ * However, the URLs for the files cannot be shared.
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @ingroup Upload
+ */
+
+class SpecialUploadStash extends UnlistedSpecialPage {
+ // UploadStash
+ private $stash;
+
+ // is the edit request authorized? boolean
+ private $isEditAuthorized;
+
+ // did the user request us to clear the stash? boolean
+ private $requestedClear;
+
+ // Since we are directly writing the file to STDOUT,
+ // we should not be reading in really big files and serving them out.
+ //
+ // We also don't want people using this as a file drop, even if they
+ // share credentials.
+ //
+ // This service is really for thumbnails and other such previews while
+ // uploading.
+ const MAX_SERVE_BYTES = 262144; // 256K
+
+ public function __construct( $request = null ) {
+ global $wgRequest;
+
+ parent::__construct( 'UploadStash', 'upload' );
+ try {
+ $this->stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
+ } catch ( UploadStashNotAvailableException $e ) {
+ return null;
+ }
+
+ $this->loadRequest( is_null( $request ) ? $wgRequest : $request );
+ }
+
+ /**
+ * Execute page -- can output a file directly or show a listing of them.
+ *
+ * @param $subPage String: subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
+ * @return Boolean: success
+ */
+ public function execute( $subPage ) {
+ global $wgUser;
+
+ if ( !$this->userCanExecute( $wgUser ) ) {
+ $this->displayRestrictionError();
+ return;
+ }
+
+ if ( !isset( $subPage ) || $subPage === '' ) {
+ return $this->showUploads();
+ }
+
+ return $this->showUpload( $subPage );
+ }
+
+
+ /**
+ * If file available in stash, cats it out to the client as a simple HTTP response.
+ * n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
+ *
+ * @param $key String: the key of a particular requested file
+ */
+ public function showUpload( $key ) {
+ global $wgOut;
+
+ // prevent callers from doing standard HTML output -- we'll take it from here
+ $wgOut->disable();
+
+ try {
+ $params = $this->parseKey( $key );
+ if ( $params['type'] === 'thumb' ) {
+ return $this->outputThumbFromStash( $params['file'], $params['params'] );
+ } else {
+ return $this->outputLocalFile( $params['file'] );
+ }
+ } catch( UploadStashFileNotFoundException $e ) {
+ $code = 404;
+ $message = $e->getMessage();
+ } catch( UploadStashZeroLengthFileException $e ) {
+ $code = 500;
+ $message = $e->getMessage();
+ } catch( UploadStashBadPathException $e ) {
+ $code = 500;
+ $message = $e->getMessage();
+ } catch( SpecialUploadStashTooLargeException $e ) {
+ $code = 500;
+ $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage();
+ } catch( Exception $e ) {
+ $code = 500;
+ $message = $e->getMessage();
+ }
+
+ wfHttpError( $code, OutputPage::getStatusMessage( $code ), $message );
+ return false;
+ }
+
+ /**
+ * Parse the key passed to the SpecialPage. Returns an array containing
+ * the associated file object, the type ('file' or 'thumb') and if
+ * application the transform parameters
+ *
+ * @param string $key
+ * @return array
+ */
+ private function parseKey( $key ) {
+ $type = strtok( $key, '/' );
+
+ if ( $type !== 'file' && $type !== 'thumb' ) {
+ throw new UploadStashBadPathException( "Unknown type '$type'" );
+ }
+ $fileName = strtok( '/' );
+ $thumbPart = strtok( '/' );
+ $file = $this->stash->getFile( $fileName );
+ if ( $type === 'thumb' ) {
+ $srcNamePos = strrpos( $thumbPart, $fileName );
+ if ( $srcNamePos === false || $srcNamePos < 1 ) {
+ throw new UploadStashBadPathException( 'Unrecognized thumb name' );
+ }
+ $paramString = substr( $thumbPart, 0, $srcNamePos - 1 );
+
+ $handler = $file->getHandler();
+ $params = $handler->parseParamString( $paramString );
+ return array( 'file' => $file, 'type' => $type, 'params' => $params );
+ }
+
+ return array( 'file' => $file, 'type' => $type );
+ }
+
+
+
+
+ /**
+ * Get a thumbnail for file, either generated locally or remotely, and stream it out
+ * @param String $key: key for the file in the stash
+ * @param int $width: width of desired thumbnail
+ * @return boolean success
+ */
+ private function outputThumbFromStash( $file, $params ) {
+
+ // this global, if it exists, points to a "scaler", as you might find in the Wikimedia Foundation cluster. See outputRemoteScaledThumb()
+ // this is part of our horrible NFS-based system, we create a file on a mount point here, but fetch the scaled file from somewhere else that
+ // happens to share it over NFS
+ global $wgUploadStashScalerBaseUrl;
+
+ $flags = 0;
+ if ( $wgUploadStashScalerBaseUrl ) {
+ $this->outputRemoteScaledThumb( $file, $params, $flags );
+ } else {
+ $this->outputLocallyScaledThumb( $file, $params, $flags );
+ }
+
+
+ }
+
+
+ /**
+ * Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT.
+ * @param $file: File object
+ * @param $params: scaling parameters ( e.g. array( width => '50' ) );
+ * @param $flags: scaling flags ( see File:: constants )
+ * @throws MWException
+ * @return boolean success
+ */
+ private function outputLocallyScaledThumb( $file, $params, $flags ) {
+
+ // n.b. this is stupid, we insist on re-transforming the file every time we are invoked. We rely
+ // on HTTP caching to ensure this doesn't happen.
+
+ $flags |= File::RENDER_NOW;
+
+ $thumbnailImage = $file->transform( $params, $flags );
+ if ( !$thumbnailImage ) {
+ throw new MWException( 'Could not obtain thumbnail' );
+ }
+
+ // we should have just generated it locally
+ if ( ! $thumbnailImage->getPath() ) {
+ throw new UploadStashFileNotFoundException( "no local path for scaled item" );
+ }
+
+ // now we should construct a File, so we can get mime and other such info in a standard way
+ // n.b. mimetype may be different from original (ogx original -> jpeg thumb)
+ $thumbFile = new UnregisteredLocalFile( false, $this->stash->repo, $thumbnailImage->getPath(), false );
+ if ( ! $thumbFile ) {
+ throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" );
+ }
+
+ return $this->outputLocalFile( $thumbFile );
+
+ }
+
+ /**
+ * Scale a file with a remote "scaler", as exists on the Wikimedia Foundation cluster, and output it to STDOUT.
+ * Note: unlike the usual thumbnail process, the web client never sees the cluster URL; we do the whole HTTP transaction to the scaler ourselves
+ * and cat the results out.
+ * Note: We rely on NFS to have propagated the file contents to the scaler. However, we do not rely on the thumbnail being created in NFS and then
+ * propagated back to our filesystem. Instead we take the results of the HTTP request instead.
+ * Note: no caching is being done here, although we are instructing the client to cache it forever.
+ * @param $file: File object
+ * @param $params: scaling parameters ( e.g. array( width => '50' ) );
+ * @param $flags: scaling flags ( see File:: constants )
+ * @throws MWException
+ * @return boolean success
+ */
+ private function outputRemoteScaledThumb( $file, $params, $flags ) {
+
+ // this global probably looks something like 'http://upload.wikimedia.org/wikipedia/test/thumb/temp'
+ // do not use trailing slash
+ global $wgUploadStashScalerBaseUrl;
+
+ $scalerThumbName = $file->getParamThumbName( $file->name, $params );
+ $scalerThumbUrl = $wgUploadStashScalerBaseUrl . '/' . $file->getRel() . '/' . $scalerThumbName;
+
+ // make a curl call to the scaler to create a thumbnail
+ $httpOptions = array(
+ 'method' => 'GET',
+ 'timeout' => 'default'
+ );
+ $req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions );
+ $status = $req->execute();
+ if ( ! $status->isOK() ) {
+ $errors = $status->getErrorsArray();
+ throw new MWException( "Fetching thumbnail failed: " . join( ", ", $errors ) );
+ }
+ $contentType = $req->getResponseHeader( "content-type" );
+ if ( ! $contentType ) {
+ throw new MWException( "Missing content-type header" );
+ }
+ return $this->outputContents( $req->getContent(), $contentType );
+ }
+
+ /**
+ * Output HTTP response for file
+ * Side effect: writes HTTP response to STDOUT.
+ * XXX could use wfStreamfile (in includes/Streamfile.php), but for consistency with outputContents() doing it this way.
+ * XXX is mimeType really enough, or do we need encoding for full Content-Type header?
+ *
+ * @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!)
+ */
+ private function outputLocalFile( $file ) {
+ if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
+ throw new SpecialUploadStashTooLargeException();
+ }
+ self::outputFileHeaders( $file->getMimeType(), $file->getSize() );
+ readfile( $file->getPath() );
+ return true;
+ }
+
+ /**
+ * Output HTTP response of raw content
+ * Side effect: writes HTTP response to STDOUT.
+ * @param String $content: content
+ * @param String $mimeType: mime type
+ */
+ private function outputContents( $content, $contentType ) {
+ $size = strlen( $content );
+ if ( $size > self::MAX_SERVE_BYTES ) {
+ throw new SpecialUploadStashTooLargeException();
+ }
+ self::outputFileHeaders( $contentType, $size );
+ print $content;
+ return true;
+ }
+
+ /**
+ * Output headers for streaming
+ * XXX unsure about encoding as binary; if we received from HTTP perhaps we should use that encoding, concatted with semicolon to mimeType as it usually is.
+ * Side effect: preps PHP to write headers to STDOUT.
+ * @param String $contentType : string suitable for content-type header
+ * @param String $size: length in bytes
+ */
+ private static function outputFileHeaders( $contentType, $size ) {
+ header( "Content-Type: $contentType", true );
+ header( 'Content-Transfer-Encoding: binary', true );
+ header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true );
+ header( "Content-Length: $size", true );
+ }
+
+
+ /**
+ * Initialize authorization & actions to take, from the request
+ * @param $request: WebRequest
+ */
+ private function loadRequest( $request ) {
+ global $wgUser;
+ if ( $request->wasPosted() ) {
+
+ $token = $request->getVal( 'wpEditToken' );
+ $this->isEditAuthorized = $wgUser->matchEditToken( $token );
+
+ $this->requestedClear = $request->getBool( 'clear' );
+
+ }
+ }
+
+ /**
+ * Static callback for the HTMLForm in showUploads, to process
+ * Note the stash has to be recreated since this is being called in a static context.
+ * This works, because there really is only one stash per logged-in user, despite appearances.
+ *
+ * @return Status
+ */
+ public static function tryClearStashedUploads( $formData ) {
+ wfDebug( __METHOD__ . " form data : " . print_r( $formData, 1 ) );
+ if ( isset( $formData['clear'] ) and $formData['clear'] ) {
+ $stash = new UploadStash();
+ wfDebug( "stash has: " . print_r( $stash->listFiles(), 1 ) );
+ if ( ! $stash->clear() ) {
+ return Status::newFatal( 'uploadstash-errclear' );
+ }
+ }
+ return Status::newGood();
+ }
+
+ /**
+ * Default action when we don't have a subpage -- just show links to the uploads we have,
+ * Also show a button to clear stashed files
+ * @param Status : $status - the result of processRequest
+ */
+ private function showUploads( $status = null ) {
+ global $wgOut;
+ if ( $status === null ) {
+ $status = Status::newGood();
+ }
+
+ // sets the title, etc.
+ $this->setHeaders();
+ $this->outputHeader();
+
+
+ // create the form, which will also be used to execute a callback to process incoming form data
+ // this design is extremely dubious, but supposedly HTMLForm is our standard now?
+
+ $form = new HTMLForm( array(
+ 'Clear' => array(
+ 'type' => 'hidden',
+ 'default' => true,
+ 'name' => 'clear',
+ )
+ ), 'clearStashedUploads' );
+ $form->setSubmitCallback( array( __CLASS__, 'tryClearStashedUploads' ) );
+ $form->setTitle( $this->getTitle() );
+ $form->addHiddenField( 'clear', true, array( 'type' => 'boolean' ) );
+ $form->setSubmitText( wfMsg( 'uploadstash-clear' ) );
+
+ $form->prepareForm();
+ $formResult = $form->tryAuthorizedSubmit();
+
+
+ // show the files + form, if there are any, or just say there are none
+ $refreshHtml = Html::element( 'a', array( 'href' => $this->getTitle()->getLocalURL() ), wfMsg( 'uploadstash-refresh' ) );
+ $files = $this->stash->listFiles();
+ if ( count( $files ) ) {
+ sort( $files );
+ $fileListItemsHtml = '';
+ foreach ( $files as $file ) {
+ $fileListItemsHtml .= Html::rawElement( 'li', array(),
+ Html::element( 'a', array( 'href' =>
+ $this->getTitle( "file/$file" )->getLocalURL() ), $file )
+ );
+ }
+ $wgOut->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) );
+ $form->displayForm( $formResult );
+ $wgOut->addHtml( Html::rawElement( 'p', array(), $refreshHtml ) );
+ } else {
+ $wgOut->addHtml( Html::rawElement( 'p', array(),
+ Html::element( 'span', array(), wfMsg( 'uploadstash-nofiles' ) )
+ . ' '
+ . $refreshHtml
+ ) );
+ }
+
+ return true;
+ }
+}
+
+class SpecialUploadStashTooLargeException extends MWException {};
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index 8b8d0e9e..ccace79d 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -1,11 +1,28 @@
<?php
/**
+ * Implements Special:UserLogin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * constructor
+ * Constructor
*/
function wfSpecialUserlogin( $par = '' ) {
global $wgRequest;
@@ -18,7 +35,8 @@ function wfSpecialUserlogin( $par = '' ) {
}
/**
- * implements Special:Login
+ * Implements Special:UserLogin
+ *
* @ingroup SpecialPage
*/
class LoginForm {
@@ -41,7 +59,7 @@ class LoginForm {
var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
- var $mSkipCookieCheck, $mReturnToQuery, $mToken;
+ var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS;
private $mExtUser = null;
@@ -50,7 +68,7 @@ class LoginForm {
* @param $request WebRequest: a WebRequest object passed by reference
* @param $par String: subpage parameter
*/
- function LoginForm( &$request, $par = '' ) {
+ function __construct( &$request, $par = '' ) {
global $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
$this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]]
@@ -58,6 +76,7 @@ class LoginForm {
$this->mPassword = $request->getText( 'wpPassword' );
$this->mRetype = $request->getText( 'wpRetype' );
$this->mDomain = $request->getText( 'wpDomain' );
+ $this->mReason = $request->getText( 'wpReason' );
$this->mReturnTo = $request->getVal( 'returnto' );
$this->mReturnToQuery = $request->getVal( 'returntoquery' );
$this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
@@ -70,9 +89,10 @@ class LoginForm {
$this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
$this->mAction = $request->getVal( 'action' );
$this->mRemember = $request->getCheck( 'wpRemember' );
+ $this->mStickHTTPS = $request->getCheck( 'wpStickHTTPS' );
$this->mLanguage = $request->getText( 'uselang' );
$this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' );
- $this->mToken = ($this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' );
+ $this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' );
if ( $wgRedirectOnLogin ) {
$this->mReturnTo = $wgRedirectOnLogin;
@@ -107,14 +127,14 @@ class LoginForm {
if ( !is_null( $this->mCookieCheck ) ) {
$this->onCookieRedirectCheck( $this->mCookieCheck );
return;
- } else if( $this->mPosted ) {
+ } elseif( $this->mPosted ) {
if( $this->mCreateaccount ) {
return $this->addNewAccount();
- } else if ( $this->mCreateaccountMail ) {
+ } elseif ( $this->mCreateaccountMail ) {
return $this->addNewAccountMailPassword();
- } else if ( $this->mMailmypassword ) {
+ } elseif ( $this->mMailmypassword ) {
return $this->mailPassword();
- } else if ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
+ } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
return $this->processLogin();
}
}
@@ -128,13 +148,13 @@ class LoginForm {
global $wgOut;
if ( $this->mEmail == '' ) {
- $this->mainLoginForm( wfMsg( 'noemail', htmlspecialchars( $this->mName ) ) );
+ $this->mainLoginForm( wfMsgExt( 'noemailcreate', array( 'parsemag', 'escape' ) ) );
return;
}
$u = $this->addNewaccountInternal();
- if ($u == null) {
+ if ( $u == null ) {
return;
}
@@ -144,47 +164,46 @@ class LoginForm {
$result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' );
wfRunHooks( 'AddNewAccount', array( $u, true ) );
- $u->addNewUserLogEntry();
+ $u->addNewUserLogEntry( true, $this->mReason );
$wgOut->setPageTitle( wfMsg( 'accmailtitle' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
- if( WikiError::isError( $result ) ) {
- $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) );
+ if( !$result->isGood() ) {
+ $this->mainLoginForm( wfMsg( 'mailerror', $result->getWikiText() ) );
} else {
$wgOut->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() );
$wgOut->returnToMain( false );
}
- $u = 0;
}
-
/**
* @private
*/
function addNewAccount() {
- global $wgUser, $wgEmailAuthentication;
+ global $wgUser, $wgEmailAuthentication, $wgOut;
# Create the account and abort if there's a problem doing so
$u = $this->addNewAccountInternal();
- if( $u == null )
+ if( $u == null ) {
return;
+ }
# If we showed up language selection links, and one was in use, be
# smart (and sensible) and save that language as the user's preference
global $wgLoginLanguageSelector;
- if( $wgLoginLanguageSelector && $this->mLanguage )
+ if( $wgLoginLanguageSelector && $this->mLanguage ) {
$u->setOption( 'language', $this->mLanguage );
+ }
# Send out an email authentication message if needed
if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) {
- global $wgOut;
- $error = $u->sendConfirmationMail();
- if( WikiError::isError( $error ) ) {
- $wgOut->addWikiMsg( 'confirmemail_sendfailed', $error->getMessage() );
- } else {
+ $status = $u->sendConfirmationMail();
+ if( $status->isGood() ) {
$wgOut->addWikiMsg( 'confirmemail_oncreate' );
+ } else {
+ $wgOut->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
}
}
@@ -206,7 +225,6 @@ class LoginForm {
}
} else {
# Confirm that the account was created
- global $wgOut;
$self = SpecialPage::getTitleFor( 'Userlogin' );
$wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) );
$wgOut->setArticleRelated( false );
@@ -214,7 +232,7 @@ class LoginForm {
$wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) );
$wgOut->returnToMain( false, $self );
wfRunHooks( 'AddNewAccount', array( $u, false ) );
- $u->addNewUserLogEntry();
+ $u->addNewUserLogEntry( false, $this->mReason );
return true;
}
}
@@ -254,16 +272,16 @@ class LoginForm {
# Request forgery checks.
if ( !self::getCreateaccountToken() ) {
self::setCreateaccountToken();
- $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
+ $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
return false;
}
-
+
# The user didn't pass a createaccount token
if ( !$this->mToken ) {
$this->mainLoginForm( wfMsg( 'sessionfailure' ) );
return false;
}
-
+
# Validate the createaccount token
if ( $this->mToken !== self::getCreateaccountToken() ) {
$this->mainLoginForm( wfMsg( 'sessionfailure' ) );
@@ -272,7 +290,7 @@ class LoginForm {
# Check permissions
if ( !$wgUser->isAllowed( 'createaccount' ) ) {
- $this->userNotPrivilegedMessage();
+ $wgOut->permissionRequired( 'createaccount' );
return false;
} elseif ( $wgUser->isBlockedFromCreateAccount() ) {
$this->userBlockedMessage();
@@ -359,7 +377,7 @@ class LoginForm {
return false;
}
- self::clearCreateaccountToken();
+ self::clearCreateaccountToken();
return $this->initUser( $u, false );
}
@@ -413,16 +431,17 @@ class LoginForm {
* creation.
*/
public function authenticateUserData() {
- global $wgUser, $wgAuth;
+ global $wgUser, $wgAuth, $wgMemc;
+
if ( $this->mName == '' ) {
return self::NO_NAME;
}
-
+
// We require a login token to prevent login CSRF
// Handle part of this before incrementing the throttle so
// token-less login attempts don't count towards the throttle
// but wrong-token attempts do.
-
+
// If the user doesn't have a login token yet, set one.
if ( !self::getLoginToken() ) {
self::setLoginToken();
@@ -432,7 +451,7 @@ class LoginForm {
if ( !$this->mToken ) {
return self::NEED_TOKEN;
}
-
+
global $wgPasswordAttemptThrottle;
$throttleCount = 0;
@@ -440,18 +459,17 @@ class LoginForm {
$throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) );
$count = $wgPasswordAttemptThrottle['count'];
$period = $wgPasswordAttemptThrottle['seconds'];
-
- global $wgMemc;
+
$throttleCount = $wgMemc->get( $throttleKey );
if ( !$throttleCount ) {
$wgMemc->add( $throttleKey, 1, $period ); // start counter
- } else if ( $throttleCount < $count ) {
- $wgMemc->incr($throttleKey);
- } else if ( $throttleCount >= $count ) {
+ } elseif ( $throttleCount < $count ) {
+ $wgMemc->incr( $throttleKey );
+ } elseif ( $throttleCount >= $count ) {
return self::THROTTLED;
}
}
-
+
// Validate the login token
if ( $this->mToken !== self::getLoginToken() ) {
return self::WRONG_TOKEN;
@@ -464,7 +482,7 @@ class LoginForm {
// for user existence using User::newFromName($name)->getId() below
// will effectively be using stale data.
if ( $wgUser->getName() === $this->mName ) {
- wfDebug( __METHOD__.": already logged in as {$this->mName}\n" );
+ wfDebug( __METHOD__ . ": already logged in as {$this->mName}\n" );
return self::SUCCESS;
}
@@ -505,7 +523,7 @@ class LoginForm {
}
global $wgBlockDisablesLogin;
- if (!$u->checkPassword( $this->mPassword )) {
+ if ( !$u->checkPassword( $this->mPassword ) ) {
if( $u->checkTemporaryPassword( $this->mPassword ) ) {
// The e-mailed temporary password should not be used for actu-
// al logins; that's a very sloppy habit, and insecure if an
@@ -533,7 +551,7 @@ class LoginForm {
// faces etc will probably just fail cleanly here.
$retval = self::RESET_PASS;
} else {
- $retval = ($this->mPassword == '') ? self::EMPTY_PASS : self::WRONG_PASS;
+ $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
}
} elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
// If we've enabled it, make it so that a blocked user cannot login
@@ -543,8 +561,8 @@ class LoginForm {
$wgUser = $u;
// Please reset throttle for successful logins, thanks!
- if($throttleCount) {
- $wgMemc->delete($throttleKey);
+ if( $throttleCount ) {
+ $wgMemc->delete( $throttleKey );
}
if ( $isAutoCreated ) {
@@ -567,7 +585,7 @@ class LoginForm {
global $wgAuth, $wgUser, $wgAutocreatePolicy;
if ( $wgUser->isBlockedFromCreateAccount() ) {
- wfDebug( __METHOD__.": user is blocked from account creation\n" );
+ wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
return self::CREATE_BLOCKED;
}
@@ -591,22 +609,22 @@ class LoginForm {
return self::NOT_EXISTS;
}
if ( !$wgAuth->userExists( $user->getName() ) ) {
- wfDebug( __METHOD__.": user does not exist\n" );
+ wfDebug( __METHOD__ . ": user does not exist\n" );
return self::NOT_EXISTS;
}
if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
- wfDebug( __METHOD__.": \$wgAuth->authenticate() returned false, aborting\n" );
+ wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
return self::WRONG_PLUGIN_PASS;
}
}
- wfDebug( __METHOD__.": creating account\n" );
- $user = $this->initUser( $user, true );
+ wfDebug( __METHOD__ . ": creating account\n" );
+ $this->initUser( $user, true );
return self::SUCCESS;
}
function processLogin() {
- global $wgUser, $wgAuth;
+ global $wgUser;
switch ( $this->authenticateUserData() ) {
case self::SUCCESS:
@@ -637,8 +655,10 @@ class LoginForm {
return $this->cookieRedirectCheck( 'login' );
}
break;
-
+
case self::NEED_TOKEN:
+ $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
+ break;
case self::WRONG_TOKEN:
$this->mainLoginForm( wfMsg( 'sessionfailure' ) );
break;
@@ -650,7 +670,7 @@ class LoginForm {
$this->mainLoginForm( wfMsg( 'wrongpassword' ) );
break;
case self::NOT_EXISTS:
- if( $wgUser->isAllowed( 'createaccount' ) ){
+ if( $wgUser->isAllowed( 'createaccount' ) ) {
$this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mName ) ) );
} else {
$this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) );
@@ -676,7 +696,7 @@ class LoginForm {
array( 'parsemag', 'escape' ), $this->mName ) );
break;
default:
- throw new MWException( "Unhandled case value" );
+ throw new MWException( 'Unhandled case value' );
}
}
@@ -692,27 +712,27 @@ class LoginForm {
*/
function mailPassword() {
global $wgUser, $wgOut, $wgAuth;
-
+
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
return false;
}
-
+
if( !$wgAuth->allowPasswordChange() ) {
$this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) );
return;
}
- # Check against blocked IPs so blocked users can't flood admins
+ # Check against blocked IPs so blocked users can't flood admins
# with password resets
if( $wgUser->isBlocked() ) {
$this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) );
return;
}
-
+
# Check for hooks
$error = null;
- if ( ! wfRunHooks( 'UserLoginMailPassword', array( $this->mName, &$error ) ) ) {
+ if ( !wfRunHooks( 'UserLoginMailPassword', array( $this->mName, &$error ) ) ) {
$this->mainLoginForm( $error );
return;
}
@@ -729,7 +749,7 @@ class LoginForm {
$this->mainLoginForm( wfMsg( 'sessionfailure' ) );
return;
}
-
+
# Check against the rate limiter
if( $wgUser->pingLimiter( 'mailpassword' ) ) {
$wgOut->rateLimited();
@@ -767,11 +787,11 @@ class LoginForm {
}
$result = $this->mailPasswordInternal( $u, true, 'passwordremindertitle', 'passwordremindertext' );
- if( WikiError::isError( $result ) ) {
- $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) );
- } else {
+ if( $result->isGood() ) {
$this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' );
self::clearLoginToken();
+ } else {
+ $this->mainLoginForm( $result->getWikiText( 'mailerror' ) );
}
}
@@ -781,21 +801,21 @@ class LoginForm {
* @param $throttle Boolean
* @param $emailTitle String: message name of email title
* @param $emailText String: message name of email text
- * @return Mixed: true on success, WikiError on failure
+ * @return Status object
* @private
*/
function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) {
global $wgServer, $wgScript, $wgUser, $wgNewPasswordExpiry;
if ( $u->getEmail() == '' ) {
- return new WikiError( wfMsg( 'noemail', $u->getName() ) );
+ return Status::newFatal( 'noemail', $u->getName() );
}
$ip = wfGetIP();
if( !$ip ) {
- return new WikiError( wfMsg( 'badipaddress' ) );
+ return Status::newFatal( 'badipaddress' );
}
-
- wfRunHooks( 'User::mailPasswordInternal', array(&$wgUser, &$ip, &$u) );
+
+ wfRunHooks( 'User::mailPasswordInternal', array( &$wgUser, &$ip, &$u ) );
$np = $u->randomPassword();
$u->setNewpassword( $np, $throttle );
@@ -824,7 +844,7 @@ class LoginForm {
# Run any hooks; display injected HTML if any, else redirect
$injected_html = '';
- wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
+ wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) );
if( $injected_html !== '' ) {
$this->displaySuccessfulLogin( 'loginsuccess', $injected_html );
@@ -833,7 +853,12 @@ class LoginForm {
if ( !$titleObj instanceof Title ) {
$titleObj = Title::newMainPage();
}
- $wgOut->redirect( $titleObj->getFullURL( $this->mReturnToQuery ) );
+ $redirectUrl = $titleObj->getFullURL( $this->mReturnToQuery );
+ global $wgSecureLogin;
+ if( $wgSecureLogin && !$this->mStickHTTPS ) {
+ $redirectUrl = preg_replace( '/^https:/', 'http:', $redirectUrl );
+ }
+ $wgOut->redirect( $redirectUrl );
}
}
@@ -844,11 +869,10 @@ class LoginForm {
* @private
*/
function successfulCreation() {
- global $wgUser, $wgOut;
-
+ global $wgUser;
# Run any hooks; display injected HTML
$injected_html = '';
- wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
+ wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) );
$this->displaySuccessfulLogin( 'welcomecreation', $injected_html );
}
@@ -873,22 +897,6 @@ class LoginForm {
}
/** */
- function userNotPrivilegedMessage($errors) {
- global $wgOut;
-
- $wgOut->setPageTitle( wfMsg( 'permissionserrors' ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setArticleRelated( false );
-
- $wgOut->addWikitext( $wgOut->formatPermissionsErrorMessage( $errors, 'createaccount' ) );
- // Stuff that might want to be added at the end. For example, instruc-
- // tions if blocked.
- $wgOut->addWikiMsg( 'cantcreateaccount-nonblock-text' );
-
- $wgOut->returnToMain( false );
- }
-
- /** */
function userBlockedMessage() {
global $wgOut, $wgUser;
@@ -920,14 +928,15 @@ class LoginForm {
*/
function mainLoginForm( $msg, $msgtype = 'error' ) {
global $wgUser, $wgOut, $wgHiddenPrefs, $wgEnableEmail;
- global $wgCookiePrefix, $wgLoginLanguageSelector;
+ global $wgRequest, $wgLoginLanguageSelector;
global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
-
+ global $wgSecureLogin;
+
$titleObj = SpecialPage::getTitleFor( 'Userlogin' );
-
+
if ( $this->mType == 'signup' ) {
- // Block signup here if in readonly. Keeps user from
- // going through the process (filling out data, etc)
+ // Block signup here if in readonly. Keeps user from
+ // going through the process (filling out data, etc)
// and being informed later.
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
@@ -945,12 +954,10 @@ class LoginForm {
if ( $wgUser->isLoggedIn() ) {
$this->mName = $wgUser->getName();
} else {
- $this->mName = isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ? $_COOKIE[$wgCookiePrefix.'UserName'] : null;
+ $this->mName = $wgRequest->getCookie( 'UserName' );
}
}
- $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
-
if ( $this->mType == 'signup' ) {
$template = new UsercreateTemplate();
$q = 'action=submitlogin&type=signup';
@@ -965,26 +972,29 @@ class LoginForm {
if ( !empty( $this->mReturnTo ) ) {
$returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
- if ( !empty( $this->mReturnToQuery ) )
+ if ( !empty( $this->mReturnToQuery ) ) {
$returnto .= '&returntoquery=' .
wfUrlencode( $this->mReturnToQuery );
+ }
$q .= $returnto;
$linkq .= $returnto;
}
# Pass any language selection on to the mode switch link
- if( $wgLoginLanguageSelector && $this->mLanguage )
+ if( $wgLoginLanguageSelector && $this->mLanguage ) {
$linkq .= '&uselang=' . $this->mLanguage;
+ }
- $link = '<a href="' . htmlspecialchars ( $titleObj->getLocalUrl( $linkq ) ) . '">';
+ $link = '<a href="' . htmlspecialchars ( $titleObj->getLocalURL( $linkq ) ) . '">';
$link .= wfMsgHtml( $linkmsg . 'link' ); # Calling either 'gotaccountlink' or 'nologinlink'
$link .= '</a>';
# Don't show a "create account" link if the user can't
- if( $this->showCreateOrLoginLink( $wgUser ) )
- $template->set( 'link', wfMsgWikiHtml( $linkmsg, $link ) );
- else
+ if( $this->showCreateOrLoginLink( $wgUser ) ) {
+ $template->set( 'link', wfMsgExt( $linkmsg, array( 'parseinline', 'replaceafter' ), $link ) );
+ } else {
$template->set( 'link', '' );
+ }
$template->set( 'header', '' );
$template->set( 'name', $this->mName );
@@ -993,8 +1003,9 @@ class LoginForm {
$template->set( 'email', $this->mEmail );
$template->set( 'realname', $this->mRealName );
$template->set( 'domain', $this->mDomain );
+ $template->set( 'reason', $this->mReason );
- $template->set( 'action', $titleObj->getLocalUrl( $q ) );
+ $template->set( 'action', $titleObj->getLocalURL( $q ) );
$template->set( 'message', $msg );
$template->set( 'messagetype', $msgtype );
$template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn() );
@@ -1003,7 +1014,10 @@ class LoginForm {
$template->set( 'emailrequired', $wgEmailConfirmToEdit );
$template->set( 'canreset', $wgAuth->allowPasswordChange() );
$template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
- $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember );
+ $template->set( 'usereason', $wgUser->isLoggedIn() );
+ $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) || $this->mRemember );
+ $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
+ $template->set( 'stickHTTPS', $this->mStickHTTPS );
if ( $this->mType == 'signup' ) {
if ( !self::getCreateaccountToken() ) {
@@ -1016,7 +1030,7 @@ class LoginForm {
}
$template->set( 'token', self::getLoginToken() );
}
-
+
# Prepare language selection links as needed
if( $wgLoginLanguageSelector ) {
$template->set( 'languages', $this->makeLanguageSelector() );
@@ -1032,7 +1046,7 @@ class LoginForm {
wfRunHooks( 'UserLoginForm', array( &$template ) );
}
- //Changes the title depending on permissions for creating account
+ // Changes the title depending on permissions for creating account
if ( $wgUser->isAllowed( 'createaccount' ) ) {
$wgOut->setPageTitle( wfMsg( 'userlogin' ) );
} else {
@@ -1041,7 +1055,7 @@ class LoginForm {
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
- $wgOut->disallowUserJs(); // just in case...
+ $wgOut->disallowUserJs(); // just in case...
$wgOut->addTemplate( $template );
}
@@ -1071,7 +1085,7 @@ class LoginForm {
global $wgDisableCookieCheck, $wgRequest;
return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie();
}
-
+
/**
* Get the login token from the current session
*/
@@ -1079,7 +1093,7 @@ class LoginForm {
global $wgRequest;
return $wgRequest->getSessionData( 'wsLoginToken' );
}
-
+
/**
* Randomly generate a new login token and attach it to the current session
*/
@@ -1089,7 +1103,7 @@ class LoginForm {
// because the latter reuses $_SESSION['wsEditToken']
$wgRequest->setSessionData( 'wsLoginToken', User::generateToken() );
}
-
+
/**
* Remove any login token attached to the current session
*/
@@ -1105,7 +1119,7 @@ class LoginForm {
global $wgRequest;
return $wgRequest->getSessionData( 'wsCreateaccountToken' );
}
-
+
/**
* Randomly generate a new createaccount token and attach it to the current session
*/
@@ -1113,7 +1127,7 @@ class LoginForm {
global $wgRequest;
$wgRequest->setSessionData( 'wsCreateaccountToken', User::generateToken() );
}
-
+
/**
* Remove any createaccount token attached to the current session
*/
@@ -1130,7 +1144,9 @@ class LoginForm {
$titleObj = SpecialPage::getTitleFor( 'Userlogin' );
$query = array( 'wpCookieCheck' => $type );
- if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo;
+ if ( $this->mReturnTo ) {
+ $query['returnto'] = $this->mReturnTo;
+ }
$check = $titleObj->getFullURL( $query );
return $wgOut->redirect( $check );
@@ -1143,7 +1159,7 @@ class LoginForm {
if ( !$this->hasSessionCookie() ) {
if ( $type == 'new' ) {
return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
- } else if ( $type == 'login' ) {
+ } elseif ( $type == 'login' ) {
return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
} else {
# shouldn't happen
@@ -1177,7 +1193,7 @@ class LoginForm {
foreach( $langs as $lang ) {
$lang = trim( $lang, '* ' );
$parts = explode( '|', $lang );
- if (count($parts) >= 2) {
+ if ( count( $parts ) >= 2 ) {
$links[] = $this->makeLanguageSelectorLink( $parts[0], $parts[1] );
}
}
@@ -1198,10 +1214,12 @@ class LoginForm {
global $wgUser;
$self = SpecialPage::getTitleFor( 'Userlogin' );
$attr = array( 'uselang' => $lang );
- if( $this->mType == 'signup' )
+ if( $this->mType == 'signup' ) {
$attr['type'] = 'signup';
- if( $this->mReturnTo )
+ }
+ if( $this->mReturnTo ) {
$attr['returnto'] = $this->mReturnTo;
+ }
$skin = $wgUser->getSkin();
return $skin->linkKnown(
$self,
diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php
index e23df612..39b5b284 100644
--- a/includes/specials/SpecialUserlogout.php
+++ b/includes/specials/SpecialUserlogout.php
@@ -1,33 +1,63 @@
<?php
/**
+ * Implements Special:Upload
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
/**
- * constructor
+ * Implements Special:Userlogout
+ *
+ * @ingroup SpecialPage
*/
-function wfSpecialUserlogout() {
- global $wgUser, $wgOut;
-
- /**
- * Some satellite ISPs use broken precaching schemes that log people out straight after
- * they're logged in (bug 17790). Luckily, there's a way to detect such requests.
- */
- if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], '&amp;' ) !== false ) {
- wfDebug( "Special:Userlogout request {$_SERVER['REQUEST_URI']} looks suspicious, denying.\n" );
- wfHttpError( 400, wfMsg( 'loginerror' ), wfMsg( 'suspicious-userlogout' ) );
- return;
+class SpecialUserlogout extends UnlistedSpecialPage {
+
+ function __construct() {
+ parent::__construct( 'Userlogout' );
}
-
- $oldName = $wgUser->getName();
- $wgUser->logout();
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- // Hook.
- $injected_html = '';
- wfRunHooks( 'UserLogoutComplete', array(&$wgUser, &$injected_html, $oldName) );
+ function execute( $par ) {
+ global $wgUser, $wgOut;
+
+ /**
+ * Some satellite ISPs use broken precaching schemes that log people out straight after
+ * they're logged in (bug 17790). Luckily, there's a way to detect such requests.
+ */
+ if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], '&amp;' ) !== false ) {
+ wfDebug( "Special:Userlogout request {$_SERVER['REQUEST_URI']} looks suspicious, denying.\n" );
+ wfHttpError( 400, wfMsg( 'loginerror' ), wfMsg( 'suspicious-userlogout' ) );
+ return;
+ }
+
+ $this->setHeaders();
+ $this->outputHeader();
- $wgOut->addHTML( wfMsgExt( 'logouttext', array( 'parse' ) ) . $injected_html );
- $wgOut->returnToMain();
+ $oldName = $wgUser->getName();
+ $wgUser->logout();
+
+ $wgOut->addWikiMsg( 'logouttext' );
+
+ // Hook.
+ $injected_html = '';
+ wfRunHooks( 'UserLogoutComplete', array( &$wgUser, &$injected_html, $oldName ) );
+ $wgOut->addHTML( $injected_html );
+
+ $wgOut->returnToMain();
+ }
}
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index 36caf9a6..6ea8668b 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -1,13 +1,29 @@
<?php
/**
- * Special page to allow managing user group membership
+ * Implements Special:Userrights
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 class to manage user levels rights.
+ * Special page to allow managing user group membership
+ *
* @ingroup SpecialPage
*/
class UserrightsPage extends SpecialPage {
@@ -32,10 +48,10 @@ class UserrightsPage extends SpecialPage {
public function userCanChangeRights( $user, $checkIfSelf = true ) {
$available = $this->changeableGroups();
return !empty( $available['add'] )
- or !empty( $available['remove'] )
- or ( ( $this->isself || !$checkIfSelf ) and
+ || !empty( $available['remove'] )
+ || ( ( $this->isself || !$checkIfSelf ) &&
( !empty( $available['add-self'] )
- or !empty( $available['remove-self'] ) ) );
+ || !empty( $available['remove-self'] ) ) );
}
/**
@@ -49,7 +65,7 @@ class UserrightsPage extends SpecialPage {
// any groups, it's a bit silly to give them the user search prompt.
global $wgUser, $wgRequest, $wgOut;
- if( $par ) {
+ if( $par !== null ) {
$this->mTarget = $par;
} else {
$this->mTarget = $wgRequest->getVal( 'user' );
@@ -67,7 +83,7 @@ class UserrightsPage extends SpecialPage {
$available = $this->changeableGroups();
- if ( !$this->mTarget ) {
+ if ( $this->mTarget === null ) {
/*
* If the user specified no target, and they can only
* edit their own groups, automatically set them as the
@@ -77,8 +93,9 @@ class UserrightsPage extends SpecialPage {
$this->mTarget = $wgUser->getName();
}
- if ( $this->mTarget == $wgUser->getName() )
+ if ( User::getCanonicalName( $this->mTarget ) == $wgUser->getName() ) {
$this->isself = true;
+ }
if( !$this->userCanChangeRights( $wgUser, true ) ) {
// fixme... there may be intermediate groups we can mention.
@@ -99,8 +116,9 @@ class UserrightsPage extends SpecialPage {
$this->setHeaders();
// show the general form
- if ( count( $available['add'] ) || count( $available['remove'] ) )
+ if ( count( $available['add'] ) || count( $available['remove'] ) ) {
$this->switchForm();
+ }
if( $wgRequest->wasPosted() ) {
// save settings
@@ -121,7 +139,7 @@ class UserrightsPage extends SpecialPage {
}
// show some more forms
- if( $this->mTarget ) {
+ if( $this->mTarget !== null ) {
$this->editUserGroupsForm( $this->mTarget );
}
}
@@ -139,12 +157,14 @@ class UserrightsPage extends SpecialPage {
* @return null
*/
function saveUserGroups( $username, $reason = '' ) {
- global $wgRequest, $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
+ global $wgRequest, $wgOut;
- $user = $this->fetchUser( $username );
- if( $user instanceof WikiErrorMsg ) {
- $wgOut->addWikiMsgArray( $user->getMessageKey(), $user->getMessageArgs() );
+ $status = $this->fetchUser( $username );
+ if( !$status->isOK() ) {
+ $wgOut->addWikiText( $status->getWikiText() );
return;
+ } else {
+ $user = $status->value;
}
$allgroups = $this->getAllGroups();
@@ -162,7 +182,7 @@ class UserrightsPage extends SpecialPage {
$removegroup[] = $group;
}
}
-
+
$this->doSaveUserGroups( $user, $addgroup, $removegroup, $reason );
}
@@ -247,10 +267,12 @@ class UserrightsPage extends SpecialPage {
function editUserGroupsForm( $username ) {
global $wgOut;
- $user = $this->fetchUser( $username );
- if( $user instanceof WikiErrorMsg ) {
- $wgOut->addWikiMsgArray( $user->getMessageKey(), $user->getMessageArgs() );
+ $status = $this->fetchUser( $username );
+ if( !$status->isOK() ) {
+ $wgOut->addWikiText( $status->getWikiText() );
return;
+ } else {
+ $user = $status->value;
}
$groups = $user->getGroups();
@@ -267,7 +289,7 @@ class UserrightsPage extends SpecialPage {
* return a user (or proxy) object for manipulating it.
*
* Side effects: error output for invalid access
- * @return mixed User, UserRightsProxy, or WikiErrorMsg
+ * @return Status object
*/
public function fetchUser( $username ) {
global $wgUser, $wgUserrightsInterwikiDelimiter;
@@ -278,21 +300,21 @@ class UserrightsPage extends SpecialPage {
$database = '';
} else {
list( $name, $database ) = array_map( 'trim', $parts );
-
+
if( $database == wfWikiID() ) {
$database = '';
} else {
if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) {
- return new WikiErrorMsg( 'userrights-no-interwiki' );
+ return Status::newFatal( 'userrights-no-interwiki' );
}
if( !UserRightsProxy::validDatabase( $database ) ) {
- return new WikiErrorMsg( 'userrights-nodatabase', $database );
+ return Status::newFatal( 'userrights-nodatabase', $database );
}
}
}
- if( $name == '' ) {
- return new WikiErrorMsg( 'nouserspecified' );
+ if( $name === '' ) {
+ return Status::newFatal( 'nouserspecified' );
}
if( $name{0} == '#' ) {
@@ -307,13 +329,13 @@ class UserrightsPage extends SpecialPage {
}
if( !$name ) {
- return new WikiErrorMsg( 'noname' );
+ return Status::newFatal( 'noname' );
}
} else {
$name = User::getCanonicalName( $name );
- if( !$name ) {
+ if( $name === false ) {
// invalid name
- return new WikiErrorMsg( 'nosuchusershort', $username );
+ return Status::newFatal( 'nosuchusershort', $username );
}
}
@@ -324,10 +346,10 @@ class UserrightsPage extends SpecialPage {
}
if( !$user || $user->isAnon() ) {
- return new WikiErrorMsg( 'nosuchusershort', $username );
+ return Status::newFatal( 'nosuchusershort', $username );
}
- return $user;
+ return Status::newGood( $user );
}
function makeGroupNameList( $ids ) {
@@ -352,14 +374,13 @@ class UserrightsPage extends SpecialPage {
function switchForm() {
global $wgOut, $wgScript;
$wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) .
- Xml::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', array(), wfMsg( 'userrights-lookup-user' ) ) .
- Xml::inputLabel( wfMsg( 'userrights-user-editname' ), 'user', 'username', 30, $this->mTarget ) . ' ' .
+ Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Xml::fieldset( wfMsg( 'userrights-lookup-user' ) ) .
+ Xml::inputLabel( wfMsg( 'userrights-user-editname' ), 'user', 'username', 30, str_replace( '_', ' ', $this->mTarget ) ) . ' ' .
Xml::submitButton( wfMsg( 'editusergroup' ) ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' ) . "\n"
+ Html::closeElement( 'fieldset' ) .
+ Html::closeElement( 'form' ) . "\n"
);
}
@@ -396,8 +417,9 @@ class UserrightsPage extends SpecialPage {
global $wgOut, $wgUser, $wgLang;
$list = array();
- foreach( $groups as $group )
+ foreach( $groups as $group ) {
$list[] = self::buildGroupLink( $group );
+ }
$autolist = array();
if ( $user instanceof User ) {
@@ -417,8 +439,8 @@ class UserrightsPage extends SpecialPage {
}
$wgOut->addHTML(
Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) .
- Xml::hidden( 'user', $this->mTarget ) .
- Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->mTarget ) ) .
+ Html::hidden( 'user', $this->mTarget ) .
+ Html::hidden( 'wpEditToken', $wgUser->editToken( $this->mTarget ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) .
wfMsgExt( 'editinguser', array( 'parse' ), wfEscapeWikiText( $user->getName() ) ) .
@@ -437,7 +459,8 @@ class UserrightsPage extends SpecialPage {
<tr>
<td></td>
<td class='mw-submit'>" .
- Xml::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups', 'accesskey' => 's' ) ) .
+ Xml::submitButton( wfMsg( 'saveusergroups' ),
+ array( 'name' => 'saveusergroups' ) + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'userrights-set' ) ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ) . "\n" .
@@ -511,7 +534,7 @@ class UserrightsPage extends SpecialPage {
foreach( $columns as $name => $column ) {
if( $column === array() )
continue;
- $ret .= xml::element( 'th', null, wfMsg( 'userrights-' . $name . '-col' ) );
+ $ret .= Xml::element( 'th', null, wfMsg( 'userrights-' . $name . '-col' ) );
}
$ret.= "</tr>\n<tr>\n";
foreach( $columns as $column ) {
@@ -522,7 +545,7 @@ class UserrightsPage extends SpecialPage {
$attr = $checkbox['disabled'] ? array( 'disabled' => 'disabled' ) : array();
if ( $checkbox['irreversible'] ) {
- $text = htmlspecialchars( wfMsg( 'userrights-irreversible-marker',
+ $text = htmlspecialchars( wfMsg( 'userrights-irreversible-marker',
User::getGroupMember( $group ) ) );
} else {
$text = htmlspecialchars( User::getGroupMember( $group ) );
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index ebc50bab..101823db 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -1,35 +1,56 @@
<?php
+/**
+ * Implements Special:Version
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
+ */
/**
* Give information about the version of MediaWiki, PHP, the DB and extensions
*
* @ingroup SpecialPage
- *
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
class SpecialVersion extends SpecialPage {
- private $firstExtOpened = true;
+
+ protected $firstExtOpened = false;
- static $viewvcUrls = array(
+ protected static $extensionTypes = false;
+
+ protected static $viewvcUrls = array(
'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
# Doesn't work at the time of writing but maybe some day:
'https://svn.wikimedia.org/viewvc/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
);
- function __construct(){
+ public function __construct(){
parent::__construct( 'Version' );
}
/**
* main()
*/
- function execute( $par ) {
- global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks, $wgContLang;
- $wgMessageCache->loadAllMessages();
-
+ public function execute( $par ) {
+ global $wgOut, $wgSpecialVersionShowHooks, $wgContLang;
+
$this->setHeaders();
$this->outputHeader();
$wgOut->allowClickjacking();
@@ -37,74 +58,76 @@ class SpecialVersion extends SpecialPage {
$wgOut->addHTML( Xml::openElement( 'div',
array( 'dir' => $wgContLang->getDir() ) ) );
$text =
- $this->MediaWikiCredits() .
+ $this->getMediaWikiCredits() .
$this->softwareInformation() .
- $this->extensionCredits();
+ $this->getExtensionCredits();
if ( $wgSpecialVersionShowHooks ) {
- $text .= $this->wgHooks();
+ $text .= $this->getWgHooks();
}
+
$wgOut->addWikiText( $text );
$wgOut->addHTML( $this->IPInfo() );
$wgOut->addHTML( '</div>' );
}
- /**#@+
- * @private
- */
-
/**
- * @return wiki text showing the license information
+ * Returns wiki text showing the license information.
+ *
+ * @return string
*/
- static function MediaWikiCredits() {
- global $wgContLang;
-
+ private static function getMediaWikiCredits() {
$ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) );
// This text is always left-to-right.
- $ret .= '<div dir="ltr">';
+ $ret .= '<div>';
$ret .= "__NOTOC__
- This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''',
- copyright © 2001-2010 Magnus Manske, Brion Vibber, Lee Daniel Crocker,
- Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason,
- Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor,
- Aaron Schulz, Andrew Garrett, Raimond Spekking, Alexandre Emsenhuber,
- Siebrand Mazeland, Chad Horohoe and others.
-
- MediaWiki is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- MediaWiki is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU General Public License]
- 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/licenses/old-licenses/gpl-2.0.html read it online].
- ";
+ " . self::getCopyrightAndAuthorList() . "\n
+ " . wfMsg( 'version-license-info' );
$ret .= '</div>';
return str_replace( "\t\t", '', $ret ) . "\n";
}
/**
- * @return wiki text showing the third party software versions (apache, php, mysql).
+ * Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text
+ *
+ * @return String
+ */
+ public static function getCopyrightAndAuthorList() {
+ global $wgLang;
+
+ $authorList = array(
+ 'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
+ 'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
+ 'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
+ 'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
+ 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
+ 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
+ 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Ashar Voultoiz',
+ wfMsg( 'version-poweredby-others' )
+ );
+
+ return wfMsg( 'version-poweredby-credits', date( 'Y' ),
+ $wgLang->listToText( $authorList ) );
+ }
+
+ /**
+ * Returns wiki text showing the third party software versions (apache, php, mysql).
+ *
+ * @return string
*/
static function softwareInformation() {
$dbr = wfGetDB( DB_SLAVE );
// Put the software in an array of form 'name' => 'version'. All messages should
// be loaded here, so feel free to use wfMsg*() in the 'name'. Raw HTML or wikimarkup
- // can be used
+ // can be used.
$software = array();
$software['[http://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
$software['[http://www.php.net/ PHP]'] = phpversion() . " (" . php_sapi_name() . ")";
- $software[$dbr->getSoftwareLink()] = $dbr->getServerVersion();
+ $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
- // Allow a hook to add/remove items
+ // Allow a hook to add/remove items.
wfRunHooks( 'SoftwareInfo', array( &$software ) );
$out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) .
@@ -113,17 +136,19 @@ class SpecialVersion extends SpecialPage {
<th>" . wfMsg( 'version-software-product' ) . "</th>
<th>" . wfMsg( 'version-software-version' ) . "</th>
</tr>\n";
+
foreach( $software as $name => $version ) {
$out .= "<tr>
<td>" . $name . "</td>
<td>" . $version . "</td>
</tr>\n";
- }
+ }
+
return $out . Xml::closeElement( 'table' );
}
/**
- * Return a string of the MediaWiki version with SVN revision if available
+ * Return a string of the MediaWiki version with SVN revision if available.
*
* @return mixed
*/
@@ -151,20 +176,23 @@ class SpecialVersion extends SpecialPage {
/**
* Return a wikitext-formatted string of the MediaWiki version with a link to
- * the SVN revision if available
+ * the SVN revision if available.
*
* @return mixed
*/
public static function getVersionLinked() {
global $wgVersion, $IP;
wfProfileIn( __METHOD__ );
+
$info = self::getSvnInfo( $IP );
- if ( isset( $info['checkout-rev'] ) ) {
+
+ if ( isset( $info['checkout-rev'] ) ) {
$linkText = wfMsg(
'version-svn-revision',
isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
$info['checkout-rev']
);
+
if ( isset( $info['viewvc-url'] ) ) {
$version = "$wgVersion [{$info['viewvc-url']} $linkText]";
} else {
@@ -173,54 +201,115 @@ class SpecialVersion extends SpecialPage {
} else {
$version = $wgVersion;
}
+
wfProfileOut( __METHOD__ );
return $version;
}
- /** Generate wikitext showing extensions name, URL, author and description */
- function extensionCredits() {
+ /**
+ * Returns an array with the base extension types.
+ * Type is stored as array key, the message as array value.
+ *
+ * TODO: ideally this would return all extension types, including
+ * those added by SpecialVersionExtensionTypes. This is not possible
+ * since this hook is passing along $this though.
+ *
+ * @since 1.17
+ *
+ * @return array
+ */
+ public static function getExtensionTypes() {
+ if ( self::$extensionTypes === false ) {
+ self::$extensionTypes = array(
+ 'specialpage' => wfMsg( 'version-specialpages' ),
+ 'parserhook' => wfMsg( 'version-parserhooks' ),
+ 'variable' => wfMsg( 'version-variables' ),
+ 'media' => wfMsg( 'version-mediahandlers' ),
+ 'skin' => wfMsg( 'version-skins' ),
+ 'other' => wfMsg( 'version-other' ),
+ );
+
+ wfRunHooks( 'ExtensionTypes', array( &self::$extensionTypes ) );
+ }
+
+ return self::$extensionTypes;
+ }
+
+ /**
+ * Returns the internationalized name for an extension type.
+ *
+ * @since 1.17
+ *
+ * @param $type String
+ *
+ * @return string
+ */
+ public static function getExtensionTypeName( $type ) {
+ $types = self::getExtensionTypes();
+ return isset( $types[$type] ) ? $types[$type] : $types['other'];
+ }
+
+ /**
+ * Generate wikitext showing extensions name, URL, author and description.
+ *
+ * @return String: Wikitext
+ */
+ function getExtensionCredits() {
global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions;
- if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) )
+ if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) && !count( $wgSkinExtensionFunctions ) ) {
return '';
+ }
- $extensionTypes = array(
- 'specialpage' => wfMsg( 'version-specialpages' ),
- 'parserhook' => wfMsg( 'version-parserhooks' ),
- 'variable' => wfMsg( 'version-variables' ),
- 'media' => wfMsg( 'version-mediahandlers' ),
- 'other' => wfMsg( 'version-other' ),
- );
+ $extensionTypes = self::getExtensionTypes();
+
+ /**
+ * @deprecated as of 1.17, use hook ExtensionTypes instead.
+ */
wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
$out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) .
Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-ext' ) );
- foreach ( $extensionTypes as $type => $text ) {
- if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) {
- $out .= $this->openExtType( $text, 'credits-' . $type );
-
- usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
-
- foreach ( $wgExtensionCredits[$type] as $extension ) {
- $out .= $this->formatCredits( $extension );
- }
+ // Make sure the 'other' type is set to an array.
+ if ( !array_key_exists( 'other', $wgExtensionCredits ) ) {
+ $wgExtensionCredits['other'] = array();
+ }
+
+ // Find all extensions that do not have a valid type and give them the type 'other'.
+ foreach ( $wgExtensionCredits as $type => $extensions ) {
+ if ( !array_key_exists( $type, $extensionTypes ) ) {
+ $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions );
+ }
+ }
+
+ // Loop through the extension categories to display their extensions in the list.
+ foreach ( $extensionTypes as $type => $message ) {
+ if ( $type != 'other' ) {
+ $out .= $this->getExtensionCategory( $type, $message );
}
}
+
+ // We want the 'other' type to be last in the list.
+ $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
if ( count( $wgExtensionFunctions ) ) {
$out .= $this->openExtType( wfMsg( 'version-extension-functions' ), 'extension-functions' );
$out .= '<tr><td colspan="4">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
}
- if ( $cnt = count( $tags = $wgParser->getTags() ) ) {
- for ( $i = 0; $i < $cnt; ++$i )
+ $tags = $wgParser->getTags();
+ $cnt = count( $tags );
+
+ if ( $cnt ) {
+ for ( $i = 0; $i < $cnt; ++$i ) {
$tags[$i] = "&lt;{$tags[$i]}&gt;";
+ }
$out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ), 'parser-tags' );
$out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n";
}
- if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) {
+ if( count( $fhooks = $wgParser->getFunctionHooks() ) ) {
$out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ), 'parser-function-hooks' );
$out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
}
@@ -229,11 +318,43 @@ class SpecialVersion extends SpecialPage {
$out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ), 'skin-extension-functions' );
$out .= '<tr><td colspan="4">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n";
}
+
$out .= Xml::closeElement( 'table' );
+
return $out;
}
+
+ /**
+ * Creates and returns the HTML for a single extension category.
+ *
+ * @since 1.17
+ *
+ * @param $type String
+ * @param $message String
+ *
+ * @return string
+ */
+ protected function getExtensionCategory( $type, $message ) {
+ global $wgExtensionCredits;
+
+ $out = '';
+
+ if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) {
+ $out .= $this->openExtType( $message, 'credits-' . $type );
+
+ usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
+
+ foreach ( $wgExtensionCredits[$type] as $extension ) {
+ $out .= $this->getCreditsForExtension( $extension );
+ }
+ }
- /** Callback to sort extensions by type */
+ return $out;
+ }
+
+ /**
+ * Callback to sort extensions by type.
+ */
function compare( $a, $b ) {
global $wgLang;
if( $a['name'] === $b['name'] ) {
@@ -245,8 +366,16 @@ class SpecialVersion extends SpecialPage {
}
}
- function formatCredits( $extension ) {
+ /**
+ * Creates and formats the creidts for a single extension and returns this.
+ *
+ * @param $extension Array
+ *
+ * @return string
+ */
+ function getCreditsForExtension( array $extension ) {
$name = isset( $extension['name'] ) ? $extension['name'] : '[no name]';
+
if ( isset( $extension['path'] ) ) {
$svnInfo = self::getSvnInfo( dirname($extension['path']) );
$directoryRev = isset( $svnInfo['directory-rev'] ) ? $svnInfo['directory-rev'] : null;
@@ -258,12 +387,13 @@ class SpecialVersion extends SpecialPage {
$viewvcUrl = null;
}
- # Make main link (or just the name if there is no URL)
+ # Make main link (or just the name if there is no URL).
if ( isset( $extension['url'] ) ) {
$mainLink = "[{$extension['url']} $name]";
} else {
$mainLink = $name;
}
+
if ( isset( $extension['version'] ) ) {
$versionText = '<span class="mw-version-ext-version">' .
wfMsg( 'version-version', $extension['version'] ) .
@@ -272,7 +402,7 @@ class SpecialVersion extends SpecialPage {
$versionText = '';
}
- # Make subversion text/link
+ # Make subversion text/link.
if ( $checkoutRev ) {
$svnText = wfMsg( 'version-svn-revision', $directoryRev, $checkoutRev );
$svnText = isset( $viewvcUrl ) ? "[$viewvcUrl $svnText]" : $svnText;
@@ -280,11 +410,13 @@ class SpecialVersion extends SpecialPage {
$svnText = false;
}
- # Make description text
+ # Make description text.
$description = isset ( $extension['description'] ) ? $extension['description'] : '';
+
if( isset ( $extension['descriptionmsg'] ) ) {
- # Look for a localized description
+ # Look for a localized description.
$descriptionMsg = $extension['descriptionmsg'];
+
if( is_array( $descriptionMsg ) ) {
$descriptionMsgKey = $descriptionMsg[0]; // Get the message key
array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
@@ -306,17 +438,21 @@ class SpecialVersion extends SpecialPage {
$extNameVer = "<tr>
<td colspan=\"2\"><em>$mainLink $versionText</em></td>";
}
+
$author = isset ( $extension['author'] ) ? $extension['author'] : array();
$extDescAuthor = "<td>$description</td>
<td>" . $this->listToText( (array)$author, false ) . "</td>
</tr>\n";
+
return $extNameVer . $extDescAuthor;
}
/**
- * @return string
+ * Generate wikitext showing hooks in $wgHooks.
+ *
+ * @return String: wikitext
*/
- function wgHooks() {
+ private function getWgHooks() {
global $wgHooks;
if ( count( $wgHooks ) ) {
@@ -346,32 +482,39 @@ class SpecialVersion extends SpecialPage {
$opt = array( 'colspan' => 4 );
$out = '';
- if( !$this->firstExtOpened ) {
+ if( $this->firstExtOpened ) {
// Insert a spacing line
- $out .= '<tr class="sv-space">' . Xml::element( 'td', $opt ) . "</tr>\n";
+ $out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n";
}
- $this->firstExtOpened = false;
-
- if( $name )
+ $this->firstExtOpened = true;
+
+ if( $name ) {
$opt['id'] = "sv-$name";
+ }
$out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n";
+
return $out;
}
/**
- * @return string
+ * Get information about client's IP address.
+ *
+ * @return String: HTML fragment
*/
- function IPInfo() {
+ private function IPInfo() {
$ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) );
return "<!-- visited from $ip -->\n" .
"<span style='display:none'>visited from $ip</span>";
}
/**
- * @param array $list
- * @param bool $sort
- * @return string
+ * Convert an array of items into a list for display.
+ *
+ * @param $list Array of elements to display
+ * @param $sort Boolean: whether to sort the items in $list
+ *
+ * @return String
*/
function listToText( $list, $sort = true ) {
$cnt = count( $list );
@@ -391,9 +534,12 @@ class SpecialVersion extends SpecialPage {
}
/**
- * @param mixed $list Will convert an array to string if given and return
- * the paramater unaltered otherwise
- * @return mixed
+ * Convert an array or object to a string for display.
+ *
+ * @param $list Mixed: will convert an array to string if given and return
+ * the paramater unaltered otherwise
+ *
+ * @return Mixed
*/
static function arrayToString( $list ) {
if( is_array( $list ) && count( $list ) == 1 )
@@ -462,41 +608,47 @@ class SpecialVersion extends SpecialPage {
}
}
}
+
return false;
}
- // subversion is release 1.4 or above
+ // Subversion is release 1.4 or above.
if ( count( $lines ) < 11 ) {
return false;
}
+
$info = array(
'checkout-rev' => intval( trim( $lines[3] ) ),
'url' => trim( $lines[4] ),
'repo-url' => trim( $lines[5] ),
'directory-rev' => intval( trim( $lines[10] ) )
);
+
if ( isset( self::$viewvcUrls[$info['repo-url']] ) ) {
$viewvc = str_replace(
$info['repo-url'],
self::$viewvcUrls[$info['repo-url']],
$info['url']
);
- $pathRelativeToRepo = substr( $info['url'], strlen( $info['repo-url'] ) );
+
$viewvc .= '/?pathrev=';
$viewvc .= urlencode( $info['checkout-rev'] );
$info['viewvc-url'] = $viewvc;
}
+
return $info;
}
/**
* Retrieve the revision number of a Subversion working directory.
*
- * @param String $dir Directory of the svn checkout
- * @return int revision number as int
+ * @param $dir String: directory of the svn checkout
+ *
+ * @return Integer: revision number as int
*/
public static function getSvnRevision( $dir ) {
$info = self::getSvnInfo( $dir );
+
if ( $info === false ) {
return false;
} elseif ( isset( $info['checkout-rev'] ) ) {
@@ -506,5 +658,4 @@ class SpecialVersion extends SpecialPage {
}
}
- /**#@-*/
}
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
index 5e5a4f17..b588dbf0 100644
--- a/includes/specials/SpecialWantedcategories.php
+++ b/includes/specials/SpecialWantedcategories.php
@@ -1,5 +1,24 @@
<?php
/**
+ * Implements Special:Wantedcategories
+ *
+ * Copyright © 2005 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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
*/
@@ -8,10 +27,6 @@
* A querypage to list the most wanted categories - implements Special:Wantedcategories
*
* @ingroup SpecialPage
- *
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
class WantedCategoriesPage extends WantedQueryPage {
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index 189b9d8b..d6c1157b 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -1,17 +1,33 @@
<?php
-/*
+/**
+ * Implements Special:Wantedfiles
+ *
+ * Copyright © 2008 Soxred93
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
+ * @author Soxred93 <soxred93@gmail.com>
*/
/**
- * Querypage that lists the most wanted files - implements Special:Wantedfiles
+ * Querypage that lists the most wanted files
*
* @ingroup SpecialPage
- *
- * @author Soxred93 <soxred93@gmail.com>
- * @copyright Copyright © 2008, Soxred93
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
class WantedFilesPage extends WantedQueryPage {
@@ -19,9 +35,19 @@ class WantedFilesPage extends WantedQueryPage {
return 'Wantedfiles';
}
+ /**
+ * KLUGE: The results may contain false positives for files
+ * that exist e.g. in a shared repo. Setting this at least
+ * keeps them from showing up as redlinks in the output, even
+ * if it doesn't fix the real problem (bug 6220).
+ */
+ function forceExistenceCheck() {
+ return true;
+ }
+
function getSQL() {
$dbr = wfGetDB( DB_SLAVE );
- list( $imagelinks, $page ) = $dbr->tableNamesN( 'imagelinks', 'page' );
+ list( $imagelinks, $image ) = $dbr->tableNamesN( 'imagelinks', 'image' );
$name = $dbr->addQuotes( $this->getName() );
return
"
@@ -31,8 +57,8 @@ class WantedFilesPage extends WantedQueryPage {
il_to as title,
COUNT(*) as value
FROM $imagelinks
- LEFT JOIN $page ON il_to = page_title AND page_namespace = ". NS_FILE ."
- WHERE page_title IS NULL
+ LEFT JOIN $image ON il_to = img_name
+ WHERE img_name IS NULL
GROUP BY il_to
";
}
diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php
index eeca87ab..4e1611bc 100644
--- a/includes/specials/SpecialWantedpages.php
+++ b/includes/specials/SpecialWantedpages.php
@@ -1,17 +1,35 @@
<?php
/**
+ * Implements Special:Wantedpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
*/
/**
- * implements Special:Wantedpages
+ * A special page that lists most linked pages that does not exist
+ *
* @ingroup SpecialPage
*/
class WantedPagesPage extends WantedQueryPage {
var $nlinks;
- function WantedPagesPage( $inc = false, $nlinks = true ) {
+ function __construct( $inc = false, $nlinks = true ) {
$this->setListoutput( $inc );
$this->nlinks = $nlinks;
}
diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php
index 329d7a3f..ae43c237 100644
--- a/includes/specials/SpecialWantedtemplates.php
+++ b/includes/specials/SpecialWantedtemplates.php
@@ -1,19 +1,35 @@
<?php
/**
+ * Implements Special:Wantedtemplates
+ *
+ * Copyright © 2008, Danny B.
+ * Based on SpecialWantedcategories.php by Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * makeWlhLink() taken from SpecialMostlinkedtemplates by Rob Church <robchur@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
+ * @author Danny B.
*/
/**
- * A querypage to list the most wanted templates - implements Special:Wantedtemplates
- * based on SpecialWantedcategories.php by Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * makeWlhLink() taken from SpecialMostlinkedtemplates by Rob Church <robchur@gmail.com>
+ * A querypage to list the most wanted templates
*
* @ingroup SpecialPage
- *
- * @author Danny B.
- * @copyright Copyright © 2008, Danny B.
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
class WantedTemplatesPage extends WantedQueryPage {
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index c32af2ae..bb1c194d 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -1,5 +1,22 @@
<?php
/**
+ * Implements Special:Watchlist
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 Watchlist
*/
@@ -21,7 +38,7 @@ function wfSpecialWatchlist( $par ) {
$wgUser->saveSettings();
}
- global $wgServer, $wgScriptPath, $wgFeedClasses;
+ global $wgFeedClasses;
$apiParams = array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
'wlowner' => $wgUser->getName(), 'wltoken' => $wlToken );
$feedTemplate = wfScript('api').'?';
@@ -51,8 +68,7 @@ function wfSpecialWatchlist( $par ) {
$wgOut->setPageTitle( wfMsg( 'watchlist' ) );
- $sub = wfMsgExt( 'watchlistfor', 'parseinline', $wgUser->getName() );
- $sub .= '<br />' . WatchlistEditor::buildTools( $wgUser->getSkin() );
+ $sub = wfMsgExt( 'watchlistfor2', array( 'parseinline', 'replaceafter' ), $wgUser->getName(), WatchlistEditor::buildTools( $wgUser->getSkin() ) );
$wgOut->setSubtitle( $sub );
if( ( $mode = WatchlistEditor::getMode( $wgRequest, $par ) ) !== false ) {
@@ -89,7 +105,7 @@ function wfSpecialWatchlist( $par ) {
$prefs['days'] = floatval( $wgUser->getOption( 'watchlistdays' ) );
$prefs['hideminor'] = $wgUser->getBoolOption( 'watchlisthideminor' );
$prefs['hidebots'] = $wgUser->getBoolOption( 'watchlisthidebots' );
- $prefs['hideanons'] = $wgUser->getBoolOption( 'watchlisthideanon' );
+ $prefs['hideanons'] = $wgUser->getBoolOption( 'watchlisthideanons' );
$prefs['hideliu'] = $wgUser->getBoolOption( 'watchlisthideliu' );
$prefs['hideown' ] = $wgUser->getBoolOption( 'watchlisthideown' );
$prefs['hidepatrolled' ] = $wgUser->getBoolOption( 'watchlisthidepatrolled' );
@@ -155,10 +171,11 @@ function wfSpecialWatchlist( $par ) {
return;
}
- if( $days <= 0 ) {
- $andcutoff = '';
- } else {
- $andcutoff = "rc_timestamp > '".$dbr->timestamp( time() - intval( $days * 86400 ) )."'";
+ # Possible where conditions
+ $conds = array();
+
+ if( $days > 0 ) {
+ $conds[] = "rc_timestamp > '".$dbr->timestamp( time() - intval( $days * 86400 ) )."'";
}
# If the watchlist is relatively short, it's simplest to zip
@@ -170,21 +187,35 @@ function wfSpecialWatchlist( $par ) {
# Up estimate of watched items by 15% to compensate for talk pages...
# Toggles
- $andHideOwn = $hideOwn ? "rc_user != $uid" : '';
- $andHideBots = $hideBots ? "rc_bot = 0" : '';
- $andHideMinor = $hideMinor ? "rc_minor = 0" : '';
- $andHideLiu = $hideLiu ? "rc_user = 0" : '';
- $andHideAnons = $hideAnons ? "rc_user != 0" : '';
- $andHidePatrolled = $wgUser->useRCPatrol() && $hidePatrolled ? "rc_patrolled != 1" : '';
+ if( $hideOwn ) {
+ $conds[] = "rc_user != $uid";
+ }
+ if( $hideBots ) {
+ $conds[] = 'rc_bot = 0';
+ }
+ if( $hideMinor ) {
+ $conds[] = 'rc_minor = 0';
+ }
+ if( $hideLiu ) {
+ $conds[] = 'rc_user = 0';
+ }
+ if( $hideAnons ) {
+ $conds[] = 'rc_user != 0';
+ }
+ if ( $wgUser->useRCPatrol() && $hidePatrolled ) {
+ $conds[] = 'rc_patrolled != 1';
+ }
+ if( $nameSpaceClause ) {
+ $conds[] = $nameSpaceClause;
+ }
# Toggle watchlist content (all recent edits or just the latest)
if( $wgUser->getOption( 'extendwatchlist' )) {
- $andLatest='';
$limitWatchlist = intval( $wgUser->getOption( 'wllimit' ) );
$usePage = false;
} else {
# Top log Ids for a page are not stored
- $andLatest = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG;
+ $conds[] = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG;
$limitWatchlist = 0;
$usePage = true;
}
@@ -208,14 +239,13 @@ function wfSpecialWatchlist( $par ) {
'id' => 'mw-watchlist-resetbutton' ) ) .
wfMsgExt( 'wlheader-showupdated', array( 'parseinline' ) ) . ' ' .
Xml::submitButton( wfMsg( 'enotif_reset' ), array( 'name' => 'dummy' ) ) .
- Xml::hidden( 'reset', 'all' ) .
+ Html::hidden( 'reset', 'all' ) .
Xml::closeElement( 'form' );
}
$form .= '<hr />';
$tables = array( 'recentchanges', 'watchlist' );
$fields = array( "{$recentchanges}.*" );
- $conds = array();
$join_conds = array(
'watchlist' => array('INNER JOIN',"wl_user='{$uid}' AND wl_namespace=rc_namespace AND wl_title=rc_title"),
);
@@ -226,15 +256,6 @@ function wfSpecialWatchlist( $par ) {
if( $limitWatchlist ) {
$options['LIMIT'] = $limitWatchlist;
}
- if( $andcutoff ) $conds[] = $andcutoff;
- if( $andLatest ) $conds[] = $andLatest;
- if( $andHideOwn ) $conds[] = $andHideOwn;
- if( $andHideBots ) $conds[] = $andHideBots;
- if( $andHideMinor ) $conds[] = $andHideMinor;
- if( $andHideLiu ) $conds[] = $andHideLiu;
- if( $andHideAnons ) $conds[] = $andHideAnons;
- if( $andHidePatrolled ) $conds[] = $andHidePatrolled;
- if( $nameSpaceClause ) $conds[] = $nameSpaceClause;
$rollbacker = $wgUser->isAllowed('rollback');
if ( $usePage || $rollbacker ) {
@@ -287,29 +308,27 @@ function wfSpecialWatchlist( $par ) {
$form .= $wlInfo;
$form .= $cutofflinks;
$form .= $wgLang->pipeList( $links );
- $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) );
+ $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl(), 'id' => 'mw-watchlist-form-namespaceselector' ) );
$form .= '<hr /><p>';
- $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . '&nbsp;';
- $form .= Xml::namespaceSelector( $nameSpace, '' ) . '&nbsp;';
- $form .= Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $invert ) . '&nbsp;';
+ $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . '&#160;';
+ $form .= Xml::namespaceSelector( $nameSpace, '' ) . '&#160;';
+ $form .= Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $invert ) . '&#160;';
$form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '</p>';
- $form .= Xml::hidden( 'days', $days );
+ $form .= Html::hidden( 'days', $days );
if( $hideMinor )
- $form .= Xml::hidden( 'hideMinor', 1 );
+ $form .= Html::hidden( 'hideMinor', 1 );
if( $hideBots )
- $form .= Xml::hidden( 'hideBots', 1 );
+ $form .= Html::hidden( 'hideBots', 1 );
if( $hideAnons )
- $form .= Xml::hidden( 'hideAnons', 1 );
+ $form .= Html::hidden( 'hideAnons', 1 );
if( $hideLiu )
- $form .= Xml::hidden( 'hideLiu', 1 );
+ $form .= Html::hidden( 'hideLiu', 1 );
if( $hideOwn )
- $form .= Xml::hidden( 'hideOwn', 1 );
+ $form .= Html::hidden( 'hideOwn', 1 );
$form .= Xml::closeElement( 'form' );
$form .= Xml::closeElement( 'fieldset' );
$wgOut->addHTML( $form );
- $wgOut->addHTML( ChangesList::flagLegend() );
-
# If there's nothing to show, stop here
if( $numRows == 0 ) {
$wgOut->addWikiMsg( 'watchnochange' );
@@ -320,7 +339,7 @@ function wfSpecialWatchlist( $par ) {
/* Do link batch query */
$linkBatch = new LinkBatch;
- while ( $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
$userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text );
if ( $row->rc_user != 0 ) {
$linkBatch->add( NS_USER, $userNameUnderscored );
@@ -337,7 +356,7 @@ function wfSpecialWatchlist( $par ) {
$s = $list->beginRecentChangesList();
$counter = 1;
- while ( $obj = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $obj ) {
# Make RC entry
$rc = RecentChange::newFromRow( $obj );
$rc->counter = $counter++;
@@ -364,7 +383,6 @@ function wfSpecialWatchlist( $par ) {
}
$s .= $list->endRecentChangesList();
- $dbr->freeResult( $res );
$wgOut->addHTML( $s );
}
@@ -444,8 +462,9 @@ function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) {
/**
* Count the number of items on a user's watchlist
*
- * @param $talk Include talk pages
- * @return integer
+ * @param $user User object
+ * @param $talk Boolean: include talk pages
+ * @return Integer
*/
function wlCountItems( &$user, $talk = true ) {
$dbr = wfGetDB( DB_SLAVE, 'watchlist' );
@@ -455,7 +474,6 @@ function wlCountItems( &$user, $talk = true ) {
array( 'wl_user' => $user->mId ), 'wlCountItems' );
$row = $dbr->fetchObject( $res );
$count = $row->count;
- $dbr->freeResult( $res );
# Halve to remove talk pages if needed
if( !$talk )
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index b63c0eee..360f3f68 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -1,13 +1,29 @@
<?php
/**
- * @todo Use some variant of Pager or something; the pagination here is lousy.
+ * Implements Special:Whatlinkshere
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @ingroup SpecialPage
+ * @todo Use some variant of Pager or something; the pagination here is lousy.
*/
/**
- * implements Special:Whatlinkshere
+ * Implements Special:Whatlinkshere
+ *
* @ingroup SpecialPage
*/
class SpecialWhatLinksHere extends SpecialPage {
@@ -60,7 +76,7 @@ class SpecialWhatLinksHere extends SpecialPage {
return;
}
- $this->selfTitle = SpecialPage::getTitleFor( 'Whatlinkshere', $this->target->getPrefixedDBkey() );
+ $this->selfTitle = $this->getTitle( $this->target->getPrefixedDBkey() );
$wgOut->setPageTitle( wfMsg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
$wgOut->setSubtitle( wfMsg( 'whatlinkshere-backlink', $this->skin->link( $this->target, $this->target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) );
@@ -171,29 +187,25 @@ class SpecialWhatLinksHere extends SpecialPage {
// templatelinks comes second so that the templatelinks row overwrites the
// pagelinks row, so we get (inclusion) rather than nothing
if( $fetchlinks ) {
- while ( $row = $dbr->fetchObject( $plRes ) ) {
+ foreach ( $plRes as $row ) {
$row->is_template = 0;
$row->is_image = 0;
$rows[$row->page_id] = $row;
}
- $dbr->freeResult( $plRes );
-
}
if( !$hidetrans ) {
- while ( $row = $dbr->fetchObject( $tlRes ) ) {
+ foreach ( $tlRes as $row ) {
$row->is_template = 1;
$row->is_image = 0;
$rows[$row->page_id] = $row;
}
- $dbr->freeResult( $tlRes );
}
if( !$hideimages ) {
- while ( $row = $dbr->fetchObject( $ilRes ) ) {
+ foreach ( $ilRes as $row ) {
$row->is_template = 0;
$row->is_image = 1;
$rows[$row->page_id] = $row;
}
- $dbr->freeResult( $ilRes );
}
// Sort by key and then change the keys to 0-based indices
@@ -224,7 +236,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$wgOut->addHTML( $prevnext );
}
- $wgOut->addHTML( $this->listStart() );
+ $wgOut->addHTML( $this->listStart( $level ) );
foreach ( $rows as $row ) {
$nt = Title::makeTitle( $row->page_namespace, $row->page_title );
@@ -244,8 +256,8 @@ class SpecialWhatLinksHere extends SpecialPage {
}
}
- protected function listStart() {
- return Xml::openElement( 'ul', array ( 'id' => 'mw-whatlinkshere-list' ) );
+ protected function listStart( $level ) {
+ return Xml::openElement( 'ul', ( $level ? array() : array( 'id' => 'mw-whatlinkshere-list' ) ) );
}
protected function listItem( $row, $nt, $notClose = false ) {
@@ -303,7 +315,7 @@ class SpecialWhatLinksHere extends SpecialPage {
protected function wlhLink( Title $target, $text ) {
static $title = null;
if ( $title === null )
- $title = SpecialPage::getTitleFor( 'Whatlinkshere' );
+ $title = $this->getTitle();
return $this->skin->linkKnown(
$title,
@@ -368,9 +380,9 @@ class SpecialWhatLinksHere extends SpecialPage {
$f = Xml::openElement( 'form', array( 'action' => $wgScript ) );
# Values that should not be forgotten
- $f .= Xml::hidden( 'title', SpecialPage::getTitleFor( 'Whatlinkshere' )->getPrefixedText() );
+ $f .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
- $f .= Xml::hidden( $name, $value );
+ $f .= Html::hidden( $name, $value );
}
$f .= Xml::fieldset( wfMsg( 'whatlinkshere' ) );
@@ -382,7 +394,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$f .= ' ';
# Namespace selector
- $f .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . '&nbsp;' .
+ $f .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . '&#160;' .
Xml::namespaceSelector( $namespace, '' );
$f .= ' ';
diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php
index a5d60d2f..90c1f441 100644
--- a/includes/specials/SpecialWithoutinterwiki.php
+++ b/includes/specials/SpecialWithoutinterwiki.php
@@ -1,14 +1,31 @@
<?php
/**
+ * Implements Special:Withoutinterwiki
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
*/
/**
* Special page lists pages without language links
*
* @ingroup SpecialPage
- * @author Rob Church <robchur@gmail.com>
*/
class WithoutInterwikiPage extends PageQueryPage {
private $prefix = '';
@@ -31,7 +48,7 @@ class WithoutInterwikiPage extends PageQueryPage {
return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'withoutinterwiki-legend' ) ) .
- Xml::hidden( 'title', $t->getPrefixedText() ) .
+ Html::hidden( 'title', $t->getPrefixedText() ) .
Xml::inputLabel( wfMsg( 'allpagesprefix' ), 'prefix', 'wiprefix', 20, $prefix ) . ' ' .
Xml::submitButton( wfMsg( 'withoutinterwiki-submit' ) ) .
Xml::closeElement( 'fieldset' ) .
@@ -75,7 +92,7 @@ class WithoutInterwikiPage extends PageQueryPage {
}
function wfSpecialWithoutinterwiki() {
- global $wgRequest, $wgContLang;
+ global $wgRequest;
list( $limit, $offset ) = wfCheckLimits();
// Only searching the mainspace anyway
$prefix = Title::capitalize( $wgRequest->getVal( 'prefix' ), NS_MAIN );
diff --git a/includes/templates/NoLocalSettings.php b/includes/templates/NoLocalSettings.php
index 45b758a9..9001e3ba 100644
--- a/includes/templates/NoLocalSettings.php
+++ b/includes/templates/NoLocalSettings.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Template used when there is no LocalSettings.php file
+ *
* @file
* @ingroup Templates
*/
@@ -7,31 +9,20 @@
if ( !isset( $wgVersion ) ) {
$wgVersion = 'VERSION';
}
+$script = $_SERVER['SCRIPT_NAME'];
+$path = pathinfo( $script, PATHINFO_DIRNAME ) . '/';
+$path = str_replace( '//', '/', $path );
+$ext = pathinfo( $script, PATHINFO_EXTENSION );
-$scriptName = $_SERVER['SCRIPT_NAME'];
-$ext = substr( $scriptName, strrpos( $scriptName, "." ) + 1 );
-$path = '';
-# Add any directories in the main folder that could contain an entrypoint (even possibly).
-# We cannot just do a dir listing here, as we do not know where it is yet
-# These must not also be the names of subfolders that may contain an entrypoint
-$topdirs = array( 'extensions', 'includes' );
-foreach( $topdirs as $dir ){
- # Check whether a directory by this name is in the path
- if( strrpos( $scriptName, "/" . $dir . "/" ) ){
- # If so, check whether it is the right folder
- # First, get the number of directories up it is (to generate path)
- $numToGoUp = substr_count( substr( $scriptName, strrpos( $scriptName, "/" . $dir . "/" ) + 1 ), "/" );
- # And generate the path using ..'s
- for( $i = 0; $i < $numToGoUp; $i++ ){
- $realPath = "../" . $realPath;
- }
- # Checking existance (using the image here as it is something not likely to change, and to always be here)
- if( file_exists( $realPath . "skins/common/images/mediawiki.png" ) ) {
- # If so, get the path that we can use in this file, and stop looking
- $path = substr( $scriptName, 0, strrpos( $scriptName, "/" . $dir . "/" ) + 1 );
- break;
- }
- }
+# Check to see if the installer is running
+if ( !function_exists( 'session_name' ) ) {
+ $installerStarted = false;
+} else {
+ session_name( 'mw_installer_session' );
+ $oldReporting = error_reporting( E_ALL & ~E_NOTICE );
+ $success = session_start();
+ error_reporting( $oldReporting );
+ $installerStarted = ( $success && isset( $_SESSION['installData'] ) );
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@@ -57,13 +48,16 @@ foreach( $topdirs as $dir ){
<h1>MediaWiki <?php echo htmlspecialchars( $wgVersion ) ?></h1>
<div class='error'>
+ <p>LocalSettings.php not found.</p>
+ <p>
<?php
- if ( file_exists( 'config/LocalSettings.php' ) ) {
- echo( 'To complete the installation, move <tt>config/LocalSettings.php</tt> to the parent directory.' );
+ 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 ) . "config/index." . htmlspecialchars( $ext ) . "\" title='setup'> set up the wiki</a> first." );
+ echo( "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\"> set up the wiki</a> first." );
}
?>
+ </p>
</div>
</body>
diff --git a/includes/templates/PHP4.php b/includes/templates/PHP4.php
index b071ebd5..69f7d55d 100644
--- a/includes/templates/PHP4.php
+++ b/includes/templates/PHP4.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Template used when the installer detects that this is PHP 4
+ *
* @file
* @ingroup Templates
*/
@@ -17,7 +19,7 @@ if( isset( $_SERVER['SCRIPT_NAME'] ) ) {
} else {
$scriptUrl = '';
}
-if ( preg_match( '!^(.*)/config/[^/]*.php$!', $scriptUrl, $m ) ) {
+if ( preg_match( '!^(.*)/(mw-)?config/[^/]*.php$!', $scriptUrl, $m ) ) {
$baseUrl = $m[1];
} elseif ( preg_match( '!^(.*)/[^/]*.php$!', $scriptUrl, $m ) ) {
$baseUrl = $m[1];
@@ -56,7 +58,7 @@ if ( preg_match( '!^(.*)/config/[^/]*.php$!', $scriptUrl, $m ) ) {
<h1>MediaWiki <?php echo htmlspecialchars( $wgVersion ); ?></h1>
<div class='error'>
<p>
- MediaWiki requires PHP 5.0.0 or higher. You are running PHP
+ MediaWiki requires PHP 5.1.x or higher. You are running PHP
<?php echo htmlspecialchars( phpversion() ); ?>.
</p>
<?php
diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php
index 60f33767..99ab2d8e 100644
--- a/includes/templates/Userlogin.php
+++ b/includes/templates/Userlogin.php
@@ -1,9 +1,15 @@
<?php
/**
- * @defgroup Templates Templates
+ * Html forms for user login and account creation
+ *
* @file
* @ingroup Templates
*/
+
+/**
+ * @defgroup Templates Templates
+ */
+
if( !defined( 'MEDIAWIKI' ) ) die( -1 );
/**
@@ -61,7 +67,7 @@ class UserloginTemplate extends QuickTemplate {
</td>
</tr>
- <?php if( $this->data['usedomain'] ) {
+ <?php if( isset( $this->data['usedomain'] ) && $this->data['usedomain'] ) {
$doms = "";
foreach( $this->data['domainnames'] as $dom ) {
$doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
@@ -77,17 +83,41 @@ class UserloginTemplate extends QuickTemplate {
</td>
</tr>
<?php }
+
+ if( $this->haveData( 'extrafields' ) ) {
+ echo $this->data['extrafields'];
+ }
+
if( $this->data['canremember'] ) { ?>
<tr>
<td></td>
<td class="mw-input">
<?php
- echo Html::input( 'wpRemember', '1', 'checkbox', array(
- 'tabindex' => '4',
- 'id' => 'wpRemember'
- ) + ( $this->data['remember'] ? array( 'checked' ) : array() ) ); ?>
-
- <label for="wpRemember"><?php $this->msg('remembermypassword') ?></label>
+ global $wgCookieExpiration, $wgLang;
+ echo Xml::checkLabel(
+ wfMsgExt( 'remembermypassword', 'parsemag', $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) ) ),
+ 'wpRemember',
+ 'wpRemember',
+ $this->data['remember'],
+ array( 'tabindex' => '8' )
+ )
+ ?>
+ </td>
+ </tr>
+<?php } ?>
+<?php if( $this->data['cansecurelogin'] ) { ?>
+ <tr>
+ <td></td>
+ <td class="mw-input">
+ <?php
+ echo Xml::checkLabel(
+ wfMsg( 'securelogin-stick-https' ),
+ 'wpStickHTTPS',
+ 'wpStickHTTPS',
+ $this->data['stickHTTPS'],
+ array( 'tabindex' => '9' )
+ );
+ ?>
</td>
</tr>
<?php } ?>
@@ -97,13 +127,13 @@ class UserloginTemplate extends QuickTemplate {
<?php
echo Html::input( 'wpLoginAttempt', wfMsg( 'login' ), 'submit', array(
'id' => 'wpLoginAttempt',
- 'tabindex' => '5'
+ 'tabindex' => '9'
) );
if ( $this->data['useemail'] && $this->data['canreset'] ) {
- echo '&nbsp;';
+ echo '&#160;';
echo Html::input( 'wpMailmypassword', wfMsg( 'mailmypassword' ), 'submit', array(
'id' => 'wpMailmypassword',
- 'tabindex' => '6'
+ 'tabindex' => '10'
) );
} ?>
@@ -145,6 +175,8 @@ class UsercreateTemplate extends QuickTemplate {
</div>
<div class="visualClear"></div>
<?php } ?>
+
+<div id="signupstart"><?php $this->msgWiki( 'signupstart' ); ?></div>
<div id="userlogin">
<form name="userlogin2" id="userlogin2" method="post" action="<?php $this->text('action') ?>">
@@ -240,21 +272,36 @@ class UsercreateTemplate extends QuickTemplate {
</div>
</td>
<?php } ?>
+ <?php if( $this->data['usereason'] ) { ?>
+ </tr>
+ <tr>
+ <td class="mw-label"><label for='wpReason'><?php $this->msg('createaccountreason') ?></label></td>
+ <td class="mw-input">
+ <input type='text' class='loginText' name="wpReason" id="wpReason"
+ tabindex="7"
+ value="<?php $this->text('reason') ?>" size='20' />
+ </td>
+ <?php } ?>
</tr>
<?php if( $this->data['canremember'] ) { ?>
<tr>
<td></td>
<td class="mw-input">
- <input type='checkbox' name="wpRemember"
- tabindex="7"
- value="1" id="wpRemember"
- <?php if( $this->data['remember'] ) { ?>checked="checked"<?php } ?>
- /> <label for="wpRemember"><?php $this->msg('remembermypassword') ?></label>
+ <?php
+ global $wgCookieExpiration, $wgLang;
+ echo Xml::checkLabel(
+ wfMsgExt( 'remembermypassword', 'parsemag', $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) ) ),
+ 'wpRemember',
+ 'wpRemember',
+ $this->data['remember'],
+ array( 'tabindex' => '8' )
+ )
+ ?>
</td>
</tr>
<?php }
- $tabIndex = 8;
+ $tabIndex = 9;
if ( isset( $this->data['extraInput'] ) && is_array( $this->data['extraInput'] ) ) {
foreach ( $this->data['extraInput'] as $inputItem ) { ?>
<tr>
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index 5d955b36..546b9db8 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -25,14 +25,40 @@ abstract class UploadBase {
const EMPTY_FILE = 3;
const MIN_LENGTH_PARTNAME = 4;
const ILLEGAL_FILENAME = 5;
- const OVERWRITE_EXISTING_FILE = 7;
+ const OVERWRITE_EXISTING_FILE = 7; # Not used anymore; handled by verifyPermissions()
const FILETYPE_MISSING = 8;
const FILETYPE_BADTYPE = 9;
const VERIFICATION_ERROR = 10;
+
+ # HOOK_ABORTED is the new name of UPLOAD_VERIFICATION_ERROR
const UPLOAD_VERIFICATION_ERROR = 11;
const HOOK_ABORTED = 11;
+ const FILE_TOO_LARGE = 12;
const SESSION_VERSION = 2;
+ const SESSION_KEYNAME = 'wsUploadData';
+
+ static public function getSessionKeyname() {
+ return self::SESSION_KEYNAME;
+ }
+
+ public function getVerificationErrorCode( $error ) {
+ $code_to_status = array(self::EMPTY_FILE => 'empty-file',
+ self::FILE_TOO_LARGE => 'file-too-large',
+ self::FILETYPE_MISSING => 'filetype-missing',
+ self::FILETYPE_BADTYPE => 'filetype-banned',
+ self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
+ self::ILLEGAL_FILENAME => 'illegal-filename',
+ self::OVERWRITE_EXISTING_FILE => 'overwrite',
+ self::VERIFICATION_ERROR => 'verification-error',
+ self::HOOK_ABORTED => 'hookaborted',
+ );
+ if( isset( $code_to_status[$error] ) ) {
+ return $code_to_status[$error];
+ }
+
+ return 'unknown-error';
+ }
/**
* Returns true if uploads are enabled.
@@ -57,8 +83,10 @@ abstract class UploadBase {
* Can be overriden by subclasses.
*/
public static function isAllowed( $user ) {
- if( !$user->isAllowed( 'upload' ) ) {
- return 'upload';
+ foreach ( array( 'upload', 'edit' ) as $permission ) {
+ if ( !$user->isAllowed( $permission ) ) {
+ return $permission;
+ }
}
return true;
}
@@ -143,15 +171,37 @@ abstract class UploadBase {
}
/**
- * Return the file size
+ * Return true if the file is empty
+ * @return bool
*/
public function isEmptyFile() {
return empty( $this->mFileSize );
}
/**
- * @param string $srcPath the source path
- * @returns the real path if it was a virtual URL
+ * Return the file size
+ * @return integer
+ */
+ public function getFileSize() {
+ return $this->mFileSize;
+ }
+
+ /**
+ * Append a file to the Repo file
+ *
+ * @param $srcPath String: path to source file
+ * @param $toAppendPath String: path to the Repo file that will be appended to.
+ * @return Status Status
+ */
+ protected function appendToUploadFile( $srcPath, $toAppendPath ) {
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ $status = $repo->append( $srcPath, $toAppendPath );
+ return $status;
+ }
+
+ /**
+ * @param $srcPath String: the source path
+ * @return the real path if it was a virtual URL
*/
function getRealPath( $srcPath ) {
$repo = RepoGroup::singleton()->getLocalRepo();
@@ -163,7 +213,7 @@ abstract class UploadBase {
/**
* Verify whether the upload is sane.
- * Returns self::OK or else an array with error information
+ * @return mixed self::OK or else an array with error information
*/
public function verifyUpload() {
/**
@@ -174,21 +224,53 @@ abstract class UploadBase {
}
/**
+ * Honor $wgMaxUploadSize
+ */
+ global $wgMaxUploadSize;
+ if( $this->mFileSize > $wgMaxUploadSize ) {
+ return array(
+ 'status' => self::FILE_TOO_LARGE,
+ 'max' => $wgMaxUploadSize,
+ );
+ }
+
+ /**
* Look at the contents of the file; if we can recognize the
* type but it's corrupt or data of the wrong type, we should
* probably not accept it.
*/
$verification = $this->verifyFile();
if( $verification !== true ) {
- if( !is_array( $verification ) ) {
- $verification = array( $verification );
- }
return array(
'status' => self::VERIFICATION_ERROR,
'details' => $verification
);
}
+ /**
+ * Make sure this file can be created
+ */
+ $result = $this->validateName();
+ if( $result !== true ) {
+ return $result;
+ }
+
+ $error = '';
+ if( !wfRunHooks( 'UploadVerification',
+ array( $this->mDestName, $this->mTempPath, &$error ) ) ) {
+ return array( 'status' => self::HOOK_ABORTED, 'error' => $error );
+ }
+
+ return array( 'status' => self::OK );
+ }
+
+ /**
+ * Verify that the name is valid and, if necessary, that we can overwrite
+ *
+ * @return mixed true if valid, otherwise and array with 'status'
+ * and other keys
+ **/
+ protected function validateName() {
$nt = $this->getTitle();
if( is_null( $nt ) ) {
$result = array( 'status' => $this->mTitleError );
@@ -202,41 +284,16 @@ abstract class UploadBase {
}
$this->mDestName = $this->getLocalFile()->getName();
- /**
- * In some cases we may forbid overwriting of existing files.
- */
- $overwrite = $this->checkOverwrite();
- if( $overwrite !== true ) {
- return array(
- 'status' => self::OVERWRITE_EXISTING_FILE,
- 'overwrite' => $overwrite
- );
- }
-
- $error = '';
- if( !wfRunHooks( 'UploadVerification',
- array( $this->mDestName, $this->mTempPath, &$error ) ) ) {
- // This status needs another name...
- return array( 'status' => self::HOOK_ABORTED, 'error' => $error );
- }
-
- return array( 'status' => self::OK );
+ return true;
}
/**
- * Verifies that it's ok to include the uploaded file
+ * Verify the mime type
*
- * @return mixed true of the file is verified, a string or array otherwise.
+ * @param $mime string representing the mime
+ * @return mixed true if the file is verified, an array otherwise
*/
- protected function verifyFile() {
- $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
- $this->checkMacBinary();
-
- # magically determine mime type
- $magic = MimeMagic::singleton();
- $mime = $magic->guessMimeType( $this->mTempPath, false );
-
- # check mime type, if desired
+ protected function verifyMimeType( $mime ) {
global $wgVerifyMimeType;
if ( $wgVerifyMimeType ) {
wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n");
@@ -253,6 +310,8 @@ abstract class UploadBase {
$fp = fopen( $this->mTempPath, 'rb' );
$chunk = fread( $fp, 256 );
fclose( $fp );
+
+ $magic = MimeMagic::singleton();
$extMime = $magic->guessTypesForExtension( $this->mFinalExtension );
$ieTypes = $magic->getIEMimeTypes( $this->mTempPath, $chunk, $extMime );
foreach ( $ieTypes as $ieType ) {
@@ -262,13 +321,36 @@ abstract class UploadBase {
}
}
+ return true;
+ }
+
+ /**
+ * Verifies that it's ok to include the uploaded file
+ *
+ * @return mixed true of the file is verified, array otherwise.
+ */
+ protected function verifyFile() {
+ # get the title, even though we are doing nothing with it, because
+ # we need to populate mFinalExtension
+ $this->getTitle();
+
+ $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
+ $this->checkMacBinary();
+
+ # check mime type, if desired
+ $mime = $this->mFileProps[ 'file-mime' ];
+ $status = $this->verifyMimeType( $mime );
+ if ( $status !== true ) {
+ return $status;
+ }
+
# check for htmlish code and javascript
if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
- return 'uploadscripted';
+ return array( 'uploadscripted' );
}
if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
- if( self::detectScriptInSvg( $this->mTempPath ) ) {
- return 'uploadscripted';
+ if( $this->detectScriptInSvg( $this->mTempPath ) ) {
+ return array( 'uploadscripted' );
}
}
@@ -279,14 +361,33 @@ abstract class UploadBase {
if ( $virus ) {
return array( 'uploadvirus', $virus );
}
+
+ $handler = MediaHandler::getHandler( $mime );
+ if ( $handler ) {
+ $handlerStatus = $handler->verifyUpload( $this->mTempPath );
+ if ( !$handlerStatus->isOK() ) {
+ $errors = $handlerStatus->getErrorsArray();
+ return reset( $errors );
+ }
+ }
+
+ wfRunHooks( 'UploadVerifyFile', array( $this, $mime, &$status ) );
+ if ( $status !== true ) {
+ return $status;
+ }
+
wfDebug( __METHOD__ . ": all clear; passing.\n" );
return true;
}
/**
- * Check whether the user can edit, upload and create the image.
+ * Check whether the user can edit, upload and create the image. This
+ * checks only against the current title; if it returns errors, it may
+ * very well be that another title will not give errors. Therefore
+ * isAllowed() should be called as well for generic is-user-blocked or
+ * can-user-upload checking.
*
- * @param User $user the user to verify the permissions against
+ * @param $user the User object to verify the permissions against
* @return mixed An array as returned by getUserPermissionsErrors or true
* in case the user has proper permissions.
*/
@@ -301,19 +402,29 @@ abstract class UploadBase {
}
$permErrors = $nt->getUserPermissionsErrors( 'edit', $user );
$permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user );
- $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $user ) );
+ if ( !$nt->exists() ) {
+ $permErrorsCreate = $nt->getUserPermissionsErrors( 'createpage', $user );
+ } else {
+ $permErrorsCreate = array();
+ }
if( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
$permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
$permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
return $permErrors;
}
+
+ $overwriteError = $this->checkOverwrite( $user );
+ if ( $overwriteError !== true ) {
+ return array( $overwriteError );
+ }
+
return true;
}
/**
* Check for non fatal problems with the file
*
- * @return array Array of warnings
+ * @return Array of warnings
*/
public function checkWarnings() {
$warnings = array();
@@ -321,7 +432,6 @@ abstract class UploadBase {
$localFile = $this->getLocalFile();
$filename = $localFile->getName();
$n = strrpos( $filename, '.' );
- $partname = $n ? substr( $filename, 0, $n ) : $filename;
/**
* Check whether the resulting filename is different from the desired one,
@@ -386,15 +496,21 @@ abstract class UploadBase {
* @return mixed Status indicating the whether the upload succeeded.
*/
public function performUpload( $comment, $pageText, $watch, $user ) {
- wfDebug( "\n\n\performUpload: sum:" . $comment . ' c: ' . $pageText . ' w:' . $watch );
- $status = $this->getLocalFile()->upload( $this->mTempPath, $comment, $pageText,
- File::DELETE_SOURCE, $this->mFileProps, false, $user );
-
- if( $status->isGood() && $watch ) {
- $user->addWatch( $this->getLocalFile()->getTitle() );
- }
+ $status = $this->getLocalFile()->upload(
+ $this->mTempPath,
+ $comment,
+ $pageText,
+ File::DELETE_SOURCE,
+ $this->mFileProps,
+ false,
+ $user
+ );
if( $status->isGood() ) {
+ if ( $watch ) {
+ $user->addWatch( $this->getLocalFile()->getTitle() );
+ }
+
wfRunHooks( 'UploadComplete', array( &$this ) );
}
@@ -417,9 +533,7 @@ abstract class UploadBase {
* filter out illegal characters, and try to make a legible name
* out of it. We'll strip some silently that Title would die on.
*/
- $basename = $this->mDesiredDestName;
-
- $this->mFilteredName = wfStripIllegalFilenameChars( $basename );
+ $this->mFilteredName = wfStripIllegalFilenameChars( $this->mDesiredDestName );
/* Normalize to title form before we do any further processing */
$nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
if( is_null( $nt ) ) {
@@ -466,11 +580,6 @@ abstract class UploadBase {
return $this->mTitle = null;
}
- $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
- if( is_null( $nt ) ) {
- $this->mTitleError = self::ILLEGAL_FILENAME;
- return $this->mTitle = null;
- }
return $this->mTitle = $nt;
}
@@ -486,15 +595,18 @@ abstract class UploadBase {
}
/**
+ * NOTE: Probably should be deprecated in favor of UploadStash, but this is sometimes
+ * called outside that context.
+ *
* Stash a file in a temporary directory for later processing
* after the user has confirmed it.
*
* If the user doesn't explicitly cancel or accept, these files
* can accumulate in the temp directory.
*
- * @param string $saveName - the destination filename
- * @param string $tempSrc - the source temporary file to save
- * @return string - full path the stashed file, or false on failure
+ * @param $saveName String: the destination filename
+ * @param $tempSrc String: the source temporary file to save
+ * @return String: full path the stashed file, or false on failure
*/
protected function saveTempUploadedFile( $saveName, $tempSrc ) {
$repo = RepoGroup::singleton()->getLocalRepo();
@@ -503,39 +615,35 @@ abstract class UploadBase {
}
/**
- * Stash a file in a temporary directory for later processing,
- * and save the necessary descriptive info into the session.
- * Returns a key value which will be passed through a form
- * to pick up the path info on a later invocation.
+ * If the user does not supply all necessary information in the first upload form submission (either by accident or
+ * by design) then we may want to stash the file temporarily, get more information, and publish the file later.
+ *
+ * This method will stash a file in a temporary directory for later processing, and save the necessary descriptive info
+ * into the user's session.
+ * This method returns the file object, which also has a 'sessionKey' property which can be passed through a form or
+ * API request to find this stashed file again.
*
- * @return int Session key
+ * @param $key String: (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
+ * @return File: stashed file
*/
- public function stashSession() {
- $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
- if( !$status->isOK() ) {
- # Couldn't save the file.
- return false;
- }
- if( !isset( $_SESSION ) ) {
- session_start(); // start up the session (might have been previously closed to prevent php session locking)
- }
- $key = $this->getSessionKey();
- $_SESSION['wsUploadData'][$key] = array(
- 'mTempPath' => $status->value,
- 'mFileSize' => $this->mFileSize,
- 'mFileProps' => $this->mFileProps,
- 'version' => self::SESSION_VERSION,
+ public function stashSessionFile( $key = null ) {
+ $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
+ $data = array(
+ 'mFileProps' => $this->mFileProps
);
- return $key;
+ $file = $stash->stashFile( $this->mTempPath, $data, $key );
+ $this->mLocalFile = $file;
+ return $file;
}
/**
- * Generate a random session key from stash in cases where we want to start an upload without much information
+ * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashSessionFile().
+ *
+ * @param $key String: (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
+ * @return String: session key
*/
- protected function getSessionKey() {
- $key = mt_rand( 0, 0x7fffffff );
- $_SESSION['wsUploadData'][$key] = array();
- return $key;
+ public function stashSession( $key = null ) {
+ return $this->stashSessionFile( $key )->getSessionKey();
}
/**
@@ -571,9 +679,9 @@ abstract class UploadBase {
* Perform case-insensitive match against a list of file extensions.
* Returns true if the extension is in the list.
*
- * @param string $ext
- * @param array $list
- * @return bool
+ * @param $ext String
+ * @param $list Array
+ * @return Boolean
*/
public static function checkFileExtension( $ext, $list ) {
return in_array( strtolower( $ext ), $list );
@@ -583,9 +691,9 @@ abstract class UploadBase {
* Perform case-insensitive match against a list of file extensions.
* Returns true if any of the extensions are in the list.
*
- * @param array $ext
- * @param array $list
- * @return bool
+ * @param $ext Array
+ * @param $list Array
+ * @return Boolean
*/
public static function checkFileExtensionList( $ext, $list ) {
foreach( $ext as $e ) {
@@ -599,9 +707,9 @@ abstract class UploadBase {
/**
* Checks if the mime type of the uploaded file matches the file extension.
*
- * @param string $mime the mime type of the uploaded file
- * @param string $extension The filename extension that the file is to be served with
- * @return bool
+ * @param $mime String: the mime type of the uploaded file
+ * @param $extension String: the filename extension that the file is to be served with
+ * @return Boolean
*/
public static function verifyExtension( $mime, $extension ) {
$magic = MimeMagic::singleton();
@@ -640,10 +748,10 @@ abstract class UploadBase {
* potentially harmful. The present implementation will produce false
* positives in some situations.
*
- * @param string $file Pathname to the temporary upload file
- * @param string $mime The mime type of the file
- * @param string $extension The extension of the file
- * @return bool true if the file contains something looking like embedded scripts
+ * @param $file String: pathname to the temporary upload file
+ * @param $mime String: the mime type of the file
+ * @param $extension String: the extension of the file
+ * @return Boolean: true if the file contains something looking like embedded scripts
*/
public static function detectScript( $file, $mime, $extension ) {
global $wgAllowTitlesInSVG;
@@ -790,7 +898,7 @@ abstract class UploadBase {
* This relies on the $wgAntivirus and $wgAntivirusSetup variables.
* $wgAntivirusRequired may be used to deny upload if the scan fails.
*
- * @param string $file Pathname to the temporary upload file
+ * @param $file String: pathname to the temporary upload file
* @return mixed false if not virus is found, NULL if the scan fails or is disabled,
* or a string containing feedback from the virus scanner if a virus was found.
* If textual feedback is missing but a virus was found, this function returns true.
@@ -805,7 +913,8 @@ abstract class UploadBase {
if ( !$wgAntivirusSetup[$wgAntivirus] ) {
wfDebug( __METHOD__ . ": unknown virus scanner: $wgAntivirus\n" );
- $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1</div>", array( 'virus-badscanner', $wgAntivirus ) );
+ $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>",
+ array( 'virus-badscanner', $wgAntivirus ) );
return wfMsg( 'virus-unknownscanner' ) . " $wgAntivirus";
}
@@ -907,15 +1016,14 @@ abstract class UploadBase {
* Check if there's an overwrite conflict and, if so, if restrictions
* forbid this user from performing the upload.
*
- * @return mixed true on success, error string on failure
+ * @return mixed true on success, array on failure
*/
- private function checkOverwrite() {
- global $wgUser;
+ private function checkOverwrite( $user ) {
// First check whether the local file can be overwritten
$file = $this->getLocalFile();
if( $file->exists() ) {
- if( !self::userCanReUpload( $wgUser, $file ) ) {
- return 'fileexists-forbidden';
+ if( !self::userCanReUpload( $user, $file ) ) {
+ return array( 'fileexists-forbidden', $file->getName() );
} else {
return true;
}
@@ -925,8 +1033,8 @@ abstract class UploadBase {
* wfFindFile finds a file, it exists in a shared repository.
*/
$file = wfFindFile( $this->getTitle() );
- if ( $file && !$wgUser->isAllowed( 'reupload-shared' ) ) {
- return 'fileexists-shared-forbidden';
+ if ( $file && !$user->isAllowed( 'reupload-shared' ) ) {
+ return array( 'fileexists-shared-forbidden', $file->getName() );
}
return true;
@@ -935,9 +1043,9 @@ abstract class UploadBase {
/**
* Check if a user is the last uploader
*
- * @param User $user
- * @param string $img, image name
- * @return bool
+ * @param $user User object
+ * @param $img String: image name
+ * @return Boolean
*/
public static function userCanReUpload( User $user, $img ) {
if( $user->isAllowed( 'reupload' ) ) {
@@ -964,7 +1072,7 @@ abstract class UploadBase {
* - File exists with normalized extension
* - The file looks like a thumbnail and the original exists
*
- * @param File $file The file to check
+ * @param $file The File object to check
* @return mixed False if the file does not exists, else an array
*/
public static function getExistsWarning( $file ) {
@@ -1082,10 +1190,34 @@ abstract class UploadBase {
return $blacklist;
}
+ /**
+ * Gets image info about the file just uploaded.
+ *
+ * Also has the effect of setting metadata to be an 'indexed tag name' in returned API result if
+ * 'metadata' was requested. Oddly, we have to pass the "result" object down just so it can do that
+ * with the appropriate format, presumably.
+ *
+ * @param $result ApiResult:
+ * @return Array: image info
+ */
public function getImageInfo( $result ) {
$file = $this->getLocalFile();
- $imParam = ApiQueryImageInfo::getPropertyNames();
- return ApiQueryImageInfo::getInfo( $file, array_flip( $imParam ), $result );
+ // TODO This cries out for refactoring. We really want to say $file->getAllInfo(); here.
+ // Perhaps "info" methods should be moved into files, and the API should just wrap them in queries.
+ if ( $file instanceof UploadStashFile ) {
+ $imParam = ApiQueryStashImageInfo::getPropertyNames();
+ $info = ApiQueryStashImageInfo::getInfo( $file, array_flip( $imParam ), $result );
+ } else {
+ $imParam = ApiQueryImageInfo::getPropertyNames();
+ $info = ApiQueryImageInfo::getInfo( $file, array_flip( $imParam ), $result );
+ }
+ return $info;
}
+
+ public function convertVerifyErrorToStatus( $error ) {
+ $code = $error['status'];
+ unset( $code['status'] );
+ return Status::newFatal( $this->getVerificationErrorCode( $code ), $error );
+ }
}
diff --git a/includes/upload/UploadFromFile.php b/includes/upload/UploadFromFile.php
index 73581a61..e67ec191 100644
--- a/includes/upload/UploadFromFile.php
+++ b/includes/upload/UploadFromFile.php
@@ -1,32 +1,65 @@
<?php
/**
+ * Implements regular file uploads
+ *
* @file
* @ingroup upload
- *
* @author Bryan Tong Minh
- *
- * Implements regular file uploads
*/
-class UploadFromFile extends UploadBase {
+class UploadFromFile extends UploadBase {
+ protected $mUpload = null;
function initializeFromRequest( &$request ) {
+ $upload = $request->getUpload( 'wpUploadFile' );
$desiredDestName = $request->getText( 'wpDestFile' );
if( !$desiredDestName )
- $desiredDestName = $request->getFileName( 'wpUploadFile' );
- return $this->initializePathInfo(
- $desiredDestName,
- $request->getFileTempName( 'wpUploadFile' ),
- $request->getFileSize( 'wpUploadFile' )
- );
+ $desiredDestName = $upload->getName();
+
+ return $this->initialize( $desiredDestName, $upload );
}
+
/**
- * Entry point for upload from file.
+ * Initialize from a filename and a WebRequestUpload
*/
- function initialize( $name, $tempPath, $fileSize ) {
- return $this->initializePathInfo( $name, $tempPath, $fileSize );
+ function initialize( $name, $webRequestUpload ) {
+ $this->mUpload = $webRequestUpload;
+ return $this->initializePathInfo( $name,
+ $this->mUpload->getTempName(), $this->mUpload->getSize() );
}
static function isValidRequest( $request ) {
- return (bool)$request->getFileTempName( 'wpUploadFile' );
+ # Allow all requests, even if no file is present, so that an error
+ # because a post_max_size or upload_max_filesize overflow
+ return true;
+ }
+
+ public function verifyUpload() {
+ # Check for a post_max_size or upload_max_size overflow, so that a
+ # proper error can be shown to the user
+ if ( is_null( $this->mTempPath ) || $this->isEmptyFile() ) {
+ if ( $this->mUpload->isIniSizeOverflow() ) {
+ global $wgMaxUploadSize;
+ return array(
+ 'status' => UploadBase::FILE_TOO_LARGE,
+ 'max' => min(
+ $wgMaxUploadSize,
+ wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
+ wfShorthandToInteger( ini_get( 'post_max_size' ) )
+ ),
+ );
+ }
+ }
+
+ return parent::verifyUpload();
+ }
+
+ /**
+ * Get the path to the file underlying the upload
+ * @return String path to file
+ */
+ public function getFileTempname() {
+ return $this->mUpload->getTempname();
}
+
+
}
diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php
index 17e922b0..156781e9 100644
--- a/includes/upload/UploadFromStash.php
+++ b/includes/upload/UploadFromStash.php
@@ -1,10 +1,9 @@
<?php
/**
- * @file
- * @ingroup upload
- *
* Implements uploading from previously stored file.
*
+ * @file
+ * @ingroup upload
* @author Bryan Tong Minh
*/
@@ -14,13 +13,13 @@ class UploadFromStash extends UploadBase {
is_array( $sessionData ) &&
isset( $sessionData[$key] ) &&
isset( $sessionData[$key]['version'] ) &&
- $sessionData[$key]['version'] == self::SESSION_VERSION;
+ $sessionData[$key]['version'] == UploadBase::SESSION_VERSION;
}
public static function isValidRequest( $request ) {
- $sessionData = $request->getSessionData( 'wsUploadData' );
+ $sessionData = $request->getSessionData( UploadBase::SESSION_KEYNAME );
return self::isValidSessionKey(
- $request->getInt( 'wpSessionKey' ),
+ $request->getText( 'wpSessionKey' ),
$sessionData
);
}
@@ -45,8 +44,8 @@ class UploadFromStash extends UploadBase {
}
public function initializeFromRequest( &$request ) {
- $sessionKey = $request->getInt( 'wpSessionKey' );
- $sessionData = $request->getSessionData('wsUploadData');
+ $sessionKey = $request->getText( 'wpSessionKey' );
+ $sessionData = $request->getSessionData( UploadBase::SESSION_KEYNAME );
$desiredDestName = $request->getText( 'wpDestFile' );
if( !$desiredDestName )
@@ -65,7 +64,7 @@ class UploadFromStash extends UploadBase {
/**
* There is no need to stash the image twice
*/
- public function stashSession() {
+ public function stashSession( $key = null ) {
if ( !empty( $this->mSessionKey ) )
return $this->mSessionKey;
return parent::stashSession();
diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php
index 763dae38..c28fd7da 100644
--- a/includes/upload/UploadFromUrl.php
+++ b/includes/upload/UploadFromUrl.php
@@ -1,28 +1,30 @@
<?php
/**
- * @file
- * @ingroup upload
- *
* Implements uploading from a HTTP resource.
*
+ * @file
+ * @ingroup upload
* @author Bryan Tong Minh
* @author Michael Dale
*/
+
class UploadFromUrl extends UploadBase {
- protected $mTempDownloadPath;
+ protected $mAsync, $mUrl;
+ protected $mIgnoreWarnings = true;
/**
* Checks if the user is allowed to use the upload-by-URL feature. If the
* user is allowed, pass on permissions checking to the parent.
*/
public static function isAllowed( $user ) {
- if( !$user->isAllowed( 'upload_by_url' ) )
+ if ( !$user->isAllowed( 'upload_by_url' ) )
return 'upload_by_url';
return parent::isAllowed( $user );
}
/**
* Checks if the upload from URL feature is enabled
+ * @return bool
*/
public static function isEnabled() {
global $wgAllowCopyUploads;
@@ -31,14 +33,22 @@ class UploadFromUrl extends UploadBase {
/**
* Entry point for API upload
+ *
+ * @param $name string
+ * @param $url string
+ * @param $async mixed Whether the download should be performed
+ * asynchronous. False for synchronous, async or async-leavemessage for
+ * asynchronous download.
*/
- public function initialize( $name, $url, $na, $nb = false ) {
- global $wgTmpDirectory;
+ public function initialize( $name, $url, $async = false ) {
+ global $wgAllowAsyncCopyUploads;
- $localFile = tempnam( $wgTmpDirectory, 'WEBUPLOAD' );
- $this->initializePathInfo( $name, $localFile, 0, true );
+ $this->mUrl = $url;
+ $this->mAsync = $wgAllowAsyncCopyUploads ? $async : false;
- $this->mUrl = trim( $url );
+ $tempPath = $this->mAsync ? null : $this->makeTemporaryFile();
+ # File size and removeTempFile will be filled in later
+ $this->initializePathInfo( $name, $tempPath, 0, false );
}
/**
@@ -47,7 +57,7 @@ class UploadFromUrl extends UploadBase {
*/
public function initializeFromRequest( &$request ) {
$desiredDestName = $request->getText( 'wpDestFile' );
- if( !$desiredDestName )
+ if ( !$desiredDestName )
$desiredDestName = $request->getText( 'wpUploadFileURL' );
return $this->initialize(
$desiredDestName,
@@ -59,79 +69,162 @@ class UploadFromUrl extends UploadBase {
/**
* @param $request Object: WebRequest object
*/
- public static function isValidRequest( $request ){
- if( !$request->getVal( 'wpUploadFileURL' ) )
- return false;
- // check that is a valid url:
- return self::isValidUrl( $request->getVal( 'wpUploadFileURL' ) );
+ public static function isValidRequest( $request ) {
+ global $wgUser;
+
+ $url = $request->getVal( 'wpUploadFileURL' );
+ return !empty( $url )
+ && Http::isValidURI( $url )
+ && $wgUser->isAllowed( 'upload_by_url' );
}
- public static function isValidUrl( $url ) {
- // Only allow HTTP or FTP for now
- return (bool)preg_match( '!^(http://|ftp://)!', $url );
+
+ public function fetchFile() {
+ if ( !Http::isValidURI( $this->mUrl ) ) {
+ return Status::newFatal( 'http-invalid-url' );
+ }
+
+ if ( !$this->mAsync ) {
+ return $this->reallyFetchFile();
+ }
+ return Status::newGood();
+ }
+ /**
+ * Create a new temporary file in the URL subdirectory of wfTempDir().
+ *
+ * @return string Path to the file
+ */
+ protected function makeTemporaryFile() {
+ return tempnam( wfTempDir(), 'URL' );
}
/**
- * Do the real fetching stuff
+ * Callback: save a chunk of the result of a HTTP request to the temporary file
+ *
+ * @param $req mixed
+ * @param $buffer string
+ * @return int number of bytes handled
*/
- function fetchFile() {
- if( !self::isValidUrl( $this->mUrl ) ) {
- return Status::newFatal( 'upload-proto-error' );
+ public function saveTempFileChunk( $req, $buffer ) {
+ $nbytes = fwrite( $this->mTmpHandle, $buffer );
+
+ if ( $nbytes == strlen( $buffer ) ) {
+ $this->mFileSize += $nbytes;
+ } else {
+ // Well... that's not good!
+ fclose( $this->mTmpHandle );
+ $this->mTmpHandle = false;
}
- $res = $this->curlCopy();
- if( $res !== true ) {
- return Status::newFatal( $res );
- }
- return Status::newGood();
+
+ return $nbytes;
}
/**
- * Safe copy from URL
- * Returns true if there was an error, false otherwise
+ * Download the file, save it to the temporary file and update the file
+ * size and set $mRemoveTempFile to true.
*/
- private function curlCopy() {
- global $wgOut;
-
- # Open temporary file
- $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" );
- if( $this->mCurlDestHandle === false ) {
- # Could not open temporary file to write in
- return 'upload-file-error';
+ protected function reallyFetchFile() {
+ if ( $this->mTempPath === false ) {
+ return Status::newFatal( 'tmp-create-error' );
}
- $ch = curl_init();
- curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug
- curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout
- curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed
- curl_setopt( $ch, CURLOPT_URL, $this->mUrl);
- curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) );
- curl_exec( $ch );
- $error = curl_errno( $ch );
- curl_close( $ch );
+ // Note the temporary file should already be created by makeTemporaryFile()
+ $this->mTmpHandle = fopen( $this->mTempPath, 'wb' );
+ if ( !$this->mTmpHandle ) {
+ return Status::newFatal( 'tmp-create-error' );
+ }
+
+ $this->mRemoveTempFile = true;
+ $this->mFileSize = 0;
- fclose( $this->mCurlDestHandle );
- unset( $this->mCurlDestHandle );
+ $req = MWHttpRequest::factory( $this->mUrl );
+ $req->setCallback( array( $this, 'saveTempFileChunk' ) );
+ $status = $req->execute();
- if( $error )
- return "upload-curl-error$errornum";
+ if ( $this->mTmpHandle ) {
+ // File got written ok...
+ fclose( $this->mTmpHandle );
+ $this->mTmpHandle = null;
+ } else {
+ // We encountered a write error during the download...
+ return Status::newFatal( 'tmp-write-error' );
+ }
+
+ if ( !$status->isOk() ) {
+ return $status;
+ }
- return true;
+ return $status;
}
/**
- * Callback function for CURL-based web transfer
- * Write data to file unless we've passed the length limit;
- * if so, abort immediately.
- * @access private
+ * Wrapper around the parent function in order to defer verifying the
+ * upload until the file really has been fetched.
*/
- function uploadCurlCallback( $ch, $data ) {
- global $wgMaxUploadSize;
- $length = strlen( $data );
- $this->mFileSize += $length;
- if( $this->mFileSize > $wgMaxUploadSize ) {
- return 0;
+ public function verifyUpload() {
+ if ( $this->mAsync ) {
+ return array( 'status' => UploadBase::OK );
}
- fwrite( $this->mCurlDestHandle, $data );
- return $length;
+ return parent::verifyUpload();
}
+
+ /**
+ * Wrapper around the parent function in order to defer checking warnings
+ * until the file really has been fetched.
+ */
+ public function checkWarnings() {
+ if ( $this->mAsync ) {
+ $this->mIgnoreWarnings = false;
+ return array();
+ }
+ return parent::checkWarnings();
+ }
+
+ /**
+ * Wrapper around the parent function in order to defer checking protection
+ * until we are sure that the file can actually be uploaded
+ */
+ public function verifyPermissions( $user ) {
+ if ( $this->mAsync ) {
+ return true;
+ }
+ return parent::verifyPermissions( $user );
+ }
+
+ /**
+ * Wrapper around the parent function in order to defer uploading to the
+ * job queue for asynchronous uploads
+ */
+ public function performUpload( $comment, $pageText, $watch, $user ) {
+ if ( $this->mAsync ) {
+ $sessionKey = $this->insertJob( $comment, $pageText, $watch, $user );
+
+ $status = new Status;
+ $status->error( 'async', $sessionKey );
+ return $status;
+ }
+
+ return parent::performUpload( $comment, $pageText, $watch, $user );
+ }
+
+
+ protected function insertJob( $comment, $pageText, $watch, $user ) {
+ $sessionKey = $this->stashSession();
+ $job = new UploadFromUrlJob( $this->getTitle(), array(
+ 'url' => $this->mUrl,
+ 'comment' => $comment,
+ 'pageText' => $pageText,
+ 'watch' => $watch,
+ 'userName' => $user->getName(),
+ 'leaveMessage' => $this->mAsync == 'async-leavemessage',
+ 'ignoreWarnings' => $this->mIgnoreWarnings,
+ 'sessionId' => session_id(),
+ 'sessionKey' => $sessionKey,
+ ) );
+ $job->initializeSessionData();
+ $job->insert();
+ return $sessionKey;
+ }
+
+
}
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
new file mode 100644
index 00000000..1765925d
--- /dev/null
+++ b/includes/upload/UploadStash.php
@@ -0,0 +1,397 @@
+<?php
+/**
+ * UploadStash is intended to accomplish a few things:
+ * - enable applications to temporarily stash files without publishing them to the wiki.
+ * - Several parts of MediaWiki do this in similar ways: UploadBase, UploadWizard, and FirefoggChunkedExtension
+ * And there are several that reimplement stashing from scratch, in idiosyncratic ways. The idea is to unify them all here.
+ * Mostly all of them are the same except for storing some custom fields, which we subsume into the data array.
+ * - enable applications to find said files later, as long as the session or temp files haven't been purged.
+ * - enable the uploading user (and *ONLY* the uploading user) to access said files, and thumbnails of said files, via a URL.
+ * We accomplish this by making the session serve as a URL->file mapping, on the assumption that nobody else can access
+ * the session, even the uploading user. See SpecialUploadStash, which implements a web interface to some files stored this way.
+ *
+ */
+class UploadStash {
+
+ // Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg)
+ const KEY_FORMAT_REGEX = '/^[\w-]+\.\w*$/';
+
+ // repository that this uses to store temp files
+ // public because we sometimes need to get a LocalFile within the same repo.
+ public $repo;
+
+ // array of initialized objects obtained from session (lazily initialized upon getFile())
+ private $files = array();
+
+ // TODO: Once UploadBase starts using this, switch to use these constants rather than UploadBase::SESSION*
+ // const SESSION_VERSION = 2;
+ // const SESSION_KEYNAME = 'wsUploadData';
+
+ /**
+ * Represents the session which contains temporarily stored files.
+ * Designed to be compatible with the session stashing code in UploadBase (should replace it eventually)
+ *
+ * @param $repo FileRepo: optional -- repo in which to store files. Will choose LocalRepo if not supplied.
+ */
+ public function __construct( $repo ) {
+
+ // this might change based on wiki's configuration.
+ $this->repo = $repo;
+
+ if ( ! isset( $_SESSION ) ) {
+ throw new UploadStashNotAvailableException( 'no session variable' );
+ }
+
+ if ( !isset( $_SESSION[UploadBase::SESSION_KEYNAME] ) ) {
+ $_SESSION[UploadBase::SESSION_KEYNAME] = array();
+ }
+
+ }
+
+ /**
+ * Get a file and its metadata from the stash.
+ * May throw exception if session data cannot be parsed due to schema change, or key not found.
+ *
+ * @param $key Integer: key
+ * @throws UploadStashFileNotFoundException
+ * @throws UploadStashBadVersionException
+ * @return UploadStashFile
+ */
+ public function getFile( $key ) {
+ if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
+ throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
+ }
+
+ if ( !isset( $this->files[$key] ) ) {
+ if ( !isset( $_SESSION[UploadBase::SESSION_KEYNAME][$key] ) ) {
+ throw new UploadStashFileNotFoundException( "key '$key' not found in stash" );
+ }
+
+ $data = $_SESSION[UploadBase::SESSION_KEYNAME][$key];
+ // guards against PHP class changing while session data doesn't
+ if ($data['version'] !== UploadBase::SESSION_VERSION ) {
+ throw new UploadStashBadVersionException( $data['version'] . " does not match current version " . UploadBase::SESSION_VERSION );
+ }
+
+ // separate the stashData into the path, and then the rest of the data
+ $path = $data['mTempPath'];
+ unset( $data['mTempPath'] );
+
+ $file = new UploadStashFile( $this, $this->repo, $path, $key, $data );
+ if ( $file->getSize === 0 ) {
+ throw new UploadStashZeroLengthFileException( "File is zero length" );
+ }
+ $this->files[$key] = $file;
+
+ }
+ return $this->files[$key];
+ }
+
+ /**
+ * Stash a file in a temp directory and record that we did this in the session, along with other metadata.
+ * We store data in a flat key-val namespace because that's how UploadBase did it. This also means we have to
+ * ensure that the key-val pairs in $data do not overwrite other required fields.
+ *
+ * @param $path String: path to file you want stashed
+ * @param $data Array: optional, other data you want associated with the file. Do not use 'mTempPath', 'mFileProps', 'mFileSize', or 'version' as keys here
+ * @param $key String: optional, unique key for this file in this session. Used for directory hashing when storing, otherwise not important
+ * @throws UploadStashBadPathException
+ * @throws UploadStashFileException
+ * @return UploadStashFile: file, or null on failure
+ */
+ public function stashFile( $path, $data = array(), $key = null ) {
+ if ( ! file_exists( $path ) ) {
+ wfDebug( "UploadStash: tried to stash file at '$path', but it doesn't exist\n" );
+ throw new UploadStashBadPathException( "path doesn't exist" );
+ }
+ $fileProps = File::getPropsFromPath( $path );
+
+ // we will be initializing from some tmpnam files that don't have extensions.
+ // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this.
+ $extension = self::getExtensionForPath( $path );
+ if ( ! preg_match( "/\\.\\Q$extension\\E$/", $path ) ) {
+ $pathWithGoodExtension = "$path.$extension";
+ if ( ! rename( $path, $pathWithGoodExtension ) ) {
+ throw new UploadStashFileException( "couldn't rename $path to have a better extension at $pathWithGoodExtension" );
+ }
+ $path = $pathWithGoodExtension;
+ }
+
+ // If no key was supplied, use content hash. Also has the nice property of collapsing multiple identical files
+ // uploaded this session, which could happen if uploads had failed.
+ if ( is_null( $key ) ) {
+ $key = $fileProps['sha1'] . "." . $extension;
+ }
+
+ if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
+ throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
+ }
+
+
+ // if not already in a temporary area, put it there
+ $status = $this->repo->storeTemp( basename( $path ), $path );
+
+ if( ! $status->isOK() ) {
+ // It is a convention in MediaWiki to only return one error per API exception, even if multiple errors
+ // are available. We use reset() to pick the "first" thing that was wrong, preferring errors to warnings.
+ // This is a bit lame, as we may have more info in the $status and we're throwing it away, but to fix it means
+ // redesigning API errors significantly.
+ // $status->value just contains the virtual URL (if anything) which is probably useless to the caller
+ $error = reset( $status->getErrorsArray() );
+ if ( ! count( $error ) ) {
+ $error = reset( $status->getWarningsArray() );
+ if ( ! count( $error ) ) {
+ $error = array( 'unknown', 'no error recorded' );
+ }
+ }
+ throw new UploadStashFileException( "error storing file in '$path': " . implode( '; ', $error ) );
+ }
+ $stashPath = $status->value;
+
+ // required info we always store. Must trump any other application info in $data
+ // 'mTempPath', 'mFileSize', and 'mFileProps' are arbitrary names
+ // chosen for compatibility with UploadBase's way of doing this.
+ $requiredData = array(
+ 'mTempPath' => $stashPath,
+ 'mFileSize' => $fileProps['size'],
+ 'mFileProps' => $fileProps,
+ 'version' => UploadBase::SESSION_VERSION
+ );
+
+ // now, merge required info and extra data into the session. (The extra data changes from application to application.
+ // UploadWizard wants different things than say FirefoggChunkedUpload.)
+ wfDebug( __METHOD__ . " storing under $key\n" );
+ $_SESSION[UploadBase::SESSION_KEYNAME][$key] = array_merge( $data, $requiredData );
+
+ return $this->getFile( $key );
+ }
+
+ /**
+ * Find or guess extension -- ensuring that our extension matches our mime type.
+ * Since these files are constructed from php tempnames they may not start off
+ * with an extension.
+ * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming
+ * uploads versus the desired filename. Maybe we can get that passed to us...
+ */
+ public static function getExtensionForPath( $path ) {
+ // Does this have an extension?
+ $n = strrpos( $path, '.' );
+ $extension = null;
+ if ( $n !== false ) {
+ $extension = $n ? substr( $path, $n + 1 ) : '';
+ } else {
+ // If not, assume that it should be related to the mime type of the original file.
+ $magic = MimeMagic::singleton();
+ $mimeType = $magic->guessMimeType( $path );
+ $extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) );
+ if ( count( $extensions ) ) {
+ $extension = $extensions[0];
+ }
+ }
+
+ if ( is_null( $extension ) ) {
+ throw new UploadStashFileException( "extension is null" );
+ }
+
+ return File::normalizeExtension( $extension );
+ }
+
+}
+
+class UploadStashFile extends UnregisteredLocalFile {
+ private $sessionStash;
+ private $sessionKey;
+ private $sessionData;
+ private $urlName;
+
+ /**
+ * A LocalFile wrapper around a file that has been temporarily stashed, so we can do things like create thumbnails for it
+ * Arguably UnregisteredLocalFile should be handling its own file repo but that class is a bit retarded currently
+ *
+ * @param $stash UploadStash: useful for obtaining config, stashing transformed files
+ * @param $repo FileRepo: repository where we should find the path
+ * @param $path String: path to file
+ * @param $key String: key to store the path and any stashed data under
+ * @param $data String: any other data we want stored with this file
+ * @throws UploadStashBadPathException
+ * @throws UploadStashFileNotFoundException
+ */
+ public function __construct( $stash, $repo, $path, $key, $data ) {
+ $this->sessionStash = $stash;
+ $this->sessionKey = $key;
+ $this->sessionData = $data;
+
+ // resolve mwrepo:// urls
+ if ( $repo->isVirtualUrl( $path ) ) {
+ $path = $repo->resolveVirtualUrl( $path );
+ }
+
+ // check if path appears to be sane, no parent traversals, and is in this repo's temp zone.
+ $repoTempPath = $repo->getZonePath( 'temp' );
+ if ( ( ! $repo->validateFilename( $path ) ) ||
+ ( strpos( $path, $repoTempPath ) !== 0 ) ) {
+ wfDebug( "UploadStash: tried to construct an UploadStashFile from a file that should already exist at '$path', but path is not valid\n" );
+ throw new UploadStashBadPathException( 'path is not valid' );
+ }
+
+ // check if path exists! and is a plain file.
+ if ( ! $repo->fileExists( $path, FileRepo::FILES_ONLY ) ) {
+ wfDebug( "UploadStash: tried to construct an UploadStashFile from a file that should already exist at '$path', but path is not found\n" );
+ throw new UploadStashFileNotFoundException( 'cannot find path, or not a plain file' );
+ }
+
+
+
+ parent::__construct( false, $repo, $path, false );
+
+ $this->name = basename( $this->path );
+ }
+
+ /**
+ * A method needed by the file transforming and scaling routines in File.php
+ * We do not necessarily care about doing the description at this point
+ * However, we also can't return the empty string, as the rest of MediaWiki demands this (and calls to imagemagick
+ * convert require it to be there)
+ *
+ * @return String: dummy value
+ */
+ public function getDescriptionUrl() {
+ return $this->getUrl();
+ }
+
+ /**
+ * Get the path for the thumbnail (actually any transformation of this file)
+ * The actual argument is the result of thumbName although we seem to have
+ * buggy code elsewhere that expects a boolean 'suffix'
+ *
+ * @param $thumbName String: name of thumbnail (e.g. "120px-123456.jpg" ), or false to just get the path
+ * @return String: path thumbnail should take on filesystem, or containing directory if thumbname is false
+ */
+ public function getThumbPath( $thumbName = false ) {
+ $path = dirname( $this->path );
+ if ( $thumbName !== false ) {
+ $path .= "/$thumbName";
+ }
+ return $path;
+ }
+
+ /**
+ * Return the file/url base name of a thumbnail with the specified parameters
+ *
+ * @param $params Array: handler-specific parameters
+ * @return String: base name for URL, like '120px-12345.jpg', or null if there is no handler
+ */
+ function thumbName( $params ) {
+ return $this->getParamThumbName( $this->getUrlName(), $params );
+ }
+
+
+ /**
+ * Given the name of the original, i.e. Foo.jpg, and scaling parameters, returns filename with appropriate extension
+ * This is abstracted from getThumbName because we also use it to calculate the thumbname the file should have on
+ * remote image scalers
+ *
+ * @param String $urlName: A filename, like MyMovie.ogx
+ * @param Array $parameters: scaling parameters, like array( 'width' => '120' );
+ * @return String|null parameterized thumb name, like 120px-MyMovie.ogx.jpg, or null if no handler found
+ */
+ function getParamThumbName( $urlName, $params ) {
+ if ( !$this->getHandler() ) {
+ return null;
+ }
+ $extension = $this->getExtension();
+ list( $thumbExt, ) = $this->handler->getThumbType( $extension, $this->getMimeType(), $params );
+ $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $urlName;
+ if ( $thumbExt != $extension ) {
+ $thumbName .= ".$thumbExt";
+ }
+ return $thumbName;
+ }
+
+ /**
+ * Helper function -- given a 'subpage', return the local URL e.g. /wiki/Special:UploadStash/subpage
+ * @param {String} $subPage
+ * @return {String} local URL for this subpage in the Special:UploadStash space.
+ */
+ private function getSpecialUrl( $subPage ) {
+ return SpecialPage::getTitleFor( 'UploadStash', $subPage )->getLocalURL();
+ }
+
+
+ /**
+ * Get a URL to access the thumbnail
+ * This is required because the model of how files work requires that
+ * the thumbnail urls be predictable. However, in our model the URL is not based on the filename
+ * (that's hidden in the session)
+ *
+ * @param $thumbName String: basename of thumbnail file -- however, we don't want to use the file exactly
+ * @return String: URL to access thumbnail, or URL with partial path
+ */
+ public function getThumbUrl( $thumbName = false ) {
+ wfDebug( __METHOD__ . " getting for $thumbName \n" );
+ return $this->getSpecialUrl( 'thumb/' . $this->getUrlName() . '/' . $thumbName );
+ }
+
+ /**
+ * The basename for the URL, which we want to not be related to the filename.
+ * Will also be used as the lookup key for a thumbnail file.
+ *
+ * @return String: base url name, like '120px-123456.jpg'
+ */
+ public function getUrlName() {
+ if ( ! $this->urlName ) {
+ $this->urlName = $this->sessionKey;
+ }
+ return $this->urlName;
+ }
+
+ /**
+ * Return the URL of the file, if for some reason we wanted to download it
+ * We tend not to do this for the original file, but we do want thumb icons
+ *
+ * @return String: url
+ */
+ public function getUrl() {
+ if ( !isset( $this->url ) ) {
+ $this->url = $this->getSpecialUrl( 'file/' . $this->getUrlName() );
+ }
+ return $this->url;
+ }
+
+ /**
+ * Parent classes use this method, for no obvious reason, to return the path (relative to wiki root, I assume).
+ * But with this class, the URL is unrelated to the path.
+ *
+ * @return String: url
+ */
+ public function getFullUrl() {
+ return $this->getUrl();
+ }
+
+
+ /**
+ * Getter for session key (the session-unique id by which this file's location & metadata is stored in the session)
+ *
+ * @return String: session key
+ */
+ public function getSessionKey() {
+ return $this->sessionKey;
+ }
+
+ /**
+ * Remove the associated temporary file
+ * @return Status: success
+ */
+ public function remove() {
+ return $this->repo->freeTemp( $this->path );
+ }
+
+}
+
+class UploadStashNotAvailableException extends MWException {};
+class UploadStashFileNotFoundException extends MWException {};
+class UploadStashBadPathException extends MWException {};
+class UploadStashBadVersionException extends MWException {};
+class UploadStashFileException extends MWException {};
+class UploadStashZeroLengthFileException extends MWException {};
+
diff --git a/includes/zhtable/Makefile.py b/includes/zhtable/Makefile.py
index 26e229df..a7822b0b 100644
--- a/includes/zhtable/Makefile.py
+++ b/includes/zhtable/Makefile.py
@@ -1,25 +1,33 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @author Philip
-import tarfile, zipfile
+import tarfile as tf
+import zipfile as zf
import os, re, shutil, sys, platform
pyversion = platform.python_version()
-islinux = platform.system().lower() == 'linux' or False
+islinux = platform.system().lower() == 'linux'
-if pyversion[:3] in ['2.5', '2.6', '2.7']:
+if pyversion[:3] in ['2.6', '2.7']:
import urllib as urllib_request
import codecs
- uniopen = codecs.open
- def unichr2(i):
- if sys.maxunicode >= 0x10000 or i < 0x10000:
- return unichr(i)
- else:
- return unichr(0xD7C0+(i>>10)) + unichr(0xDC00+(i&0x3FF))
+ open = codecs.open
+ _unichr = unichr
+ if sys.maxunicode < 0x10000:
+ def unichr(i):
+ if i < 0x10000:
+ return _unichr(i)
+ else:
+ return _unichr( 0xD7C0 + ( i>>10 ) ) + _unichr( 0xDC00 + ( i & 0x3FF ) )
elif pyversion[:2] == '3.':
import urllib.request as urllib_request
- uniopen = open
- unichr2 = chr
+ unichr = chr
+
+def unichr2( *args ):
+ return [unichr( int( i.split('<')[0][2:], 16 ) ) for i in args]
+
+def unichr3( *args ):
+ return [unichr( int( i[2:7], 16 ) ) for i in args if i[2:7]]
# DEFINE
SF_MIRROR = 'easynews'
@@ -28,14 +36,14 @@ SCIM_PINYIN_VER = '0.5.91'
LIBTABE_VER = '0.2.3'
# END OF DEFINE
-def GetFileFromURL( url, dest ):
- if os.path.isfile(dest):
+def download( url, dest ):
+ if os.path.isfile( dest ):
print( 'File %s up to date.' % dest )
return
global islinux
if islinux:
# we use wget instead urlretrieve under Linux,
- # because wget will display details like download progress
+ # because wget could display details like download progress
os.system('wget %s' % url)
else:
print( 'Downloading from [%s] ...' % url )
@@ -43,191 +51,200 @@ def GetFileFromURL( url, dest ):
print( 'Download complete.\n' )
return
-def GetFileFromUnihan( path ):
- print( 'Extracting files from %s ...' % path )
- text = zipfile.ZipFile(path).read('Unihan_Variants.txt')
- uhfile = uniopen('Unihan_Variants.txt', 'w')
- uhfile.write(text)
- uhfile.close()
- return
+def uncompress( fp, member, encoding = 'U8' ):
+ name = member.rsplit( '/', 1 )[-1]
+ print( 'Extracting %s ...' % name )
+ fp.extract( member )
+ shutil.move( member, name )
+ if '/' in member:
+ shutil.rmtree( member.split( '/', 1 )[0] )
+ return open( name, 'rb', encoding, 'ignore' )
-def GetFileFromTar( path, member, rename ):
- print( 'Extracting %s from %s ...' % (rename, path) )
- tarfile.open(path, 'r:gz').extract(member)
- shutil.move(member, rename)
- tree_rmv = member.split('/')[0]
- shutil.rmtree(tree_rmv)
- return
-
-def ReadBIG5File( dest ):
- print( 'Reading and decoding %s ...' % dest )
- f1 = uniopen( dest, 'r', encoding='big5hkscs', errors='replace' )
- text = f1.read()
- text = text.replace( '\ufffd', '\n' )
- f1.close()
- f2 = uniopen( dest, 'w', encoding='utf8' )
- f2.write(text)
- f2.close()
- return text
+unzip = lambda path, member, encoding = 'U8': \
+ uncompress( zf.ZipFile( path ), member, encoding )
-def ReadFile( dest ):
- print( 'Reading and decoding %s ...' % dest )
- f = uniopen( dest, 'r', encoding='utf8' )
- ret = f.read()
- f.close()
- return ret
+untargz = lambda path, member, encoding = 'U8': \
+ uncompress( tf.open( path, 'r:gz' ), member, encoding )
-def ReadUnihanFile( dest ):
- print( 'Reading and decoding %s ...' % dest )
- f = uniopen( dest, 'r', encoding='utf8' )
- t2s_code = []
- s2t_code = []
- while True:
- line = f.readline()
- if line:
- if line.startswith('#'):
- continue
- elif not line.find('kSimplifiedVariant') == -1:
- temp = line.split('kSimplifiedVariant')
- t2s_code.append( ( temp[0].strip(), temp[1].strip() ) )
- elif not line.find('kTraditionalVariant') == -1:
- temp = line.split('kTraditionalVariant')
- s2t_code.append( ( temp[0].strip(), temp[1].strip() ) )
- else:
+def parserCore( fp, pos, beginmark = None, endmark = None ):
+ if beginmark and endmark:
+ start = False
+ else: start = True
+ mlist = set()
+ for line in fp:
+ if beginmark and line.startswith( beginmark ):
+ start = True
+ continue
+ elif endmark and line.startswith( endmark ):
break
- f.close()
- return ( t2s_code, s2t_code )
+ if start and not line.startswith( '#' ):
+ elems = line.split()
+ if len( elems ) < 2:
+ continue
+ elif len( elems[0] ) > 1:
+ mlist.add( elems[pos] )
+ return mlist
-def RemoveRows( text, num ):
- text = re.sub( '.*\s*', '', text, num)
- return text
+def tablesParser( path, name ):
+ """ Read file from scim-tables and parse it. """
+ global SCIM_TABLES_VER
+ src = 'scim-tables-%s/tables/zh/%s' % ( SCIM_TABLES_VER, name )
+ fp = untargz( path, src, 'U8' )
+ return parserCore( fp, 1, 'BEGIN_TABLE', 'END_TABLE' )
-def RemoveOneCharConv( text ):
- preg = re.compile('^.\s*$', re.MULTILINE)
- text = preg.sub( '', text )
- return text
+ezbigParser = lambda path: tablesParser( path, 'EZ-Big.txt.in' )
+wubiParser = lambda path: tablesParser( path, 'Wubi.txt.in' )
+zrmParser = lambda path: tablesParser( path, 'Ziranma.txt.in' )
-def ConvertToChar( code ):
- code = code.split('<')[0]
- return unichr2( int( code[2:], 16 ) )
-
-def GetDefaultTable( code_table ):
- char_table = {}
- for ( f, t ) in code_table:
- if f and t:
- from_char = ConvertToChar( f )
- to_chars = [ConvertToChar( code ) for code in t.split()]
- char_table[from_char] = to_chars
- return char_table
-
-def GetManualTable( dest ):
- text = ReadFile( dest )
- temp1 = text.split()
- char_table = {}
- for elem in temp1:
- elem = elem.strip('|')
- if elem:
- temp2 = elem.split( '|', 1 )
- from_char = unichr2( int( temp2[0][2:7], 16 ) )
- to_chars = [unichr2( int( code[2:7], 16 ) ) for code in temp2[1].split('|')]
- char_table[from_char] = to_chars
- return char_table
-
-def GetValidTable( src_table ):
- valid_table = {}
- for f, t in src_table.items():
- valid_table[f] = t[0]
- return valid_table
-
-def GetToManyRules( src_table ):
- tomany_table = {}
- for f, t in src_table.items():
- for i in range(1, len(t)):
- tomany_table[t[i]] = True
- return tomany_table
-
-def RemoveRules( dest, table ):
- text = ReadFile( dest )
- temp1 = text.split()
- for elem in temp1:
- f = ''
- t = ''
- elem = elem.strip().replace( '"', '' ).replace( '\'', '' )
- if '=>' in elem:
- if elem.startswith( '=>' ):
- t = elem.replace( '=>', '' ).strip()
- elif elem.endswith( '=>' ):
- f = elem.replace( '=>', '' ).strip()
- else:
- temp2 = elem.split( '=>' )
- f = temp2[0].strip()
- t = temp2[1].strip()
- try:
- table.pop(f, t)
- continue
- except:
- continue
+def phraseParser( path ):
+ """ Read phrase_lib.txt and parse it. """
+ global SCIM_PINYIN_VER
+ src = 'scim-pinyin-%s/data/phrase_lib.txt' % SCIM_PINYIN_VER
+ dst = 'phrase_lib.txt'
+ fp = untargz( path, src, 'U8' )
+ return parserCore( fp, 0 )
+
+def tsiParser( path ):
+ """ Read tsi.src and parse it. """
+ src = 'libtabe/tsi-src/tsi.src'
+ dst = 'tsi.src'
+ fp = untargz( path, src, 'big5hkscs' )
+ return parserCore( fp, 0 )
+
+def unihanParser( path ):
+ """ Read Unihan_Variants.txt and parse it. """
+ fp = unzip( path, 'Unihan_Variants.txt', 'U8' )
+ t2s = dict()
+ s2t = dict()
+ for line in fp:
+ if line.startswith( '#' ):
+ continue
else:
- f = t = elem
+ elems = line.split()
+ if len( elems ) < 3:
+ continue
+ type = elems.pop( 1 )
+ elems = unichr2( *elems )
+ if type == 'kTraditionalVariant':
+ s2t[elems[0]] = elems[1:]
+ elif type == 'kSimplifiedVariant':
+ t2s[elems[0]] = elems[1:]
+ fp.close()
+ return ( t2s, s2t )
+
+def applyExcludes( mlist, path ):
+ """ Apply exclude rules from path to mlist. """
+ excludes = open( path, 'rb', 'U8' ).read().split()
+ excludes = [word.split( '#' )[0].strip() for word in excludes]
+ excludes = '|'.join( excludes )
+ excptn = re.compile( '.*(?:%s).*' % excludes )
+ diff = [mword for mword in mlist if excptn.search( mword )]
+ mlist.difference_update( diff )
+ return mlist
+
+def charManualTable( path ):
+ fp = open( path, 'rb', 'U8' )
+ ret = {}
+ for line in fp:
+ elems = line.split( '#' )[0].split( '|' )
+ elems = unichr3( *elems )
+ if len( elems ) > 1:
+ ret[elems[0]] = elems[1:]
+ return ret
+
+def toManyRules( src_table ):
+ tomany = set()
+ for ( f, t ) in src_table.iteritems():
+ for i in range( 1, len( t ) ):
+ tomany.add( t[i] )
+ return tomany
+
+def removeRules( path, table ):
+ fp = open( path, 'rb', 'U8' )
+ texc = list()
+ for line in fp:
+ elems = line.split( '=>' )
+ f = t = elems[0].strip()
+ if len( elems ) == 2:
+ t = elems[1].strip()
+ f = f.strip('"').strip("'")
+ t = t.strip('"').strip("'")
if f:
try:
- table.pop(f)
+ table.pop( f )
except:
- x = 1
+ pass
if t:
- for temp_f, temp_t in table.copy().items():
- if temp_t == t:
- table.pop(temp_f)
+ texc.append( t )
+ texcptn = re.compile( '^(?:%s)$' % '|'.join( texc ) )
+ for (tmp_f, tmp_t) in table.copy().iteritems():
+ if texcptn.match( tmp_t ):
+ table.pop( tmp_f )
return table
-def DictToSortedList1( src_table ):
- return sorted( src_table.items(), key = lambda m: m[0] ) #sorted( temp_table, key = lambda m: len( m[0] ) )
+def customRules( path ):
+ fp = open( path, 'rb', 'U8' )
+ ret = dict()
+ for line in fp:
+ elems = line.split( '#' )[0].split()
+ if len( elems ) > 1:
+ ret[elems[0]] = elems[1]
+ return ret
-def DictToSortedList2( src_table ):
- return sorted( src_table.items(), key = lambda m: m[1] )
+def dictToSortedList( src_table, pos ):
+ return sorted( src_table.items(), key = lambda m: m[pos] )
-def Converter( string, conv_table ):
+def translate( text, conv_table ):
i = 0
- while i < len(string):
- for j in range(len(string) - i, 0, -1):
- f = string[i:][:j]
+ while i < len( text ):
+ for j in range( len( text ) - i, 0, -1 ):
+ f = text[i:][:j]
t = conv_table.get( f )
if t:
- string = string[:i] + t + string[i:][j:]
+ text = text[:i] + t + text[i:][j:]
i += len(t) - 1
break
i += 1
- return string
+ return text
+
+def manualWordsTable( path, conv_table, reconv_table ):
+ fp = open( path, 'rb', 'U8' )
+ reconv_table = {}
+ wordlist = [line.split( '#' )[0].strip() for line in fp]
+ wordlist = list( set( wordlist ) )
+ wordlist.sort( key = len, reverse = True )
+ while wordlist:
+ word = wordlist.pop()
+ new_word = translate( word, conv_table )
+ rcv_word = translate( word, reconv_table )
+ if word != rcv_word:
+ reconv_table[word] = word
+ reconv_table[new_word] = word
+ return reconv_table
-def GetDefaultWordsTable( src_wordlist, src_tomany, char_conv_table, char_reconv_table ):
- wordlist = list( set( src_wordlist ) )
+def defaultWordsTable( src_wordlist, src_tomany, char_conv_table, char_reconv_table ):
+ wordlist = list( src_wordlist )
wordlist.sort( key = len, reverse = True )
word_conv_table = {}
word_reconv_table = {}
+ conv_table = char_conv_table.copy()
+ reconv_table = char_reconv_table.copy()
+ tomanyptn = re.compile( '(?:%s)' % '|'.join( src_tomany ) )
while wordlist:
- conv_table = {}
- reconv_table = {}
conv_table.update( word_conv_table )
- conv_table.update( char_conv_table )
reconv_table.update( word_reconv_table )
- reconv_table.update( char_reconv_table )
word = wordlist.pop()
- new_word_len = word_len = len(word)
+ new_word_len = word_len = len( word )
while new_word_len == word_len:
- rvt_test = False
- for char in word:
- rvt_test = rvt_test or src_tomany.get(char)
- test_word = Converter( word, reconv_table )
- new_word = Converter( word, conv_table )
- if not reconv_table.get( new_word ):
- if not test_word == word:
- word_conv_table[word] = new_word
- word_reconv_table[new_word] = word
- elif rvt_test:
- rvt_word = Converter( new_word, reconv_table )
- if not rvt_word == word:
- word_conv_table[word] = new_word
- word_reconv_table[new_word] = word
+ add = False
+ test_word = translate( word, reconv_table )
+ new_word = translate( word, conv_table )
+ if not reconv_table.get( new_word ) \
+ and ( test_word != word \
+ or ( tomanyptn.search( word ) \
+ and word != translate( new_word, reconv_table ) ) ):
+ word_conv_table[word] = new_word
+ word_reconv_table[new_word] = word
try:
word = wordlist.pop()
except IndexError:
@@ -235,205 +252,98 @@ def GetDefaultWordsTable( src_wordlist, src_tomany, char_conv_table, char_reconv
new_word_len = len(word)
return word_reconv_table
-def GetManualWordsTable( src_wordlist, conv_table ):
- src_wordlist = [items.split('#')[0].strip() for items in src_wordlist]
- wordlist = list( set( src_wordlist ) )
- wordlist.sort( key = len, reverse = True )
- reconv_table = {}
- while wordlist:
- word = wordlist.pop()
- new_word = Converter( word, conv_table )
- reconv_table[new_word] = word
- return reconv_table
-
-def CustomRules( dest ):
- text = ReadFile( dest )
- temp = text.split()
- ret = dict()
- for i in range( 0, len( temp ), 2 ):
- ret[temp[i]] = temp[i + 1]
- return ret
-
-def GetPHPArray( table ):
+def PHPArray( table ):
lines = ['\'%s\' => \'%s\',' % (f, t) for (f, t) in table if f and t]
- #lines = ['"%s"=>"%s",' % (f, t) for (f, t) in table]
return '\n'.join(lines)
-def RemoveSameChar( src_table ):
- dst_table = {}
- for f, t in src_table.items():
- if f != t:
- dst_table[f] = t
- return dst_table
-
def main():
#Get Unihan.zip:
url = 'http://www.unicode.org/Public/UNIDATA/Unihan.zip'
han_dest = 'Unihan.zip'
- GetFileFromURL( url, han_dest )
+ download( url, han_dest )
# Get scim-tables-$(SCIM_TABLES_VER).tar.gz:
url = 'http://%s.dl.sourceforge.net/sourceforge/scim/scim-tables-%s.tar.gz' % ( SF_MIRROR, SCIM_TABLES_VER )
tbe_dest = 'scim-tables-%s.tar.gz' % SCIM_TABLES_VER
- GetFileFromURL( url, tbe_dest )
+ download( url, tbe_dest )
# Get scim-pinyin-$(SCIM_PINYIN_VER).tar.gz:
url = 'http://%s.dl.sourceforge.net/sourceforge/scim/scim-pinyin-%s.tar.gz' % ( SF_MIRROR, SCIM_PINYIN_VER )
pyn_dest = 'scim-pinyin-%s.tar.gz' % SCIM_PINYIN_VER
- GetFileFromURL( url, pyn_dest )
+ download( url, pyn_dest )
# Get libtabe-$(LIBTABE_VER).tgz:
url = 'http://%s.dl.sourceforge.net/sourceforge/libtabe/libtabe-%s.tgz' % ( SF_MIRROR, LIBTABE_VER )
lbt_dest = 'libtabe-%s.tgz' % LIBTABE_VER
- GetFileFromURL( url, lbt_dest )
-
- # Extract the file from a comressed files
-
- # Unihan.txt Simp. & Trad
- GetFileFromUnihan( han_dest )
-
- # Make word lists
- t_wordlist = []
- s_wordlist = []
-
- # EZ.txt.in Trad
- src = 'scim-tables-%s/tables/zh/EZ-Big.txt.in' % SCIM_TABLES_VER
- dst = 'EZ.txt.in'
- GetFileFromTar( tbe_dest, src, dst )
- text = ReadFile( dst )
- text = text.split( 'BEGIN_TABLE' )[1].strip()
- text = text.split( 'END_TABLE' )[0].strip()
- text = re.sub( '.*\t', '', text )
- text = RemoveOneCharConv( text )
- t_wordlist.extend( text.split() )
+ download( url, lbt_dest )
- # Wubi.txt.in Simp
- src = 'scim-tables-%s/tables/zh/Wubi.txt.in' % SCIM_TABLES_VER
- dst = 'Wubi.txt.in'
- GetFileFromTar( tbe_dest, src, dst )
- text = ReadFile( dst )
- text = text.split( 'BEGIN_TABLE' )[1].strip()
- text = text.split( 'END_TABLE' )[0].strip()
- text = re.sub( '.*\t(.*?)\t\d*', '\g<1>', text )
- text = RemoveOneCharConv( text )
- s_wordlist.extend( text.split() )
-
- # Ziranma.txt.in Simp
- src = 'scim-tables-%s/tables/zh/Ziranma.txt.in' % SCIM_TABLES_VER
- dst = 'Ziranma.txt.in'
- GetFileFromTar( tbe_dest, src, dst )
- text = ReadFile( dst )
- text = text.split( 'BEGIN_TABLE' )[1].strip()
- text = text.split( 'END_TABLE' )[0].strip()
- text = re.sub( '.*\t(.*?)\t\d*', '\g<1>', text )
- text = RemoveOneCharConv( text )
- s_wordlist.extend( text.split() )
-
- # phrase_lib.txt Simp
- src = 'scim-pinyin-%s/data/phrase_lib.txt' % SCIM_PINYIN_VER
- dst = 'phrase_lib.txt'
- GetFileFromTar( pyn_dest, src, dst )
- text = ReadFile( 'phrase_lib.txt' )
- text = re.sub( '(.*)\t\d\d*.*', '\g<1>', text)
- text = RemoveRows( text, 5 )
- text = RemoveOneCharConv( text )
- s_wordlist.extend( text.split() )
-
- # tsi.src Trad
- src = 'libtabe/tsi-src/tsi.src'
- dst = 'tsi.src'
- GetFileFromTar( lbt_dest, src, dst )
- text = ReadBIG5File( 'tsi.src' )
- text = re.sub( ' \d.*', '', text.replace('# ', ''))
- text = RemoveOneCharConv( text )
- t_wordlist.extend( text.split() )
-
- # remove duplicate elements
- t_wordlist = list( set( t_wordlist ) )
- s_wordlist = list( set( s_wordlist ) )
-
- # simpphrases_exclude.manual Simp
- text = ReadFile( 'simpphrases_exclude.manual' )
- temp = text.split()
- s_string = '\n'.join( s_wordlist )
- for elem in temp:
- s_string = re.sub( '.*%s.*\n' % elem, '', s_string )
- s_wordlist = s_string.split('\n')
+ # Unihan.txt
+ ( t2s_1tomany, s2t_1tomany ) = unihanParser( han_dest )
+
+ t2s_1tomany.update( charManualTable( 'trad2simp.manual' ) )
+ s2t_1tomany.update( charManualTable( 'simp2trad.manual' ) )
- # tradphrases_exclude.manual Trad
- text = ReadFile( 'tradphrases_exclude.manual' )
- temp = text.split()
- t_string = '\n'.join( t_wordlist )
- for elem in temp:
- t_string = re.sub( '.*%s.*\n' % elem, '', t_string )
- t_wordlist = t_string.split('\n')
+ t2s_1to1 = dict( [( f, t[0] ) for ( f, t ) in t2s_1tomany.iteritems()] )
+ s2t_1to1 = dict( [( f, t[0] ) for ( f, t ) in s2t_1tomany.iteritems()] )
- # Make char to char convertion table
- # Unihan.txt, dict t2s_code, s2t_code = { 'U+XXXX': 'U+YYYY( U+ZZZZ) ... ', ... }
- ( t2s_code, s2t_code ) = ReadUnihanFile( 'Unihan_Variants.txt' )
- # dict t2s_1tomany = { '\uXXXX': '\uYYYY\uZZZZ ... ', ... }
- t2s_1tomany = {}
- t2s_1tomany.update( GetDefaultTable( t2s_code ) )
- t2s_1tomany.update( GetManualTable( 'trad2simp.manual' ) )
- # dict s2t_1tomany
- s2t_1tomany = {}
- s2t_1tomany.update( GetDefaultTable( s2t_code ) )
- s2t_1tomany.update( GetManualTable( 'simp2trad.manual' ) )
- # dict t2s_1to1 = { '\uXXXX': '\uYYYY', ... }; t2s_trans = { 'ddddd': '', ... }
- t2s_1to1 = GetValidTable( t2s_1tomany )
- s_tomany = GetToManyRules( t2s_1tomany )
- # dict s2t_1to1; s2t_trans
- s2t_1to1 = GetValidTable( s2t_1tomany )
- t_tomany = GetToManyRules( s2t_1tomany )
- # remove noconvert rules
- t2s_1to1 = RemoveRules( 'trad2simp_noconvert.manual', t2s_1to1 )
- s2t_1to1 = RemoveRules( 'simp2trad_noconvert.manual', s2t_1to1 )
+ s_tomany = toManyRules( t2s_1tomany )
+ t_tomany = toManyRules( s2t_1tomany )
+
+ # noconvert rules
+ t2s_1to1 = removeRules( 'trad2simp_noconvert.manual', t2s_1to1 )
+ s2t_1to1 = removeRules( 'simp2trad_noconvert.manual', s2t_1to1 )
- # Make word to word convertion table
+ # the supper set for word to word conversion
t2s_1to1_supp = t2s_1to1.copy()
s2t_1to1_supp = s2t_1to1.copy()
- # trad2simp_supp_set.manual
- t2s_1to1_supp.update( CustomRules( 'trad2simp_supp_set.manual' ) )
- # simp2trad_supp_set.manual
- s2t_1to1_supp.update( CustomRules( 'simp2trad_supp_set.manual' ) )
- # simpphrases.manual
- text = ReadFile( 'simpphrases.manual' )
- s_wordlist_manual = text.split('\n')
- t2s_word2word_manual = GetManualWordsTable(s_wordlist_manual, s2t_1to1_supp)
- t2s_word2word_manual.update( CustomRules( 'toSimp.manual' ) )
- # tradphrases.manual
- text = ReadFile( 'tradphrases.manual' )
- t_wordlist_manual = text.split('\n')
- s2t_word2word_manual = GetManualWordsTable(t_wordlist_manual, t2s_1to1_supp)
- s2t_word2word_manual.update( CustomRules( 'toTrad.manual' ) )
- # t2s_word2word
+ t2s_1to1_supp.update( customRules( 'trad2simp_supp_set.manual' ) )
+ s2t_1to1_supp.update( customRules( 'simp2trad_supp_set.manual' ) )
+
+ # word to word manual rules
+ t2s_word2word_manual = manualWordsTable( 'simpphrases.manual', s2t_1to1_supp, t2s_1to1_supp )
+ t2s_word2word_manual.update( customRules( 'toSimp.manual' ) )
+ s2t_word2word_manual = manualWordsTable( 'tradphrases.manual', t2s_1to1_supp, s2t_1to1_supp )
+ s2t_word2word_manual.update( customRules( 'toTrad.manual' ) )
+
+ # word to word rules from input methods
+ t_wordlist = set()
+ s_wordlist = set()
+ t_wordlist.update( ezbigParser( tbe_dest ),
+ tsiParser( lbt_dest ) )
+ s_wordlist.update( wubiParser( tbe_dest ),
+ zrmParser( tbe_dest ),
+ phraseParser( pyn_dest ) )
+
+ # exclude
+ s_wordlist = applyExcludes( s_wordlist, 'simpphrases_exclude.manual' )
+ t_wordlist = applyExcludes( t_wordlist, 'tradphrases_exclude.manual' )
+
s2t_supp = s2t_1to1_supp.copy()
s2t_supp.update( s2t_word2word_manual )
t2s_supp = t2s_1to1_supp.copy()
t2s_supp.update( t2s_word2word_manual )
- t2s_word2word = GetDefaultWordsTable( s_wordlist, s_tomany, s2t_1to1_supp, t2s_supp )
- ## toSimp.manual
+
+ # parse list to dict
+ t2s_word2word = defaultWordsTable( s_wordlist, s_tomany, s2t_1to1_supp, t2s_supp )
t2s_word2word.update( t2s_word2word_manual )
- # s2t_word2word
- s2t_word2word = GetDefaultWordsTable( t_wordlist, t_tomany, t2s_1to1_supp, s2t_supp )
- ## toTrad.manual
+ s2t_word2word = defaultWordsTable( t_wordlist, t_tomany, t2s_1to1_supp, s2t_supp )
s2t_word2word.update( s2t_word2word_manual )
# Final tables
# sorted list toHans
- t2s_1to1 = RemoveSameChar( t2s_1to1 )
- s2t_1to1 = RemoveSameChar( s2t_1to1 )
- toHans = DictToSortedList1( t2s_1to1 ) + DictToSortedList2( t2s_word2word )
+ t2s_1to1 = dict( [( f, t ) for ( f, t ) in t2s_1to1.iteritems() if f != t] )
+ toHans = dictToSortedList( t2s_1to1, 0 ) + dictToSortedList( t2s_word2word, 1 )
# sorted list toHant
- toHant = DictToSortedList1( s2t_1to1 ) + DictToSortedList2( s2t_word2word )
+ s2t_1to1 = dict( [( f, t ) for ( f, t ) in s2t_1to1.iteritems() if f != t] )
+ toHant = dictToSortedList( s2t_1to1, 0 ) + dictToSortedList( s2t_word2word, 1 )
# sorted list toCN
- toCN = DictToSortedList2( CustomRules( 'toCN.manual' ) )
+ toCN = dictToSortedList( customRules( 'toCN.manual' ), 1 )
# sorted list toHK
- toHK = DictToSortedList2( CustomRules( 'toHK.manual' ) )
+ toHK = dictToSortedList( customRules( 'toHK.manual' ), 1 )
# sorted list toSG
- toSG = DictToSortedList2( CustomRules( 'toSG.manual' ) )
+ toSG = dictToSortedList( customRules( 'toSG.manual' ), 1 )
# sorted list toTW
- toTW = DictToSortedList2( CustomRules( 'toTW.manual' ) )
+ toTW = dictToSortedList( customRules( 'toTW.manual' ), 1 )
# Get PHP Array
php = '''<?php
@@ -442,30 +352,32 @@ def main():
*
* Automatically generated using code and data in includes/zhtable/
* Do not modify directly!
+ *
+ * @file
*/
$zh2Hant = array(\n'''
- php += GetPHPArray( toHant )
- php += '\n);\n\n$zh2Hans = array(\n'
- php += GetPHPArray( toHans )
- php += '\n);\n\n$zh2TW = array(\n'
- php += GetPHPArray( toTW )
- php += '\n);\n\n$zh2HK = array(\n'
- php += GetPHPArray( toHK )
- php += '\n);\n\n$zh2CN = array(\n'
- php += GetPHPArray( toCN )
- php += '\n);\n\n$zh2SG = array(\n'
- php += GetPHPArray( toSG )
- php += '\n);'
+ php += PHPArray( toHant ) \
+ + '\n);\n\n$zh2Hans = array(\n' \
+ + PHPArray( toHans ) \
+ + '\n);\n\n$zh2TW = array(\n' \
+ + PHPArray( toTW ) \
+ + '\n);\n\n$zh2HK = array(\n' \
+ + PHPArray( toHK ) \
+ + '\n);\n\n$zh2CN = array(\n' \
+ + PHPArray( toCN ) \
+ + '\n);\n\n$zh2SG = array(\n' \
+ + PHPArray( toSG ) \
+ + '\n);'
- f = uniopen( 'ZhConversion.php', 'w', encoding = 'utf8' )
+ f = open( 'ZhConversion.php', 'wb', encoding = 'utf8' )
print ('Writing ZhConversion.php ... ')
f.write( php )
f.close()
#Remove temp files
print ('Deleting temp files ... ')
- os.remove('EZ.txt.in')
+ os.remove('EZ-Big.txt.in')
os.remove('phrase_lib.txt')
os.remove('tsi.src')
os.remove('Unihan_Variants.txt')
diff --git a/includes/zhtable/simp2trad.manual b/includes/zhtable/simp2trad.manual
index bb4eb7ef..eb5fa396 100644
--- a/includes/zhtable/simp2trad.manual
+++ b/includes/zhtable/simp2trad.manual
@@ -10,6 +10,7 @@ U+04CA0䲠|U+09C06鰆|
U+04CA1䲡|U+09C0C鰌|
U+04CA2䲢|U+09C27鰧|
U+04CA3䲣|U+04C77䱷|
+U+04DAE䶮|U+09F91龑|
U+04E07万|U+0842C萬|U+04E07万|
U+04E0E与|U+08207與|U+04E0E与|
U+04E11丑|U+04E11丑|U+0919C醜|
@@ -41,7 +42,7 @@ U+051C6准|U+051C6准|U+06E96準|
U+051E0几|U+05E7E幾|U+051E0几|
U+051EB凫|U+09CE7鳧|U+09CEC鳬|
U+051FA出|U+051FA出|U+09F63齣|
-U+05212划|U+05212划|U+05283劃|
+U+05212划|U+05283劃|U+05212划|
U+0522B别|U+05225別|U+05F46彆|
U+0522E刮|U+0522E刮|U+098B3颳|
U+05236制|U+05236制|U+088FD製|
@@ -64,7 +65,7 @@ U+05401吁|U+05401吁|U+07C72籲|
U+05408合|U+05408合|U+095A4閤|
U+0540A吊|U+0540A吊|U+05F14弔|
U+0540C同|U+0540C同|U+08855衕|
-U+0540E后|U+0540E后|U+05F8C後|
+U+0540E后|U+05F8C後|U+0540E后|
U+05411向|U+05411向|U+056AE嚮|U+066CF曏|
U+0542F启|U+0555F啟|U+05553啓|
U+05446呆|U+05446呆|U+07343獃|
@@ -369,4 +370,4 @@ U+2A38A𪎊|U+09EA8麨|
U+2A38B𪎋|U+04D34䴴|
U+2A38C𪎌|U+09EB3麳|
U+2A68F𪚏|U+2A600𪘀|
-U+2A690𪚐|U+2A62F𪘯| \ No newline at end of file
+U+2A690𪚐|U+2A62F𪘯|
diff --git a/includes/zhtable/simpphrases.manual b/includes/zhtable/simpphrases.manual
index a015a34b..4b699e26 100644
--- a/includes/zhtable/simpphrases.manual
+++ b/includes/zhtable/simpphrases.manual
@@ -135,6 +135,15 @@
乾忠
乾淳
李乾顺
+黄润乾
+男性为乾
+男为乾
+阳为乾
+乾一组
+乾一坛
+陈乾生
+陈公乾生
+字乾生
不着痕迹
不着边际
与着
@@ -2222,3 +2231,5 @@
醯壶
苧烯
近角聪信
+米泽瑠美
+峯岸南 \ No newline at end of file
diff --git a/includes/zhtable/simpphrases_exclude.manual b/includes/zhtable/simpphrases_exclude.manual
index 4606041f..3e9d3ecc 100644
--- a/includes/zhtable/simpphrases_exclude.manual
+++ b/includes/zhtable/simpphrases_exclude.manual
@@ -17,4 +17,5 @@
摺叠
-餗 \ No newline at end of file
+餗
+安甯 \ No newline at end of file
diff --git a/includes/zhtable/toCN.manual b/includes/zhtable/toCN.manual
index e3c12d0b..54e95765 100644
--- a/includes/zhtable/toCN.manual
+++ b/includes/zhtable/toCN.manual
@@ -228,6 +228,7 @@
夜学 夜校
华乐 民乐
中樂 民乐
+軍中樂園 军中乐园
华乐街 华乐街
屋价 房价
計程車 出租车
@@ -277,4 +278,4 @@
矽钢 矽钢
侏儸紀 侏罗纪
甚麽 什么
-甚麼 什么 \ No newline at end of file
+甚麼 什么
diff --git a/includes/zhtable/toHK.manual b/includes/zhtable/toHK.manual
index 10a3dfcb..53b354c7 100644
--- a/includes/zhtable/toHK.manual
+++ b/includes/zhtable/toHK.manual
@@ -2239,3 +2239,4 @@
分布 分佈
分布于 分佈於
分布於 分佈於
+想象 想像 \ No newline at end of file
diff --git a/includes/zhtable/toSimp.manual b/includes/zhtable/toSimp.manual
index f424ee73..da04b82e 100644
--- a/includes/zhtable/toSimp.manual
+++ b/includes/zhtable/toSimp.manual
@@ -41,10 +41,26 @@
乾鹄 乾鹄
乾鹊 乾鹊
乾龙 乾龙
+张法乾 张法乾
+旋乾转坤 旋乾转坤
天道为乾 天道为乾
易经·乾 易经·乾
易经乾 易经乾
乾务 乾务
+黄润乾 黄润乾
+男性为乾 男性为乾
+男为乾 男为乾
+阳为乾 阳为乾
+男性为乾 男性为乾
+男性爲乾 男性为乾
+男为乾 男为乾
+男爲乾 男为乾
+阳为乾 阳为乾
+陽爲乾 阳为乾
+乾一组 乾一组
+乾一坛 乾一坛
+陈乾生 陈乾生
+陈公乾生 陈公乾生
柳诒徵 柳诒徵
於夫罗 於夫罗
於梨华 於梨华
@@ -86,6 +102,8 @@
答覆 答复
反反覆覆 反反复复
重覆 重复
+覆核 复核
+覆查 复查
鬱姓 鬱姓
鬱氏 鬱氏
侏儸紀 侏罗纪
@@ -140,3 +158,5 @@
標誌著 标志着
近角聪信 近角聪信
修鍊 修炼
+米泽瑠美 米泽瑠美
+太閤 太阁
diff --git a/includes/zhtable/toTW.manual b/includes/zhtable/toTW.manual
index 2ce16f3f..a638e86b 100644
--- a/includes/zhtable/toTW.manual
+++ b/includes/zhtable/toTW.manual
@@ -50,7 +50,6 @@
以太网 乙太網
位图 點陣圖
例程 常式
-信道 通道
光标 游標
光盘 光碟
光驱 光碟機
@@ -329,7 +328,6 @@
平治 賓士
奔驰 賓士
積架 捷豹
-福士 福斯
雪铁龙 雪鐵龍
萬事得 馬自達
拿破仑 拿破崙
@@ -410,3 +408,5 @@
館裏 館裡
系列裏 系列裡
村子裏 村子裡
+青霉素 青黴素
+想象 想像
diff --git a/includes/zhtable/toTrad.manual b/includes/zhtable/toTrad.manual
index b3459054..0c79178f 100644
--- a/includes/zhtable/toTrad.manual
+++ b/includes/zhtable/toTrad.manual
@@ -137,6 +137,46 @@
于韋斯屈萊 于韋斯屈萊
于克-蘭多縣 于克-蘭多縣
于斯納爾斯貝里 于斯納爾斯貝里
+夏于喬 夏于喬
涂澤民 涂澤民
涂長望 涂長望
-台历 枱曆 \ No newline at end of file
+涂敏恆 涂敏恆
+台历 枱曆
+艷后 艷后
+廢后 廢后
+后髮座 后髮座
+后髮星系團 后髮星系團
+后髮FK型星 后髮FK型星
+后海灣 后海灣
+賈后 賈后
+賢后 賢后
+呂后 呂后
+蟻后 蟻后
+馬格里布 馬格里布
+佳里鎮 佳里鎮
+埔裡社撫墾局 埔裏社撫墾局
+埔裏社撫墾局 埔裏社撫墾局
+有只採 有只採
+任何表達 任何表達
+會干擾 會干擾
+党項 党項
+余三勝 余三勝
+簡筑翎 簡筑翎
+楊雅筑 楊雅筑
+杰威爾音樂 杰威爾音樂
+尸羅精舍 尸羅精舍
+索馬里 索馬里
+騰格里 騰格里
+村里長 村里長
+進制 進制
+模范三軍 模范三軍
+黃詩杰 黃詩杰
+陳冲 陳冲
+劉佳怜 劉佳怜
+范賢惠 范賢惠
+于國治 于國治
+于楓 于楓
+黎吉雲 黎吉雲
+于飛島 于飛島
+鄉愿 鄉愿
+奇迹 奇蹟
diff --git a/includes/zhtable/trad2simp.manual b/includes/zhtable/trad2simp.manual
index 4aed7e1d..6cbc3ee5 100644
--- a/includes/zhtable/trad2simp.manual
+++ b/includes/zhtable/trad2simp.manual
@@ -90,12 +90,14 @@ U+071EC燬|U+06BC1毁|
U+07343獃|U+05446呆|
U+07515甕|U+074EE瓮|
U+07526甦|U+082CF苏|
+U+0752F甯|U+05B81宁|
U+0756B畫|U+0753B画|U+05212划|
U+07575畵|U+0753B画|U+05212划|
U+075E0痠|U+09178酸|
U+07652癒|U+06108愈|
U+07661癡|U+075F4痴|
U+076C3盃|U+0676F杯|
+U+0771E眞|U+0771F真|
U+077AD瞭|U+04E86了|
U+077C7矇|U+08499蒙|
U+07843硃|U+06731朱|
@@ -228,6 +230,7 @@ U+09EF4黴|U+09709霉|
U+09F15鼕|U+051AC冬|
U+09F47齇|U+09F44齄|
U+09F63齣|U+051FA出|
+U+09F91龑|U+04DAE䶮|
U+21ED5𡻕|U+05C81岁|
U+26A99𦪙|U+0447D䑽|
U+2895B𨥛|U+28C40𨱀|
diff --git a/includes/zhtable/tradphrases.manual b/includes/zhtable/tradphrases.manual
index f3a95335..5a832a60 100644
--- a/includes/zhtable/tradphrases.manual
+++ b/includes/zhtable/tradphrases.manual
@@ -30,6 +30,26 @@
7隻
8隻
9隻
+0只支援
+1只支援
+2只支援
+3只支援
+4只支援
+5只支援
+6只支援
+7只支援
+8只支援
+9只支援
+0只支持
+1只支持
+2只支持
+3只支持
+4只支持
+5只支持
+6只支持
+7只支持
+8只支持
+9只支持
百隻
千隻
萬隻
@@ -53,6 +73,7 @@
多只是
多只需
多只會
+多只用
大只能
大只可
大只在
@@ -109,6 +130,9 @@
7天後
8天後
9天後
+天後來
+天後天
+天後半
後印
萬象
並存著
@@ -241,6 +265,7 @@
幹的停當
乾巴
偎乾
+眼乾
偷雞不著
几絲
划著
@@ -431,6 +456,7 @@
並發現
並發展
並發動
+並發布
火並非
舉手表
揮手表
@@ -649,6 +675,8 @@
牽一髮
白發其事
后髮座
+后髮星系團
+后髮FK型星
波髮藻
辮髮
逋髮
@@ -698,6 +726,10 @@
櫛髮工
鬒髮
模范棒棒堂
+模范三軍
+模范七棒
+模范14棒
+模范21棒
顏範
儀範
典範
@@ -734,6 +766,7 @@
置言成範
吾爲之範我馳驅
天地為範
+範數
丰采
丰標不凡
丰神
@@ -874,6 +907,8 @@
裏勾外連
裏手
水里鄉
+水里溪
+水里濁水溪
二里頭
年歷史
西歷史
@@ -881,6 +916,7 @@
國歷代
國歷任
國歷屆
+國歷經
新歷史
夏歷史
百花曆
@@ -926,6 +962,14 @@
格里高利曆
共和曆
掛曆
+曆獄
+天文曆表
+日心曆表
+地心曆表
+復活節曆表
+月球曆表
+伊爾汗曆表
+延曆
共和歷史
厤物之意
爰定祥厤
@@ -1001,7 +1045,6 @@
一鍋麵
伊府麵
藥麵兒
-洋麵
意大利麵
湯下麵
茶麵
@@ -1032,6 +1075,7 @@
太僕
僮僕
金僕姑
+僕婢
樸實
樸訥
樸念仁
@@ -1358,10 +1402,6 @@
昇平
爾冬陞
澹臺
-涂謹申
-涂鴻欽
-涂壯勳
-涂醒哲
拜託
委託
輓曲
@@ -1860,6 +1900,7 @@
批准的
核准的
為準
+準直
擺鐘
編鐘
碰鐘
@@ -2029,6 +2070,9 @@
任何鐘錶
任何鐘
任何錶
+任何表示
+任何表達
+任何表演
選手表現
選手表達
選手表示
@@ -2081,7 +2125,6 @@
銫鐘
數字鐘錶
數字鐘
-數字錶
顯示鐘錶
顯示鐘
顯示錶
@@ -2288,8 +2331,13 @@
7餘
8餘
9餘
+余姓
余光生
余光中
+余思敏
+余威德
+余子明
+余三勝
崑山
崑曲
崑腔
@@ -2361,7 +2409,7 @@
弔場
弔書
弔詞
-弔死
+弔死問孤
弔死問疾
弔撒
弔喪
@@ -2872,6 +2920,8 @@
陽歷史
額我略歷史
黃歷史
+天曆
+天歷史
美醜
獻醜
出醜
@@ -2898,7 +2948,7 @@
醜聞
醜語
母醜
-齣子
+一齣子
齣兒
賣獃
發獃
@@ -2935,6 +2985,7 @@
普鼕鼕
鼕鼕鼓
令人髮指
+爆發指數
開發
剪其髮
吐哺捉髮
@@ -2964,7 +3015,7 @@
細如髮
繫於一髮
膚髮
-華髮
+生華髮
蒼髮
被髮佯狂
被髮入山
@@ -3007,6 +3058,7 @@
對表中
對表明
不準確
+並不準確
一伙頭
一伙食
一半只
@@ -3314,6 +3366,7 @@
南宮适
大蜡
子云
+分子雲
小价
歲聿云暮
崖广
@@ -3690,6 +3743,17 @@
灕水
點裡
這只是
+這只不
+這只容
+這只允
+這只採
+這只用
+有只是
+有只不
+有只容
+有只允
+有只採
+有只用
葉叶琹
胡子昂
包括
@@ -3807,6 +3871,8 @@
于韋斯屈萊
于克-蘭多縣
于斯納爾斯貝里
+夏于喬
+涂姓
涂坤
涂天相
涂序瑄
@@ -3815,6 +3881,12 @@
涂羽卿
涂逢年
涂長望
+涂謹申
+涂鴻欽
+涂壯勳
+涂醒哲
+涂善妮
+涂敏恆
總裁制
故云
強制作用
@@ -3894,8 +3966,294 @@
注釋
月面
修杰楷
+修杰麟
學裡
獄裡
館裡
系列裡
村子裡
+艷后
+廢后
+妖后
+后海灣
+仙后
+賈后
+賢后
+蜂后
+皇后
+王后
+王侯后
+母后
+武后
+歌后
+影后
+封后
+太后
+天后
+呂后
+后里
+后街
+后羿
+后稷
+后座
+后平路
+后安路
+后土
+后北街
+后冠
+望后石
+后角
+蟻后
+后妃
+大周后
+小周后
+染殿后
+准三后
+風后
+風後,
+人如風後入江雲
+中風後
+屏風後
+颱風後
+颳風後
+整風後
+打風後
+遇風後
+聞風後
+逆風後
+順風後
+大風後
+馬格里布
+劃入
+中庄子
+埔裏社撫墾局
+懸掛
+僱傭
+四捨六入
+宿舍
+會干擾
+代表
+高清愿
+瓷製
+竹製
+絲製
+莜麵
+劃入
+簡筑翎
+楊雅筑
+魔杰座
+杰威爾音樂
+彭于晏
+尸羅精舍
+索馬里 # (及以下)避免里海=>裏海的轉換
+西西里
+騰格里
+阿里
+村里長
+進制
+黃詩杰
+陳冲
+何杰
+劉佳怜
+于小惠
+于品海
+于耘婕
+于洋
+于澄
+于光新
+范賢惠
+于國治
+于楓
+于熙珍
+涂善妮
+邱于庭
+熊杰
+卜云吉
+黎吉雲
+于飛島
+代表
+水無怜奈
+傲遊 # 浏览器名
+夏于喬
+賭后
+后海灣
+立后綜
+甲后路
+劉芸后
+謝華后
+趙惠后
+趙威后
+聖后
+陳有后
+許虬
+網遊
+狄志杰
+伊適杰
+于冠華
+于台煙
+于雲鶴
+于忠肅集
+于友澤
+于和偉
+于來山
+于樂
+于天龍
+于謹
+于榮光
+電波鐘
+余三勝
+掛名
+啟發式
+舞后
+甄后
+郭后
+0年 # 協助分詞
+1年
+2年
+3年
+4年
+5年
+6年
+7年
+8年
+9年
+0年
+1年
+2年
+3年
+4年
+5年
+6年
+7年
+8年
+9年
+〇年
+零年
+一年
+兩年
+二年
+三年
+四年
+五年
+六年
+七年
+八年
+九年
+十年
+百年
+千年
+萬年
+億年
+周后
+0周後
+1周後
+2周後
+3周後
+4周後
+5周後
+6周後
+7周後
+8周後
+9周後
+0周後
+1周後
+2周後
+3周後
+4周後
+5周後
+6周後
+7周後
+8周後
+9周後
+零周後
+〇周後
+一周後
+二周後
+兩周後
+三周後
+四周後
+五周後
+六周後
+七周後
+八周後
+九周後
+十周後
+百周後
+千周後
+萬周後
+億周後
+幾周後
+多周後
+前往
+后瑞站
+帝后臺
+新井里美
+樗里子
+伊達里子
+濱田里佳子
+尊后
+叶志穗
+叶不二子
+于立成
+山谷道
+李志喜
+于欣
+于少保
+于海
+於海邊
+於海上
+于凌辰
+于魁智
+于鬯
+于仲文
+于再清
+于震
+於震前
+於震后
+於震中
+固定制
+毗婆尸佛
+尸棄佛
+划船
+划不來
+划拳
+划槳
+划動
+划艇
+划行
+划算
+總裁制
+恒生
+嚴云農
+手裏劍
+秦莊襄王
+伊東怜
+衛後莊公
+餘量
+並行
+郁郁青青
+協防
+對表格
+對表示
+對表達
+對表演
+對表明
+了然後
+戴表元
+張樂于張徐
+余力為
+葉叶琴
+万俟
+幾個
+澀谷區
+協調
+選手
+併發症
+併發重症
+併發模式
+併發型模式
+金色長髮
+紅色長髮
+一頭長髮
+的長髮
+黑色長髮
diff --git a/includes/zhtable/tradphrases_exclude.manual b/includes/zhtable/tradphrases_exclude.manual
index 5fec98b2..6ed245c3 100644
--- a/includes/zhtable/tradphrases_exclude.manual
+++ b/includes/zhtable/tradphrases_exclude.manual
@@ -318,3 +318,12 @@
註釋
浮遊
冶鍊
+裡子
+裡外
+單隻
+聯係
+那裏
+殺虫藥
+好家伙
+姦污
+併發